My Software Portfolio
More projects are being added soon. Somehow it's a project just to add projects to this page.
Paint By Numbers
Paint By Numbers is an Android app that helps you calculate the amount of paint needed based on the size of the wall and the medium of the wall. You can upload an image and the app will analyze the colors and proportions to provide an accurate estimate.I was patroned by an artist in Austin Texas, he was looking for an app that does what this one is achieving. Not sure but it may be the first of its kind.
// MainActivity.kt
import android.app.Activity
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.drawable.BitmapDrawable
import android.net.Uri
import android.os.Bundle
import android.widget.Button
import android.widget.ImageView
import androidx.appcompat.app.AppCompatActivity
import androidx.palette.graphics.Palette
import com.github.dhaval2404.imagepicker.ImagePicker
class MainActivity : AppCompatActivity() {
private var selectedImageUri: Uri? = null
private val imagePickerRequestCode = 1001
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val selectImageButton: Button = findViewById(R.id.selectImageButton)
val imageView: ImageView = findViewById(R.id.imageView)
val nextButton: Button = findViewById(R.id.nextButton)
selectImageButton.setOnClickListener {
ImagePicker.with(this)
.galleryOnly()
.start(imagePickerRequestCode)
}
nextButton.setOnClickListener {
selectedImageUri?.let { uri ->
val bitmap = (imageView.drawable as BitmapDrawable).bitmap
val colors = getDominantColors(bitmap)
val intent = Intent(this, PaintCalculatorActivity::class.java).apply {
putExtra("imageUri", uri.toString())
putExtra("colors", HashMap(colors))
}
startActivity(intent)
}
}
}
private fun getDominantColors(bitmap: Bitmap): Map {
val palette = Palette.from(bitmap).generate()
val totalPopulation = palette.swatches.sumOf { it.population }
return palette.swatches.associate { swatch ->
val color = String.format("#%06X", (0xFFFFFF and swatch.rgb))
val proportion = swatch.population.toFloat() / totalPopulation
color to proportion
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == imagePickerRequestCode) {
if (resultCode == Activity.RESULT_OK) {
selectedImageUri = data?.data
val imageView: ImageView = findViewById(R.id.imageView)
imageView.setImageURI(selectedImageUri)
} else if (resultCode == ImagePicker.RESULT_ERROR) {
// Handle error
}
}
}
}
// PaintCalculatorActivity.kt
import android.net.Uri
import android.os.Bundle
import android.widget.Button
import android.widget.EditText
import android.widget.ImageView
import android.widget.Spinner
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
class PaintCalculatorActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_paint_calculator)
val selectedImageView: ImageView = findViewById(R.id.selectedImageView)
val heightEditText: EditText = findViewById(R.id.heightEditText)
val widthEditText: EditText = findViewById(R.id.widthEditText)
val materialSpinner: Spinner = findViewById(R.id.materialSpinner)
val calculateButton: Button = findViewById(R.id.calculateButton)
val resultTextView: TextView = findViewById(R.id.resultTextView)
val imageUriString = intent.getStringExtra("imageUri")
val imageUri = Uri.parse(imageUriString)
selectedImageView.setImageURI(imageUri)
val colors = intent.getSerializableExtra("colors") as? HashMap
calculateButton.setOnClickListener {
val height = heightEditText.text.toString().toDoubleOrNull() ?: 0.0
val width = widthEditText.text.toString().toDoubleOrNull() ?: 0.0
val area = height * width
val material = materialSpinner.selectedItem.toString()
val paintPerColor = calculatePaintNeededForColors(area, material, colors ?: hashMapOf())
resultTextView.text = paintPerColor
}
}
private fun calculatePaintNeededForColors(area: Double, material: String, colors: HashMap): String {
val coverage = when (material) {
"Wood" -> 300.0
"Brick" -> 250.0
"Glass" -> 350.0
"Vinyl" -> 320.0
"Plastic" -> 280.0
else -> 300.0
}
val paintNeeded = area / coverage
return colors.entries.joinToString("\n") { (color, proportion) ->
val paintForColor = paintNeeded * proportion
val roundedPaintNeeded = String.format("%.2f", paintForColor)
val colorDescription = describeColor(color)
"You need $roundedPaintNeeded gallons of paint for color $color ($colorDescription)"
}
}
private fun describeColor(color: String): String {
val rgb = Integer.parseInt(color.substring(1), 16)
val hsv = FloatArray(3)
android.graphics.Color.colorToHSV(rgb, hsv)
return when {
hsv[2] > 0.9 -> "Light"
hsv[2] < 0.2 -> "Dark"
hsv[0] in 0.0..30.0 || hsv[0] in 330.0..360.0 -> "Red"
hsv[0] in 30.0..90.0 -> "Yellow"
hsv[0] in 90.0..150.0 -> "Green"
hsv[0] in 150.0..210.0 -> "Cyan"
hsv[0] in 210.0..270.0 -> "Blue"
hsv[0] in 270.0..330.0 -> "Magenta"
else -> "Unknown"
}
}
HubCityInsured.com
HubCityInsured.com is a cutting-edge insurance lead management platform built with ASP.NET. Connected to an SQL database, it efficiently gathers insurance leads through a secure online form and features a robust employee dashboard to view and manage these leads. Designed with scalability in mind, the platform is set to integrate with approximately 20 additional websites, streamlining insurance data collection and management across multiple channels.
Platform Introduction
Employee Dashboard Demo
using Microsoft.AspNetCore.Mvc;
using HubCityInsured.Data;
using HubCityInsured.Models;
using System.Linq;
using Microsoft.EntityFrameworkCore;
namespace HubCityInsured.Controllers
{
public class LeadsController : Controller
{
private readonly ApplicationDbContext _context;
public LeadsController(ApplicationDbContext context)
{
_context = context;
}
// Display all leads
public IActionResult Index()
{
var leads = _context.Leads.OrderByDescending(l => l.SubmissionDate).ToList();
return View(leads);
}
// Display lead details
public IActionResult Details(int id)
{
var lead = _context.Leads.FirstOrDefault(l => l.Id == id);
if (lead == null)
{
return NotFound();
}
return View(lead);
}
}
}
State Capital Game
State Capital Game is an interactive educational mobile application built with .NET MAUI specifically for Android devices. Originally developed to help my daughter learn US state capitals in a fun and engaging way, this app challenges players with multiple choice quizzes that reinforce geographical knowledge. With real-time scoring, audio feedback for correct and incorrect answers, and an intuitive interface, the game transforms learning into a dynamic and enjoyable experience. Look out for its release this summer!
// MainPage.xaml.cs snippet
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Maui.Controls;
using Plugin.Maui.Audio;
using Microsoft.Maui.Storage;
namespace IveysGame
{
public partial class MainPage : ContentPage
{
private List _states;
private StateCapital _currentQuestion;
private string _correctAnswer;
private int _score = 0;
private int _incorrectCount = 0;
private Random _rnd = new Random();
private IAudioManager _audioManager;
public MainPage()
{
InitializeComponent();
// Initialize the audio manager.
_audioManager = AudioManager.Current;
// List of all 50 US states and their capitals. believe me they are there
_states = new List
{
new StateCapital { State = "Alabama", Capital = "Montgomery" },
new StateCapital { State = "Alaska", Capital = "Juneau" },
// ... additional states ...
new StateCapital { State = "Wyoming", Capital = "Cheyenne" }
};
LoadNewQuestion();
}
// Method to load a new question for the quiz.
private void LoadNewQuestion()
{
if (_incorrectCount >= 3)
return;
// Hide feedback and choose a random state.
FeedbackLabel.IsVisible = false;
var questionIndex = _rnd.Next(_states.Count);
_currentQuestion = _states[questionIndex];
QuestionLabel.Text = $"Which is the capital of {_currentQuestion.State}?";
_correctAnswer = _currentQuestion.Capital;
// Prepare and shuffle answer choices.
var answers = new List { _correctAnswer };
var wrongAnswers = _states.Where(s => s.Capital != _correctAnswer)
.OrderBy(x => _rnd.Next())
.Take(3)
.Select(s => s.Capital)
.ToList();
answers.AddRange(wrongAnswers);
answers = answers.OrderBy(a => _rnd.Next()).ToList();
OptionButton1.Text = answers[0];
OptionButton2.Text = answers[1];
OptionButton3.Text = answers[2];
OptionButton4.Text = answers[3];
// Reset buttons' background color.
OptionButton1.BackgroundColor = Color.FromArgb("#eeeeee");
OptionButton2.BackgroundColor = Color.FromArgb("#eeeeee");
OptionButton3.BackgroundColor = Color.FromArgb("#eeeeee");
OptionButton4.BackgroundColor = Color.FromArgb("#eeeeee");
}
// there is more code, but had to trim it down to fit on this site.
}
public class StateCapital
{
public string State { get; set; }
public string Capital { get; set; }
}
}
50 States
The "50 States" program uses an SQL database connection to keep track of information for all 50 states. It includes a user-friendly UI for selecting states, viewing details, and linking to their Wikipedia pages. It was created using C# with WinForms and MsSql
// Form1.cs
using System;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Windows.Forms;
using System.Diagnostics;
namespace russo50states
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
this.Load += Form1_Load;
fittyStatescomboBox.SelectedIndexChanged += fittyStatescomboBox_SelectedIndexChanged;
selectState.MouseEnter += SelectState_MouseEnter;
selectState.MouseLeave += SelectState_MouseLeave;
selectState.Click += SelectState_Click;
label1.MouseEnter += SelectState_MouseEnter;
label1.MouseLeave += SelectState_MouseLeave;
label2.MouseEnter += OpenStateWiki_MouseEnter;
label2.MouseLeave += OpenStateWiki_MouseLeave;
labelUpdateStates.MouseEnter += UpdateStatesPanel_MouseEnter;
labelUpdateStates.MouseLeave += UpdateStatesPanel_MouseLeave;
labelUpdateStates.Click += UpdateStatesPanel_Click;
updateStatesPanel.MouseEnter += UpdateStatesPanel_MouseEnter;
updateStatesPanel.MouseLeave += UpdateStatesPanel_MouseLeave;
updateStatesPanel.Click += UpdateStatesPanel_Click;
openStateWiki.MouseEnter += OpenStateWiki_MouseEnter;
openStateWiki.MouseLeave += OpenStateWiki_MouseLeave;
openStateWiki.MouseClick += OpenStateWiki_Click;
updateStatesPanel.BackColor = Color.LightGray;
selectState.BackColor = Color.LightGray;
openStateWiki.BackColor = Color.LightGray;
openStateWiki.MouseClick += OpenStateWiki_Click;
}
private void Form1_Load(object sender, EventArgs e)
{
LoadStatesIntoComboBox();
LoadStatesData();
}
private void LoadStatesIntoComboBox()
{
using (var db = new DataClasses1DataContext())
{
var stateNames = db.States
.OrderBy(state => state.Name)
.Select(state => state.Name)
.ToList();
fittyStatescomboBox.DataSource = stateNames;
}
}
private void LoadStatesData(string selectedStateName = null)
{
using (var db = new DataClasses1DataContext())
{
var statesQuery = string.IsNullOrEmpty(selectedStateName)
? db.States.ToList()
: db.States.Where(state => state.Name.Equals(selectedStateName)).ToList();
stateDataGridView.DataSource = statesQuery;
}
}
private void fittyStatescomboBox_SelectedIndexChanged(object sender, EventArgs e)
{
if (fittyStatescomboBox.SelectedIndex >= 0)
{
var selectedStateName = fittyStatescomboBox.SelectedItem.ToString();
LoadStatesData(selectedStateName);
}
}
private void SelectState_MouseEnter(object sender, EventArgs e)
{
selectState.BackColor = Color.BlueViolet;
}
private void SelectState_MouseLeave(object sender, EventArgs e)
{
selectState.BackColor = Color.LightGray;
}
private void SelectState_Click(object sender, EventArgs e)
{
if (fittyStatescomboBox.SelectedIndex >= 0)
{
var selectedStateName = fittyStatescomboBox.SelectedItem.ToString();
GenerateAndOpenHTML(selectedStateName);
}
}
private void UpdateStatesPanel_Click(object sender, EventArgs e)
{
UpdateStatesForm updateForm = new UpdateStatesForm();
updateForm.Show();
}
private void UpdateStatesPanel_MouseEnter(object sender, EventArgs e)
{
updateStatesPanel.BackColor = Color.BlueViolet;
}
private void UpdateStatesPanel_MouseLeave(object sender, EventArgs e)
{
updateStatesPanel.BackColor = Color.LightGray;
}
private void OpenStateWiki_MouseEnter(object sender, EventArgs e)
{
openStateWiki.BackColor = Color.BlueViolet;
}
private void OpenStateWiki_MouseLeave(object sender, EventArgs e)
{
openStateWiki.BackColor = Color.LightGray;
}
private void OpenStateWiki_Click(object sender, EventArgs e)
{
if (fittyStatescomboBox.SelectedIndex >= 0)
{
var selectedStateName = fittyStatescomboBox.SelectedItem.ToString();
var wikiUrl = $"https://en.wikipedia.org/wiki/{selectedStateName.Replace(" ", "_")}";
try
{
Process.Start(new ProcessStartInfo(wikiUrl) { UseShellExecute = true });
}
catch (Exception ex)
{
MessageBox.Show($"Could not open the Wikipedia page: {ex.Message}");
}
}
else
{
MessageBox.Show("Please select a state first.");
}
}
private void GenerateAndOpenHTML(string stateName)
{
string filePath = Path.Combine(Path.GetTempPath(), $"{stateName}.html");
using (StreamWriter writer = new StreamWriter(filePath))
{
writer.WriteLine("");
writer.WriteLine($"Details for {stateName}
");
using (var db = new DataClasses1DataContext())
{
var stateData = db.States.FirstOrDefault(state => state.Name.Equals(stateName));
if (stateData != null)
{
writer.WriteLine($"Name: {stateData.Name}
");
writer.WriteLine($"Population: {stateData.Population}
");
writer.WriteLine($"Flag Description: {stateData.FlagDescription}
");
writer.WriteLine($"State Flower: {stateData.StateFlower}
");
writer.WriteLine($"State Bird: {stateData.StateBird}
");
writer.WriteLine($"Colors: {stateData.Colors}
");
writer.WriteLine($"Largest Cities: {stateData.LargestCities}
");
writer.WriteLine($"Capitol: {stateData.Capitol}
");
writer.WriteLine($"Median Income: {stateData.MedianIncome}
");
writer.WriteLine($"Computer Jobs Percentage: {stateData.ComputerJobsPercentage}%
");
}
else
{
writer.WriteLine("State data not found.
");
}
}
writer.WriteLine("");
}
Process.Start(new ProcessStartInfo(filePath) { UseShellExecute = true });
}
private void label3_Click(object sender, EventArgs e)
{
}
private void exitbutton_Click(object sender, EventArgs e)
{
Application.Exit();
}
}
}
// UpdateStatesForm.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO;
using System.Diagnostics;
namespace russo50states
{
public partial class UpdateStatesForm : Form
{
bool sidebarExpand;
public UpdateStatesForm()
{
InitializeComponent();
this.Load += UpdateStatesForm_Load;
}
public void LoadStatesData()
{
using (var db = new DataClasses1DataContext())
{
stateDataGridView.DataSource = db.States.ToList();
}
}
private void UpdateStatesForm_Load(object sender, EventArgs e)
{
LoadStatesData();
}
private void button1_Click(object sender, EventArgs e)
{
this.Close();
}
private void updateButton_Click(object sender, EventArgs e)
{
}
private void sidebarTimer_Tick(object sender, EventArgs e)
{
if (sidebarExpand)
{
sidebar.Width += 10;
if (sidebar.Width >= sidebar.MaximumSize.Width)
{
sidebarTimer.Stop();
}
}
else
{
sidebar.Width -= 10;
if (sidebar.Width <= sidebar.MinimumSize.Width)
{
sidebarTimer.Stop();
}
}
}
private void menuButton_Click(object sender, EventArgs e)
{
sidebarExpand = !sidebarExpand;
sidebarTimer.Start();
}
private void searchBut_Click(object sender, EventArgs e)
{
SearchForm searchForm = new SearchForm();
if (searchForm.ShowDialog() == DialogResult.OK)
{
string searchTerms = searchForm.SearchTerms;
PerformSearchAndGenerateHtml(searchTerms);
}
}
internal void UpdateDataGridView()
{
throw new NotImplementedException();
}
private void PerformSearchAndGenerateHtml(string searchTerms)
{
var terms = searchTerms.Split(new char[] { ' ', ',' }, StringSplitOptions.RemoveEmptyEntries);
using (var db = new DataClasses1DataContext())
{
var query = db.States.AsQueryable();
foreach (var term in terms)
{
query = query.Where(s => s.Name.Contains(term) ||
s.Capitol.Contains(term) ||
s.StateBird.Contains(term) ||
s.StateFlower.Contains(term) ||
(s.FlagDescription != null && s.FlagDescription.Contains(term)) ||
(s.Colors != null && s.Colors.Contains(term)) ||
(s.LargestCities != null && s.LargestCities.Contains(term)) ||
(s.MedianIncome.ToString().Contains(term)) ||
(s.ComputerJobsPercentage != null && s.ComputerJobsPercentage.ToString().Contains(term)));
}
var results = query.ToList();
if (results.Any())
{
GenerateHtml(results);
}
else
{
MessageBox.Show("No results found.");
}
}
}
private void GenerateHtml(List results)
{
string filePath = Path.Combine(Path.GetTempPath(), "SearchResults.html");
using (StreamWriter writer = new StreamWriter(filePath))
{
writer.WriteLine("Search Results ");
writer.WriteLine("Search Results
");
writer.WriteLine("");
foreach (var state in results)
{
writer.WriteLine($"- Name: {state.Name}, Capitol: {state.Capitol}, State Bird: {state.StateBird}, State Flower: {state.StateFlower}, Population: {state.Population}, etc.
");
}
writer.WriteLine("
");
writer.WriteLine("");
}
Process.Start(new ProcessStartInfo(filePath) { UseShellExecute = true });
}
private void sidebar_Paint(object sender, PaintEventArgs e)
{
}
private void sortButton_Click(object sender, EventArgs e)
{
SortForm sortForm = new SortForm();
if (sortForm.ShowDialog() == DialogResult.OK)
{
SortData(sortForm.SortColumn, sortForm.SortAscending);
}
}
private void SortData(string sortColumn, bool ascending)
{
using (var db = new DataClasses1DataContext())
{
var query = db.States.AsQueryable();
switch (sortColumn)
{
case "State":
query = ascending ? query.OrderBy(s => s.Name) : query.OrderByDescending(s => s.Name);
break;
case "Population":
query = ascending ? query.OrderBy(s => s.Population) : query.OrderByDescending(s => s.Population);
break;
case "MedianIncome":
query = ascending ? query.OrderBy(s => s.MedianIncome ?? 0) : query.OrderByDescending(s => s.MedianIncome ?? 0);
break;
case "ComputerJobPercentage":
query = ascending ? query.OrderBy(s => s.ComputerJobsPercentage ?? 0) : query.OrderByDescending(s => s.ComputerJobsPercentage ?? 0);
break;
}
stateDataGridView.DataSource = query.ToList();
}
}
private void filterButton_Click(object sender, EventArgs e)
{
using (var filterForm = new FilterForm())
{
if (filterForm.ShowDialog() == DialogResult.OK)
{
ApplyFilters(filterForm);
}
}
}
private void ApplyColumnVisibility(FilterForm filterForm)
{
stateDataGridView.Columns["Population"].Visible = filterForm.FilterByPopulation;
stateDataGridView.Columns["Capitol"].Visible = filterForm.FilterByCapital;
stateDataGridView.Columns["MedianIncome"].Visible = filterForm.FilterByMedianIncome;
stateDataGridView.Columns["StateBird"].Visible = filterForm.FilterByStateBird;
stateDataGridView.Columns["TechJobPercentage"].Visible = filterForm.FilterByTechJob;
stateDataGridView.Columns["FlagDescription"].Visible = filterForm.FilterByFlagDescription;
stateDataGridView.Columns["LargestCities"].Visible = filterForm.FilterByLargestCities;
}
private void ApplyFilters(FilterForm filterForm)
{
using (var db = new DataClasses1DataContext())
{
var query = db.States.AsQueryable();
if (!string.IsNullOrEmpty(filterForm.SelectedState) && filterForm.SelectedState != "All States")
{
query = query.Where(state => state.Name == filterForm.SelectedState);
}
if (filterForm.FilterByPopulation)
{
query = query.Where(state => state.Population.HasValue && state.Population > 0);
}
if (filterForm.FilterByCapital)
{
query = query.Where(state => state.Capitol != null && state.Capitol != "");
}
if (filterForm.FilterByMedianIncome)
{
query = query.Where(state => state.MedianIncome.HasValue && state.MedianIncome > 0);
}
if (filterForm.FilterByStateBird)
{
query = query.Where(state => state.StateBird != null && state.StateBird != "");
}
if (filterForm.FilterByTechJob)
{
query = query.Where(state => state.ComputerJobsPercentage.HasValue && state.ComputerJobsPercentage > 0);
}
if (filterForm.FilterByFlagDescription)
{
query = query.Where(state => state.FlagDescription != null && state.FlagDescription != "");
}
if (filterForm.FilterByLargestCities)
{
query = query.Where(state => state.LargestCities != null && state.LargestCities != "");
}
stateDataGridView.DataSource = query.ToList();
}
}
private void editButton_Click(object sender, EventArgs e)
{
EditStateForm editForm = new EditStateForm();
var dialogResult = editForm.ShowDialog();
if (dialogResult == DialogResult.OK)
{
LoadStatesData();
}
}
private void UpdateDataGridView(List filteredStates)
{
stateDataGridView.DataSource = filteredStates;
}
}
}
// SortForm.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace russo50states
{
public partial class SortForm : Form
{
public bool SortAscending { get; private set; }
public string SortColumn { get; private set; }
public SortForm()
{
InitializeComponent();
}
private void goButton_Click(object sender, EventArgs e)
{
if (yoMama.Checked)
{
System.Diagnostics.Process.Start("https://ponly.com/yo-mama-jokes/");
this.DialogResult = DialogResult.Cancel;
this.Close();
}
else
{
SortAscending = radioButtonAsc.Checked;
if (radioButtonState.Checked) SortColumn = "State";
else if (radioButtonPopulation.Checked) SortColumn = "Population";
else if (radioButtonMedIncome.Checked) SortColumn = "MedianIncome";
else if (radioButtonCompJobPercent.Checked) SortColumn = "ComputerJobPercentage";
this.DialogResult = DialogResult.OK;
this.Close();
}
}
}
ERD Builder
ERD Builder is a simple, lightweight web application built with ASP.NET and JavaScript designed to quickly create entity relationship diagrams using Crow’s Foot notation. It supports one-to-one, one-to-many, many-to-one, and many-to-many relationships – all without the complexity of traditional diagram software. Sometimes, simplicity is the key! A version of this is running at Mangogamer Token (subject to change – it’s a work in progress).
// JavaScript snippet for ERD Builder
document.addEventListener("DOMContentLoaded", function () {
let tableCount = 0;
let relationshipGroups = [];
let isDrawing = false;
let currentPoints = [];
let currentPolyline = null;
const tablesContainer = document.getElementById("tablesContainer");
const svg = document.getElementById("diagramSvg");
// Add an invisible background rectangle to capture all mouse events.
let bgRect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
bgRect.setAttribute("x", "0");
bgRect.setAttribute("y", "0");
bgRect.setAttribute("width", "100%");
bgRect.setAttribute("height", "100%");
bgRect.setAttribute("fill", "transparent");
svg.insertBefore(bgRect, svg.firstChild);
// Event listener for starting free-hand drawing of relationships.
svg.addEventListener("click", function (evt) {
if (evt.target.closest("g.final-relationship")) return;
let pt = getSVGCoordinates(evt);
if (!isDrawing) {
isDrawing = true;
currentPoints = [pt];
currentPolyline = createPolyline(currentPoints, "black");
svg.appendChild(currentPolyline);
} else {
currentPoints.push(pt);
updatePolyline(currentPolyline, currentPoints);
}
});
// ... additional functions for drawing polylines, handling relationships, etc. ...
function createPolyline(points, color) {
let poly = document.createElementNS("http://www.w3.org/2000/svg", "polyline");
poly.setAttribute("stroke", color);
poly.setAttribute("stroke-width", "2");
poly.setAttribute("fill", "none");
updatePolyline(poly, points);
return poly;
}
// Helper functions: updatePolyline, getSVGCoordinates, etc.
});
ChaserFood
ChaserFood is a web application using Razor Pages and ASP.NET MVC. It includes features such as user login, SQL database integration, inventory management, customer tracking, receipt printing, email validation, and barcode reading support. It utilizes C# and Java Script for backend logic.
// DonkeyDonor.cs
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace ChaserFood.Models
{
public class DonkeyDonor
{
[Key]
public int Id { get; set; }
[Required]
[StringLength(15)]
public string PhoneNumber { get; set; }
[Required]
[StringLength(50)]
public string FirstName { get; set; }
[Required]
[StringLength(50)]
public string LastName { get; set; }
[StringLength(100)]
public string StreetAddress { get; set; }
[StringLength(50)]
public string City { get; set; }
[StringLength(2)]
public string State { get; set; }
[StringLength(10)]
public string? Zip { get; set; }
[EmailAddress]
public string? EmailAddress { get; set; }
[Required]
public bool WithSchool { get; set; }
// Add this property
public ICollection Donations { get; set; }
public string? BusinessName { get; set; }
public string? Contact { get; set; }
public bool IsBusiness { get; set; }
}
}
// EmailHelper.cs
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
namespace ChaserFood.Models
{
public class EmailHelper
{
private readonly ILogger _logger;
private readonly HttpClient _httpClient;
public EmailHelper(ILogger logger, HttpClient httpClient)
{
_logger = logger;
_httpClient = httpClient;
}
public async Task SendEmailAsync(string toEmail, string subject, string body)
{
var request = new HttpRequestMessage
{
Method = HttpMethod.Post,
RequestUri = new Uri("https://api.smtp2go.com/v3/email/send"),
Content = new StringContent(JsonConvert.SerializeObject(new
{
api_key = "api-4F30CB4FFFD04A60A864AA54E1997FE2",
to = new[] { toEmail },
sender = "jnrusso57@sccsc.edu",
subject,
text_body = body,
html_body = body
}), Encoding.UTF8, "application/json")
};
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
try
{
_logger.LogInformation("Sending email to {Email} with subject {Subject}", toEmail, subject);
var response = await _httpClient.SendAsync(request);
if (response.IsSuccessStatusCode)
{
_logger.LogInformation("Email sent successfully to {Email}", toEmail);
return true;
}
else
{
var errorResponse = await response.Content.ReadAsStringAsync();
_logger.LogError("Failed to send email to {Email}. Status Code: {StatusCode}. Response: {Response}", toEmail, response.StatusCode, errorResponse);
return false;
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Exception occurred while sending email to {Email}", toEmail);
return false;
}
}
}
}
// LoginModel.cs
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
namespace ChaserFood.Pages.Account
{
[AllowAnonymous]
public class LoginModel : PageModel
{
private readonly UserManager _userManager;
private readonly SignInManager _signInManager;
private readonly ILogger _logger;
public LoginModel(SignInManager signInManager,
ILogger logger,
UserManager userManager)
{
_userManager = userManager;
_signInManager = signInManager;
_logger = logger;
}
[BindProperty]
public InputModel Input { get; set; }
public IList ExternalLogins { get; set; }
public string? ReturnUrl { get; set; }
[TempData]
public string? ErrorMessage { get; set; }
public class InputModel
{
[Required]
[EmailAddress]
public string Email { get; set; }
[Required]
[DataType(DataType.Password)]
public string Password { get; set; }
[Display(Name = "Remember me?")]
public bool RememberMe { get; set; }
}
public async Task OnGetAsync(string returnUrl = null)
{
if (!string.IsNullOrEmpty(ErrorMessage))
{
ModelState.AddModelError(string.Empty, ErrorMessage);
}
returnUrl ??= Url.Content("~/");
// Clear the existing external cookie to ensure a clean login process before it would just stay logged in
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
ReturnUrl = returnUrl;
}
public async Task OnPostAsync(string returnUrl = null)
{
returnUrl ??= Url.Content("~/");
if (ModelState.IsValid)
{
// This doesn't count login failures towards account lockout
// To enable password failures to trigger account lockout, set lockoutOnFailure: true
var result = await _signInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: false);
if (result.Succeeded)
{
_logger.LogInformation("User logged in.");
return LocalRedirect(returnUrl);
}
if (result.RequiresTwoFactor)
{
return RedirectToPage("./LoginWith2fa", new { ReturnUrl = returnUrl, RememberMe = Input.RememberMe });
}
if (result.IsLockedOut)
{
_logger.LogWarning("User account locked out.");
return RedirectToPage("./Lockout");
}
else
{
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return Page();
}
}
// If we got this far, something failed, redisplay form
return Page();
}
}
}
// BarcodeController.cs
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
using ChaserFood.Data;
using ChaserFood.Models;
using Newtonsoft.Json.Linq;
using System;
using System.IO;
using Microsoft.EntityFrameworkCore;
namespace ChaserFood.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class BarcodeController : ControllerBase
{
private readonly BarcodeReaderService _barcodeReaderService;
private readonly ApplicationDbContext _context;
public BarcodeController(BarcodeReaderService barcodeReaderService, ApplicationDbContext context)
{
_barcodeReaderService = barcodeReaderService;
_context = context;
}
[HttpPost("read")]
public async Task ReadBarcode([FromBody] BarcodeImageModel model)
{
if (model == null || string.IsNullOrEmpty(model.ImageData))
{
return BadRequest(new { success = false, message = "Invalid data" });
}
try
{
// Decode the base64 image data
var base64Data = model.ImageData.Split(',')[1];
var imageBytes = Convert.FromBase64String(base64Data);
// Save the image to a temporary file
var tempFileName = Path.GetTempFileName();
await System.IO.File.WriteAllBytesAsync(tempFileName, imageBytes);
// Read the barcode using your barcode reader API
var productDetails = await _barcodeReaderService.GetProductDetails(tempFileName);
if (productDetails != null)
{
var barcode = productDetails["barcode"]?.ToString();
var categoryName = productDetails["category"]?.ToString();
// Store product details in the database
var inventoryItem = new InventoryItem
{
Barcode = barcode,
Name = productDetails["name"]?.ToString(),
Category = categoryName,
Quantity = 1, // Default quantity
ExpirationDate = DateTime.Now.AddYears(1) // Default expiration date
};
_context.InventoryItems.Add(inventoryItem);
await _context.SaveChangesAsync();
return Ok(new { success = true, barcode = barcode });
}
return BadRequest(new { success = false, message = "Could not read barcode" });
}
catch (Exception ex)
{
return StatusCode(500, new { success = false, message = ex.Message });
}
}
[HttpGet("details/{barcode}")]
public async Task GetDetails(string barcode)
{
var item = await _context.InventoryItems.FirstOrDefaultAsync(i => i.Barcode == barcode);
if (item == null)
{
return NotFound();
}
return Ok(new { name = item.Name, category = item.Category, quantity = item.Quantity, expirationDate = item.ExpirationDate });
}
}
public class BarcodeImageModel
{
public string ImageData { get; set; }
}