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.
+
+
+
+
+
+
+
+
+
-
-
-
-
-
+}
\ 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