From a1fddb35133a0956726c772ae47cbef541583cf1 Mon Sep 17 00:00:00 2001 From: slinky55 Date: Sat, 25 Apr 2026 13:14:08 -0500 Subject: [PATCH] bunch o stuff --- Components/Pages/Book.razor | 184 ++++++++++-------- Components/Pages/Book.razor.cs | 62 +++++- Components/Pages/Book.razor.css | 64 +++++- Components/Pages/Home.Razor.cs | 9 +- Components/Pages/Home.razor | 2 +- Components/Pages/admin/EditPages.razor | 44 ++++- Components/Pages/admin/EditPages.razor.cs | 44 ++++- Data.cs | 39 +++- DatabaseContext.cs | 42 ++++ Defaults.cs | 41 ++-- .../20260424050555_InitialCreate.Designer.cs | 173 ++++++++++++++++ Migrations/20260424050555_InitialCreate.cs | 125 ++++++++++++ Migrations/DatabaseContextModelSnapshot.cs | 170 ++++++++++++++++ Models.cs | 106 +++++++--- Program.cs | 12 +- Services.cs | 110 ++++++++--- wwwroot/uploads/exvwoomm.qgm.jpg | 0 wwwroot/uploads/uefue0ou.esy.png | Bin 0 -> 4909 bytes 18 files changed, 1037 insertions(+), 190 deletions(-) create mode 100644 Migrations/20260424050555_InitialCreate.Designer.cs create mode 100644 Migrations/20260424050555_InitialCreate.cs create mode 100644 Migrations/DatabaseContextModelSnapshot.cs create mode 100644 wwwroot/uploads/exvwoomm.qgm.jpg create mode 100644 wwwroot/uploads/uefue0ou.esy.png diff --git a/Components/Pages/Book.razor b/Components/Pages/Book.razor index e27ca70..6311b5d 100644 --- a/Components/Pages/Book.razor +++ b/Components/Pages/Book.razor @@ -1,86 +1,108 @@ @page "/book" +@rendermode InteractiveServer @using Microsoft.AspNetCore.Components.Forms -
-
-

Request an Appointment

-

Complete the form below and a technician will review your case.

+@if(!Complete) +{ +
+
+

Request an Appointment

+

Complete the form below and a technician will review your case.

+
+ + + + +
+
+

1. Appliance Details

+ +
+ + + + + + + + + + +
+ +
+ + + + + + + + + + + + + + + + +
+ +
+ + + +
+
+ +
+

2. Photos & Contact

+ +
+

Upload Photos/Video (10MB max)

+

Tip: A photo of the model number sticker helps us arrive with the right parts!

+ + + + @if (SelectedFiles.Any()) + { +
@SelectedFiles.Count files attached
+ } +
+ +
+ + + +
+ +
+ + + +
+
+
+ + +
- - - - -
-
-

1. Appliance Details

- -
- - - - - - - - - -
- -
- - - - - - - - - - - - - - - -
- -
- - -
-
- -
-

2. Photos & Contact

- -
-

Upload Photos/Video

-

Tip: A photo of the model number sticker helps us arrive with the right parts!

- - - - @if (SelectedFiles.Any()) - { -
@SelectedFiles.Count files attached
- } -
- -
- - -
- -
- - -
-
+} +else +{ +
+
+

Thank You!

+

We will be contacting you shortly.

+

Your request number is: @Model.RequestNumber

+ + 🏠 Back to Home +
- - - -
+
+} \ No newline at end of file diff --git a/Components/Pages/Book.razor.cs b/Components/Pages/Book.razor.cs index d3f0737..5cacf29 100644 --- a/Components/Pages/Book.razor.cs +++ b/Components/Pages/Book.razor.cs @@ -2,16 +2,70 @@ namespace ApplianceRepair.Components.Pages { - public partial class Book() + public static class RequestNumberGenerator { - private RepairRequestModel Model = new(); - private List SelectedFiles = new(); + private static readonly char[] _chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789".ToCharArray(); + + public static string Generate(int length = 6) + { + var result = new char[length]; + // Use Random.Shared in .NET 6+ for thread safety + for (int i = 0; i < length; i++) + { + result[i] = _chars[Random.Shared.Next(_chars.Length)]; + } + return new string(result); + } + } + + public partial class Book(RepairRequestReader repairRequestReader, RepairRequestMediaReader repairRequestMediaReader) + { + public RepairRequestModel Model = new(); + public List SelectedFiles = new(); + public bool Complete = false; private void HandleFiles(InputFileChangeEventArgs e) => SelectedFiles.AddRange(e.GetMultipleFiles()); private async Task HandleSubmit() { - // Logic to process the request + Model.RequestNumber = RequestNumberGenerator.Generate(); + Model.CreatedAt = DateTime.Now; + Model.UpdatedAt = DateTime.Now; + await repairRequestReader.AddRecord(Model); + + var imageUploadPath = Path.Combine(Environment.CurrentDirectory, "wwwroot", "uploads"); + if (!Directory.Exists(imageUploadPath)) Directory.CreateDirectory(imageUploadPath); + + foreach (var file in SelectedFiles) + { + try + { + var trustedFileName = Path.GetRandomFileName() + Path.GetExtension(file.Name); + var path = Path.Combine(imageUploadPath, trustedFileName); + + using var stream = file.OpenReadStream(maxAllowedSize: 1024 * 1024 * 10); + using var fileStream = new FileStream(path, FileMode.Create); + + await stream.CopyToAsync(fileStream); + + var mediaRecord = new RepairRequestMediaRecord() + { + CreatedAt = DateTime.Now, + UpdatedAt = DateTime.Now, + RequestNumber = Model.RequestNumber, + MediaPath = path, + }; + + await repairRequestMediaReader.AddRecord(mediaRecord); + } + catch (Exception ex) + { + // probably need to show this to the user somehow, something prettier tho + Console.WriteLine($"File: {file.Name} Error: {ex.Message}"); + } + } + + Complete = true; } } } diff --git a/Components/Pages/Book.razor.css b/Components/Pages/Book.razor.css index a2b25cf..2c10f89 100644 --- a/Components/Pages/Book.razor.css +++ b/Components/Pages/Book.razor.css @@ -93,7 +93,61 @@ transition: all 0.3s ease; } -/* Match your mobile responsiveness */ +.complete-container { + min-height: 80vh; + display: flex; + align-items: center; + justify-content: center; + text-align: center; + padding: 0 20px; +} + +.complete-content { + max-width: 800px; +} + +.complete-heading { + font-size: 2.5rem; + font-weight: 700; + margin-bottom: 1rem; + line-height: 1.2; +} + +.complete-subheading { + font-size: 1.2rem; + color: #555; + line-height: 1.5; +} + +::deep .btn-home { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 12px 24px; + background-color: #007bff; + color: white; + text-decoration: none; + border-radius: 8px; + font-weight: 600; + transition: background-color 0.2s ease, transform 0.1s ease; + border: none; + cursor: pointer; +} + +::deep .btn-home:hover { + background-color: #0056b3; + color: white; + text-decoration: none; +} + +::deep .btn-home:active { + transform: scale(0.98); +} + +.home-icon { + margin-right: 8px; +} + @media (max-width: 768px) { .form-grid { grid-template-columns: 1fr; @@ -102,4 +156,12 @@ .booking-form-wrapper { padding: 20px; } + + .complete-heading { + font-size: 1.8rem; /* Slightly smaller for small screens */ + } + + .complete-subheading { + font-size: 1rem; + } } diff --git a/Components/Pages/Home.Razor.cs b/Components/Pages/Home.Razor.cs index 2bb8ccb..49f494c 100644 --- a/Components/Pages/Home.Razor.cs +++ b/Components/Pages/Home.Razor.cs @@ -2,7 +2,7 @@ namespace ApplianceRepair.Components.Pages { - public partial class Home(IMemoryCache cache, HomePageReader homePageReader, ContentCardReader contentCardReader) + public partial class Home(IMemoryCache cache, HomePageReader homePageReader, ContentCardReader contentCardReader, BusinessConfigReader businessConfigReader) { private HomePageModel? Model; @@ -10,7 +10,12 @@ namespace ApplianceRepair.Components.Pages { if (!cache.TryGetValue(nameof(HomePageModel), out Model)) { - Model = await homePageReader.ReadLatestRecordWithModel(contentCardReader) ?? Defaults.DefaultHomePageContent; + var businessConfig = await businessConfigReader.ReadLatestRecord() ?? Defaults.DefaultBusinessConfig; + var latestHomeRecord = await homePageReader.ReadLatestRecord() ?? Defaults.DefaultHomePageContent; + var servicesList = await contentCardReader.ReadAllByPageAndGroup(HomePageModel.PageName, nameof(HomePageModel.ContentCardTypes.Services)) ?? []; + var trustList = await contentCardReader.ReadAllByPageAndGroup(HomePageModel.PageName, nameof(HomePageModel.ContentCardTypes.Trust)) ?? []; + + Model = new HomePageModel(latestHomeRecord, businessConfig, servicesList, trustList); var cacheOptions = new MemoryCacheEntryOptions() .SetAbsoluteExpiration(TimeSpan.FromHours(24)) diff --git a/Components/Pages/Home.razor b/Components/Pages/Home.razor index 540ef72..aedca44 100644 --- a/Components/Pages/Home.razor +++ b/Components/Pages/Home.razor @@ -21,7 +21,7 @@ else

@Model.HeaderLine1
@Model.HeaderLine2

@Model.HeaderText

diff --git a/Components/Pages/admin/EditPages.razor b/Components/Pages/admin/EditPages.razor index fb8802a..08efd5a 100644 --- a/Components/Pages/admin/EditPages.razor +++ b/Components/Pages/admin/EditPages.razor @@ -21,16 +21,22 @@
-
+
+ +
- @if (currentTab == AdminTab.Home) + @if (CurrentTab == AdminTab.Home) { - +
@@ -100,9 +106,35 @@
} - else + else if (CurrentTab == AdminTab.BusinessInfo) { -

Another page

+ + + +
+

🏠 Business Info

+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+ + +
}
diff --git a/Components/Pages/admin/EditPages.razor.cs b/Components/Pages/admin/EditPages.razor.cs index 2ca5fe9..ad848a2 100644 --- a/Components/Pages/admin/EditPages.razor.cs +++ b/Components/Pages/admin/EditPages.razor.cs @@ -1,39 +1,63 @@ namespace ApplianceRepair.Components.Pages.admin { - public partial class EditPages(HomePageReader homePageReader, ContentCardReader contentCardReader) + public partial class EditPages(HomePageReader homePageReader, ContentCardReader contentCardReader, BusinessConfigReader businessConfigReader) { public HomePageModel? HomePageModel; + public BusinessInfoModel? BusinessInfo; - private enum AdminTab { Home, About } - private AdminTab currentTab = AdminTab.Home; + private enum AdminTab { Home, About, BusinessInfo } + private AdminTab CurrentTab = AdminTab.Home; - override - protected async void OnInitialized() + protected override async Task OnInitializedAsync() { - HomePageModel = await homePageReader.ReadLatestRecordWithModel(contentCardReader) ?? Defaults.DefaultHomePageContent; + var businessConfig = await businessConfigReader.ReadLatestRecord() ?? Defaults.DefaultBusinessConfig; + var latestHomeRecord = await homePageReader.ReadLatestRecord() ?? Defaults.DefaultHomePageContent; + var servicesList = await contentCardReader.ReadAllByPageAndGroup(HomePageModel.PageName, nameof(HomePageModel.ContentCardTypes.Services)) ?? []; + var trustList = await contentCardReader.ReadAllByPageAndGroup(HomePageModel.PageName, nameof(HomePageModel.ContentCardTypes.Trust)) ?? []; + + BusinessInfo = new BusinessInfoModel(businessConfig); + HomePageModel = new HomePageModel(latestHomeRecord, businessConfig, servicesList, trustList); } private async void RevertHomePageModel() { - HomePageModel = await homePageReader.ReadLatestRecordWithModel(contentCardReader) ?? Defaults.DefaultHomePageContent; + var businessConfig = await businessConfigReader.ReadLatestRecord() ?? Defaults.DefaultBusinessConfig; + var latestHomeRecord = await homePageReader.ReadLatestRecord() ?? Defaults.DefaultHomePageContent; + var servicesList = await contentCardReader.ReadAllByPageAndGroup(HomePageModel.PageName, nameof(HomePageModel.ContentCardTypes.Services)) ?? []; + var trustList = await contentCardReader.ReadAllByPageAndGroup(HomePageModel.PageName, nameof(HomePageModel.ContentCardTypes.Trust)) ?? []; + + BusinessInfo = new BusinessInfoModel(businessConfig); + HomePageModel = new HomePageModel(latestHomeRecord, businessConfig, servicesList, trustList); + } + + private async void RevertBusinessInfo() + { + var businessConfig = await businessConfigReader.ReadLatestRecord() ?? Defaults.DefaultBusinessConfig; + BusinessInfo = new BusinessInfoModel(businessConfig); } private async void SaveHomePageModel() { - HomePageModel.CreatedAt = DateTime.Now; HomePageModel.UpdatedAt = DateTime.Now; + await homePageReader.UpdateRecord(HomePageModel); foreach (var card in HomePageModel.ServicesCards) { + card.UpdatedAt = DateTime.Now; await contentCardReader.UpdateRecord(card); } foreach (var card in HomePageModel.TrustCards) { + card.UpdatedAt = DateTime.Now; await contentCardReader.UpdateRecord(card); } + } - await homePageReader.AddRecord(HomePageModel); + private async void SaveBusinessInfo() + { + BusinessInfo.UpdatedAt = DateTime.Now; + await businessConfigReader.UpdateRecord(BusinessInfo); } private void AddServiceCard() @@ -42,7 +66,7 @@ CreatedAt = DateTime.Now, UpdatedAt = DateTime.Now, BelongsToPage = HomePageModel.PageName, - Group = HomePageModel.ContentCardTypes.Service.ToString(), + Group = HomePageModel.ContentCardTypes.Services.ToString(), Header = "Service Name", Text = "Short Description" }); diff --git a/Data.cs b/Data.cs index 2f950b8..49d6fc8 100644 --- a/Data.cs +++ b/Data.cs @@ -1,4 +1,6 @@ -namespace ApplianceRepair +using System.ComponentModel.DataAnnotations; + +namespace ApplianceRepair { public class HomePageRecord { @@ -11,15 +13,10 @@ public string? HeaderText { get; set; } - public string? HeaderButton1Text { get; set; } - public string? HeaderButton2Text { get; set; } - - public string? HeaderButton1Link { get; set; } - public string? HeaderButton2Link { get; set; } + public string? CallHeaderText { get; set; } + public string? BookHeaderText { get; set; } public string? SecondaryHeaderText { get; set; } - - public string? CopyrightText { get; set; } } public class ContentCardRecord @@ -47,10 +44,36 @@ public class RepairRequestRecord { + public int Id { get; set; } + public DateTime CreatedAt { get; set; } + public DateTime UpdatedAt { get; set; } + + public string? RequestNumber { get; set; } + + [Required(ErrorMessage = "Appliance Type is required.")] public string? Type { get; set; } + + [Required(ErrorMessage = "Appliance brand is required.")] public string? Brand { get; set; } + + [Required(ErrorMessage = "Description is required.")] public string? Notes { get; set; } + + [Required(ErrorMessage = "Full Name is required.")] public string? Name { get; set; } + + [Required(ErrorMessage = "Phone number is required.")] + [Phone(ErrorMessage = "Please enter a valid phone number.")] public string? Phone { get; set; } } + + public class RepairRequestMediaRecord + { + public int Id { get; set; } + public DateTime CreatedAt { get; set; } + public DateTime UpdatedAt { get; set; } + + public string? RequestNumber { get; set; } + public string? MediaPath { get; set; } + } } diff --git a/DatabaseContext.cs b/DatabaseContext.cs index d4be826..106bcef 100644 --- a/DatabaseContext.cs +++ b/DatabaseContext.cs @@ -8,5 +8,47 @@ namespace ApplianceRepair public DbSet ContentCards { get; set; } public DbSet BusinessConfig { get; set; } public DbSet RepairRequests { get; set; } + public DbSet RepairRequestMedia { get; set; } + + // Seed the data + public static async Task Initialize(DatabaseContext context) + { + if (!context.BusinessConfig.Any()) + { + var config = Defaults.DefaultBusinessConfig; + config.CreatedAt = DateTime.Now; + config.UpdatedAt = DateTime.Now; + await context.BusinessConfig.AddAsync(config); + await context.SaveChangesAsync(); + } + + if (!context.HomePage.Any()) + { + var home = Defaults.DefaultHomePageContent; + home.CreatedAt = DateTime.Now; + home.UpdatedAt = DateTime.Now; + await context.HomePage.AddAsync(home); + await context.SaveChangesAsync(); + + if (!context.ContentCards.Any()) + { + foreach (var card in Defaults.DefaultHomePageServiceCards) + { + card.CreatedAt = DateTime.Now; + card.UpdatedAt = DateTime.Now; + await context.ContentCards.AddAsync(card); + } + + foreach (var card in Defaults.DefaultHomePageTrustCards) + { + card.CreatedAt = DateTime.Now; + card.UpdatedAt = DateTime.Now; + await context.ContentCards.AddAsync(card); + } + + await context.SaveChangesAsync(); + } + } + } } } diff --git a/Defaults.cs b/Defaults.cs index f352f13..346ddf7 100644 --- a/Defaults.cs +++ b/Defaults.cs @@ -2,39 +2,36 @@ { public static class Defaults { - public static readonly HomePageModel DefaultHomePageContent = new() + public static readonly HomePageRecord DefaultHomePageContent = new() { - BusinessName = "Appliance Pro", - FormattedPhoneNumber = "(555) 012-3456", - PhoneNumberCallLink = "tel:5550123456", - HeaderLine1 = "Expert Appliance Repair,", HeaderLine2 = "Done Right Today.", HeaderText = "Fast, affordable repairs for all major brands. Serving the Greater Metro Area.", - HeaderButton1Text = "Call for Same-Day Service", - HeaderButton1Link = "tel:5550123456", - HeaderButton2Text = "Book Online", - HeaderButton2Link = "#booking", + CallHeaderText = "Call for Same-Day Service", + BookHeaderText = "Book Online", SecondaryHeaderText = "What We Fix", + }; - ServicesCards = - [ - new() { Header = "Refrigerators", Text = "Cooling issues, leaks, and compressor repairs." }, - new() { Header = "Washers & Dryers", Text = "Fixing drum issues, drainage, and heating elements." }, - new() { Header = "Ovens & Ranges", Text = "Electrical igniters, gas flow, and temperature control." } - ], + public static readonly List DefaultHomePageServiceCards = [ + new() { Header = "Refrigerators", Text = "Cooling issues, leaks, and compressor repairs.", BelongsToPage = "Home", Group = "Services" }, + new() { Header = "Washers & Dryers", Text = "Fixing drum issues, drainage, and heating elements.", BelongsToPage = "Home", Group = "Services" }, + new() { Header = "Ovens & Ranges", Text = "Electrical igniters, gas flow, and temperature control.", BelongsToPage = "Home", Group = "Services" } + ]; - TrustCards = - [ - new() { Header = "90-Day Warranty", Text = "Quality parts and labor guaranteed." }, - new() { Header = "Certified Techs", Text = "Licensed, bonded, and background-checked." }, - new() { Header = "Fair Pricing", Text = "No hidden fees or diagnostic surprises." } - ], + public static readonly List DefaultHomePageTrustCards = [ + new() { Header = "90-Day Warranty", Text = "Quality parts and labor guaranteed.", BelongsToPage = "Home", Group = "Trust" }, + new() { Header = "Certified Techs", Text = "Licensed, bonded, and background-checked.", BelongsToPage = "Home", Group = "Trust" }, + new() { Header = "Fair Pricing", Text = "No hidden fees or diagnostic surprises.", BelongsToPage = "Home", Group = "Trust" } + ]; - CopyrightText = $"© {DateTime.Now.Year} Appliance Pro. All rights reserved." + public static readonly BusinessConfigRecord DefaultBusinessConfig = new() + { + Name = "Appliance Pro", + PhoneNumber = "5550123456", + SupportEmail = "appliance@pro.net" }; } } diff --git a/Migrations/20260424050555_InitialCreate.Designer.cs b/Migrations/20260424050555_InitialCreate.Designer.cs new file mode 100644 index 0000000..7baa601 --- /dev/null +++ b/Migrations/20260424050555_InitialCreate.Designer.cs @@ -0,0 +1,173 @@ +// +using System; +using ApplianceRepair; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace ApplianceRepair.Migrations +{ + [DbContext(typeof(DatabaseContext))] + [Migration("20260424050555_InitialCreate")] + partial class InitialCreate + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "9.0.12"); + + modelBuilder.Entity("ApplianceRepair.BusinessConfigRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("SupportEmail") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("BusinessConfig"); + }); + + modelBuilder.Entity("ApplianceRepair.ContentCardRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("BelongsToPage") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Group") + .HasColumnType("TEXT"); + + b.Property("Header") + .HasColumnType("TEXT"); + + b.Property("Text") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("ContentCards"); + }); + + modelBuilder.Entity("ApplianceRepair.HomePageRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("BookHeaderText") + .HasColumnType("TEXT"); + + b.Property("CallHeaderText") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("HeaderLine1") + .HasColumnType("TEXT"); + + b.Property("HeaderLine2") + .HasColumnType("TEXT"); + + b.Property("HeaderText") + .HasColumnType("TEXT"); + + b.Property("SecondaryHeaderText") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("HomePage"); + }); + + modelBuilder.Entity("ApplianceRepair.RepairRequestMediaRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("MediaPath") + .HasColumnType("TEXT"); + + b.Property("RequestNumber") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("RepairRequestMedia"); + }); + + modelBuilder.Entity("ApplianceRepair.RepairRequestRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Brand") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("Phone") + .HasColumnType("TEXT"); + + b.Property("RequestNumber") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("RepairRequests"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Migrations/20260424050555_InitialCreate.cs b/Migrations/20260424050555_InitialCreate.cs new file mode 100644 index 0000000..40d9bb7 --- /dev/null +++ b/Migrations/20260424050555_InitialCreate.cs @@ -0,0 +1,125 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace ApplianceRepair.Migrations +{ + /// + public partial class InitialCreate : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "BusinessConfig", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + CreatedAt = table.Column(type: "TEXT", nullable: false), + UpdatedAt = table.Column(type: "TEXT", nullable: false), + Name = table.Column(type: "TEXT", nullable: true), + PhoneNumber = table.Column(type: "TEXT", nullable: true), + SupportEmail = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_BusinessConfig", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "ContentCards", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + CreatedAt = table.Column(type: "TEXT", nullable: false), + UpdatedAt = table.Column(type: "TEXT", nullable: false), + BelongsToPage = table.Column(type: "TEXT", nullable: true), + Group = table.Column(type: "TEXT", nullable: true), + Header = table.Column(type: "TEXT", nullable: true), + Text = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_ContentCards", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "HomePage", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + CreatedAt = table.Column(type: "TEXT", nullable: false), + UpdatedAt = table.Column(type: "TEXT", nullable: false), + HeaderLine1 = table.Column(type: "TEXT", nullable: true), + HeaderLine2 = table.Column(type: "TEXT", nullable: true), + HeaderText = table.Column(type: "TEXT", nullable: true), + CallHeaderText = table.Column(type: "TEXT", nullable: true), + BookHeaderText = table.Column(type: "TEXT", nullable: true), + SecondaryHeaderText = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_HomePage", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "RepairRequestMedia", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + CreatedAt = table.Column(type: "TEXT", nullable: false), + UpdatedAt = table.Column(type: "TEXT", nullable: false), + RequestNumber = table.Column(type: "TEXT", nullable: true), + MediaPath = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_RepairRequestMedia", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "RepairRequests", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + CreatedAt = table.Column(type: "TEXT", nullable: false), + UpdatedAt = table.Column(type: "TEXT", nullable: false), + RequestNumber = table.Column(type: "TEXT", nullable: true), + Type = table.Column(type: "TEXT", nullable: true), + Brand = table.Column(type: "TEXT", nullable: true), + Notes = table.Column(type: "TEXT", nullable: true), + Name = table.Column(type: "TEXT", nullable: true), + Phone = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_RepairRequests", x => x.Id); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "BusinessConfig"); + + migrationBuilder.DropTable( + name: "ContentCards"); + + migrationBuilder.DropTable( + name: "HomePage"); + + migrationBuilder.DropTable( + name: "RepairRequestMedia"); + + migrationBuilder.DropTable( + name: "RepairRequests"); + } + } +} diff --git a/Migrations/DatabaseContextModelSnapshot.cs b/Migrations/DatabaseContextModelSnapshot.cs new file mode 100644 index 0000000..a642903 --- /dev/null +++ b/Migrations/DatabaseContextModelSnapshot.cs @@ -0,0 +1,170 @@ +// +using System; +using ApplianceRepair; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace ApplianceRepair.Migrations +{ + [DbContext(typeof(DatabaseContext))] + partial class DatabaseContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "9.0.12"); + + modelBuilder.Entity("ApplianceRepair.BusinessConfigRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("SupportEmail") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("BusinessConfig"); + }); + + modelBuilder.Entity("ApplianceRepair.ContentCardRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("BelongsToPage") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Group") + .HasColumnType("TEXT"); + + b.Property("Header") + .HasColumnType("TEXT"); + + b.Property("Text") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("ContentCards"); + }); + + modelBuilder.Entity("ApplianceRepair.HomePageRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("BookHeaderText") + .HasColumnType("TEXT"); + + b.Property("CallHeaderText") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("HeaderLine1") + .HasColumnType("TEXT"); + + b.Property("HeaderLine2") + .HasColumnType("TEXT"); + + b.Property("HeaderText") + .HasColumnType("TEXT"); + + b.Property("SecondaryHeaderText") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("HomePage"); + }); + + modelBuilder.Entity("ApplianceRepair.RepairRequestMediaRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("MediaPath") + .HasColumnType("TEXT"); + + b.Property("RequestNumber") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("RepairRequestMedia"); + }); + + modelBuilder.Entity("ApplianceRepair.RepairRequestRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Brand") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("Phone") + .HasColumnType("TEXT"); + + b.Property("RequestNumber") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("RepairRequests"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Models.cs b/Models.cs index 773f2e7..5faae83 100644 --- a/Models.cs +++ b/Models.cs @@ -22,17 +22,58 @@ public class HomePageModel : HomePageRecord { public static string PageName = "Home"; + public enum ContentCardTypes { - Service, + Services, Trust, } public string BusinessName { get; set; } - public string FormattedPhoneNumber { get; set; } - public string PhoneNumberCallLink { get; set; } + + public string PhoneNumber { get; set; } + + public string FormattedPhoneNumber + { + get + { + if (!string.IsNullOrEmpty(PhoneNumber)) + { + return $"({PhoneNumber[0..3]})-{PhoneNumber[3..6]}-{PhoneNumber[6..10]}"; + } + + return ""; + } + } + + public string PhoneNumberCallLink + { + get + { + if (!string.IsNullOrEmpty(PhoneNumber)) + { + return $"tel:{PhoneNumber}"; + } + + return ""; + } + } + + public string CopyrightText + { + get + { + if (!string.IsNullOrEmpty(BusinessName)) + { + return $"© {DateTime.Now.Year} {BusinessName}. All rights reserved."; + } + + return $"© {DateTime.Now.Year} All rights reserved."; + } + } public List ServicesCards { get; set; } + public List TrustCards { get; set; } public HomePageModel() @@ -40,44 +81,57 @@ HeaderLine1 = string.Empty; HeaderLine2 = string.Empty; HeaderText = string.Empty; - HeaderButton1Text = string.Empty; - HeaderButton1Link = string.Empty; - HeaderButton2Text = string.Empty; - HeaderButton2Link = string.Empty; + CallHeaderText = string.Empty; + BookHeaderText = string.Empty; SecondaryHeaderText = string.Empty; - CopyrightText = string.Empty; - BusinessName = "Appliance Pro"; - FormattedPhoneNumber = "(555) 555-5555"; - PhoneNumberCallLink = $"tel:{FormattedPhoneNumber}"; + BusinessName = string.Empty; + PhoneNumber = string.Empty; ServicesCards = []; TrustCards = []; } - public HomePageModel(HomePageRecord record) + public HomePageModel( + HomePageRecord homePageRecord, + BusinessConfigRecord businessConfigRecord, + List serviceCards, + List trustCards) { - HeaderLine1 = record.HeaderLine1; - HeaderLine2 = record.HeaderLine2; - HeaderText = record.HeaderText; - HeaderButton1Text = record.HeaderButton1Text; - HeaderButton1Link = record.HeaderButton1Link; - HeaderButton2Text = record.HeaderButton2Text; - HeaderButton2Link = record.HeaderButton2Link; - SecondaryHeaderText = record.SecondaryHeaderText; - CopyrightText = record.CopyrightText; + HeaderLine1 = homePageRecord.HeaderLine1; + HeaderLine2 = homePageRecord.HeaderLine2; + HeaderText = homePageRecord.HeaderText; + CallHeaderText = homePageRecord.CallHeaderText; + BookHeaderText = homePageRecord.BookHeaderText; + SecondaryHeaderText = homePageRecord.SecondaryHeaderText;; - BusinessName = "Appliance Pro"; - FormattedPhoneNumber = "(555) 555-5555"; - PhoneNumberCallLink = $"tel:{FormattedPhoneNumber}"; + BusinessName = businessConfigRecord.Name ?? ""; + PhoneNumber = businessConfigRecord.PhoneNumber ?? ""; ServicesCards = []; TrustCards = []; + + foreach (var card in serviceCards) + { + ServicesCards.Add(new ContentCardModel(card)); + } + + foreach (var card in trustCards) + { + TrustCards.Add(new ContentCardModel(card)); + } } } - public class RepairRequestModel : RepairRequestRecord - { + public class RepairRequestModel : RepairRequestRecord { } + public class BusinessInfoModel : BusinessConfigRecord + { + public BusinessInfoModel(BusinessConfigRecord record) + { + Name = record.Name; + PhoneNumber = record.PhoneNumber; + SupportEmail = record.SupportEmail; + } } } diff --git a/Program.cs b/Program.cs index 0206065..08ae6af 100644 --- a/Program.cs +++ b/Program.cs @@ -17,20 +17,26 @@ builder.Services.AddLogging(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); -var app = builder.Build(); +var app = builder.Build(); using (var scope = app.Services.CreateScope()) { var services = scope.ServiceProvider; try { - services.GetRequiredService().Database.EnsureCreated(); + var context = services.GetRequiredService(); + + await context.Database.MigrateAsync(); + + await DatabaseContext.Initialize(context); } catch (Exception ex) { var logger = services.GetRequiredService>(); - logger.LogError(ex, "An error occurred creating the DB."); + logger.LogError(ex, "An error occurred while migrating or seeding the database."); } } diff --git a/Services.cs b/Services.cs index c51fc85..eb7bf8f 100644 --- a/Services.cs +++ b/Services.cs @@ -10,37 +10,27 @@ namespace ApplianceRepair return records; } - public async Task ReadLatestRecordWithModel(ContentCardReader contentCardReader) - { - var record = await db.HomePage.OrderByDescending(page => page.Id).FirstOrDefaultAsync(); - if (record == null) - { - return null; - } - - var model = new HomePageModel(record); - var pageName = HomePageModel.PageName; - - var services = await contentCardReader.ReadAllByPageAndGroup(pageName, HomePageModel.ContentCardTypes.Service.ToString()) ?? []; - foreach (var card in services) - { - model.ServicesCards.Add(new ContentCardModel(card)); - } - - var trust = await contentCardReader.ReadAllByPageAndGroup(pageName, HomePageModel.ContentCardTypes.Trust.ToString()) ?? []; - foreach (var card in trust) - { - model.TrustCards.Add(new ContentCardModel(card)); - } - - return model; - } - public async Task AddRecord(HomePageRecord record) { + record.CreatedAt = DateTime.Now; + record.UpdatedAt = DateTime.Now; await db.AddAsync(record); await db.SaveChangesAsync(); } + + public async Task UpdateRecord(HomePageRecord record) + { + var found = db.HomePage.Where((page) => page.Id == record.Id).FirstOrDefault(); + if (found == null) + { + await AddRecord(record); + } + else + { + db.HomePage.Update(record); + } + await db.SaveChangesAsync(); + } } public class ContentCardReader(DatabaseContext db) @@ -58,6 +48,7 @@ namespace ApplianceRepair public async Task AddRecord(ContentCardRecord record) { await db.ContentCards.AddAsync(record); + await db.SaveChangesAsync(); } public async Task UpdateRecord(ContentCardRecord record) @@ -81,5 +72,72 @@ namespace ApplianceRepair { return await db.BusinessConfig.OrderByDescending(page => page.Id).FirstOrDefaultAsync(); } + + public async Task AddRecord(BusinessConfigRecord record) + { + await db.BusinessConfig.AddAsync(record); + await db.SaveChangesAsync(); + } + + public async Task UpdateRecord(BusinessConfigRecord record) + { + var found = db.BusinessConfig.Where((config) => config.Id == record.Id).FirstOrDefault(); + if (found == null) + { + await AddRecord(record); + } + else + { + db.BusinessConfig.Update(record); + } + await db.SaveChangesAsync(); + } + } + + public class RepairRequestReader(DatabaseContext db) + { + public async Task ReadByRequestNumber(string requestNumber) + { + return await db.RepairRequests.Where((record) => record.RequestNumber == requestNumber).FirstOrDefaultAsync(); + } + + public async Task> ReadAll() + { + return await db.RepairRequests.ToListAsync(); + } + + public async Task AddRecord(RepairRequestRecord record) + { + await db.RepairRequests.AddAsync(record); + await db.SaveChangesAsync(); + } + + public async Task UpdateRecord(RepairRequestRecord record) + { + var found = db.RepairRequests.Where((config) => config.Id == record.Id).FirstOrDefault(); + if (found == null) + { + await AddRecord(record); + } + else + { + db.RepairRequests.Update(record); + } + await db.SaveChangesAsync(); + } + } + + public class RepairRequestMediaReader(DatabaseContext db) + { + public async Task> ReadAllByRequestNumber(string requestNumber) + { + return await db.RepairRequestMedia.Where((record) => record.RequestNumber == requestNumber).ToListAsync(); + } + + public async Task AddRecord(RepairRequestMediaRecord record) + { + await db.RepairRequestMedia.AddAsync(record); + await db.SaveChangesAsync(); + } } } diff --git a/wwwroot/uploads/exvwoomm.qgm.jpg b/wwwroot/uploads/exvwoomm.qgm.jpg new file mode 100644 index 0000000..e69de29 diff --git a/wwwroot/uploads/uefue0ou.esy.png b/wwwroot/uploads/uefue0ou.esy.png new file mode 100644 index 0000000000000000000000000000000000000000..bfb0ff723c71e0a16822cf73b5ff02d74dfb0405 GIT binary patch literal 4909 zcmd^DX;f3$mX0M(s3;bKmO%ojU@?S2rZ5$wQV3G041!M>i6TWd-pl}etYll z+xM(pp}oAPoi5?OzaUnCh9oe_XGkThp>w@ zaCTC4NP__Y2~jvWBrPExg@vU#8hpit0ncBCp$3qzF1R>H1Fr-)G71LsNQ;d=fkwwe z%*{+~%?tn#Ok_07-`(>s2*7eQh{53!VNhslYN~0fr70SNgqqvg*+I=LpcWP;fQJb- z9fgCZnV_)x0D>yyD~5X%7J-RP#KodfkT00<2y`;e(ZE0z@)vaCKS86ge=z`X0R<+Y z=BAcV01E^F6d*1J zP%#-oJ_~e!F3uwe_ch!X{!YXuM1d^KEX-hL<}eF0h`Ali%nD|H=z5mw6bQ6E&CA{8 zL|T}TyXofXWWv+=aMEmIBP1l~+>xIj6@b23e-6Uf7cTgy`$dkWnwim7>y)s>;0;&&EYMuGjfCTc|zUDW`H~U;vj-1&!bsPFH#`xzeJ74BX``BBYKrGDUBsK*rx~ zw0tZx(xyby3o;HnEK4rtzVRE0l#nKzzcp`_MHh#4-e*1?8JN+@8OjiY57}Ylq)tLy zvR+o)i{b3sam;thOm=9$i{>8nq#vxrvf)6zlu^UL=7RDcmp!M-y3gud8A9fQUweTq zC};U@Q>%nS<2kLE*O4DP_21V-`$t(@eJH1Oc=f;+H7ngYk|6Rf6$MwiqkU&}VjV4l zz9jI-O#GPnyO-Tz{Peo3Z$ZL{f*IDzV5Mixqj+*m5>g@|t`yqE99JvIP^Sk7!Ssie zzy{h!yE}PSRWF`&6uq;Jk4sS$k-U(+Guw@s?OA28GsB2b`rzslsm%-;GcCq-q%0TS`wbbZh(;krFWTYedFy>7nr-A>6yw%Nx#_iKehAh>#=JCeG~TJ9 zAjO<=w>wOhr9RQFK3{k8heDJ2Ur3_WV1%{9yDF~)u>4^chEf-{Gby-9)Z6|DVfy-@ zO41YA^DOr^Ws2JOyw{B!)wi2Qi3a21f%wN(IdLmL(fjQu&ukPv_M+pfC(HcFSH_ma zqz2+0qcgUJHENN3Eq;RLg^9NRO-V>uN6e3{nNU(|G0O^Gf_1Wh(mZYd#gXcF_ zdv@u~4w0 ztC{pnSDms~YhK)O*>>{V&zt=}FBaCFEO=%KIV^MZR?Q&vvfN3Tb($i>Xe?>Wvw{$J!CgVTRd^ zKybf&?LnoC0c6GFe8|}O;yH4Evz1GQ;%4x0URAIxKaQV+`^@^TVe*-OM5bR)>(L*t z8z^2p-D|@w?Xab;t4!FWCxl=ARox!7-CX!5DWmGeRq0{5$11LK7p!TR{o6S+XpLF8{=m?;r$3gzRv?6hIYJXNCsY8MmjT(C2S^ z#)oQq9&?~cJ4w$Ly3MarysYDjKWsgbE}vXyiM`H1?l*09SBu#&=N3QUE7g52?*;IAS&8x+7=k~98AwZ54uB<;;=M?kYK_YgNHDaufV-x4wlvF4W(&R zlUvRjH!v-zDN0!bLEr>!oqb%-_`Z6Q1ph0?jiMlrDdOOIg$9L!iyTMG;RCz9M7J5*&-mbo8J1`C!bF0+(|uo zj9WK0I8jwiX4JN{>`nXg7bnU`qJx-eNsT(zDr(PAti)Cp=f`Q*IJ2cqtcjO-BG}X& zL`zBLLN_Pp!_zHEcWf74=}!Qtxfyd*eyU}0k7=uMAAoSj9O3C`+B_7IoA%+NRz&B{ zz$4l^&W3m4*g5@Z^^7fdsBL!tx7rrDKLge*YiQ}6F0wm0)v`^1AAo#_pWGQm+ zbZYIOm66~S^7quybmS~hMm@mKpe1J^85)y6G$ss@QkiWcAm=BtnQFoCxQEthjXkI* za0OVK{Ds`>yCWo9!_f8#BaNZ#PeOE7Ygl-R&Z{l+Q-$b0tF7p^dC$Sg?i&LBT_nLH zU14D)C$u0*l|`5z$)6S1Y{yCDJi;vE^zCqBx+1RfSZN7sv9^QFLYE@@iiS@rUi6>5 z*jf#vvzl-gS|=~n_gb@!eQiHn0QVmEC@YZ^0YuKf4?zzI7&ZJAF)L3ym1ACI`%a)4 zP>@`LpLr;$sm{TW30b$qS8X_nZa||s)dSQH@GV&OqBi{dk=wtZ#%|YAMqz5O>9Pmo z+6_on9}vq0+{IXz@3=Iif7nr*u-HG(v}DmcwZcR5ytn+Q(Ib<(+U?V^j6TXNurwHC zVpkQ?!R+U~d6-vYw;yNKvVDtTR;9(2^fvnJ)3w`bpv~aKr)wl1`=I<vrDXrDkQl}?&0oz+2k4!lnDVQbUI$)&fhd!2@3@z6I`SYxH&f6I zd_~?)Y`^gUy+E~)p3eLQ!|cboCe1;FdljK&vDUAJT9f%9sE4cU!9g#+I^83vKsTXn zzAG7Yz_%!fwJUc{zDp~hAc^?VJG@H7Ep$l#qQlKW!|V>;cAEP`&aolI5T?gOkiZag z*S9Fng#yb*awugudQV_M_9hB<%eMq>%G=u*^FJ=s$sIU8He8LD+Nq(ArmUTEAtju! z**6Q_T5u@VzlOFnBC(@dShgva*n-{s`IkC6**e5D6xT1Tyr_X4S4aC&U}oqhY(IhB zd%qC%-M0shJ{YuOXNYKm!8WB#fMDD_nDoZWp!3-Vog!A>anrjuoo`r(L*N&8u5Tms zx3GBYmxCLkJAw7=wCKg+$F~{UU2h9ilI{czL6)Kb_4BmCGwa7(;seyOZHCUAhh5ko zUCR*MmP6$J?UyVXj9bpzI`f*m4ST2^Kv0{xCll9#h@W}l;dC-}>aH!YPRxu^R+MYG z?J->b0_v`0FudcM6zGHZe0$>bQQ%lz