initial commit

This commit is contained in:
2026-02-03 16:55:37 -06:00
commit 5267701e32
30 changed files with 1103 additions and 0 deletions

View File

@@ -0,0 +1,36 @@
@page "/Error"
@using System.Diagnostics
<PageTitle>Error</PageTitle>
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
@if (ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>@RequestId</code>
</p>
}
<h3>Development Mode</h3>
<p>
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
</p>
<p>
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
It can result in displaying sensitive information from exceptions to end users.
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
and restarting the app.
</p>
@code{
[CascadingParameter]
private HttpContext? HttpContext { get; set; }
private string? RequestId { get; set; }
private bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
protected override void OnInitialized() =>
RequestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier;
}

View File

@@ -0,0 +1,48 @@
using Microsoft.Extensions.Caching.Memory;
namespace ApplianceRepair.Components.Pages
{
public partial class Home(IMemoryCache cache, HomePageReader homePageReader, ContentCardReader contentCardReader)
{
private HomePageModel? Model;
protected override async Task OnInitializedAsync()
{
if (!cache.TryGetValue(nameof(HomePageModel), out Model))
{
Model = Defaults.DefaultHomePageContent;
var homePageRecord = await homePageReader.ReadLatestRecord();
if (homePageRecord != null)
{
Model = new HomePageModel(homePageRecord);
var serviceCardRecords = await contentCardReader.ReadAllByPageAndGroup(nameof(Home), "Services");
if (serviceCardRecords != null)
{
foreach (var record in serviceCardRecords)
{
Model.ServicesCards!.Add(new ContentCardModel(record));
}
}
var trustCardRecords = await contentCardReader.ReadAllByPageAndGroup(nameof(Home), "Trust");
if (trustCardRecords != null)
{
foreach (var record in trustCardRecords)
{
Model.TrustCards!.Add(new ContentCardModel(record));
}
}
}
var cacheOptions = new MemoryCacheEntryOptions()
.SetAbsoluteExpiration(TimeSpan.FromHours(24))
.SetSlidingExpiration(TimeSpan.FromHours(2));
cache.Set(nameof(HomePageModel), Model, cacheOptions);
}
}
}
}

View File

@@ -0,0 +1,67 @@
@page "/"
@rendermode InteractiveServer
<PageTitle>Home</PageTitle>
@if (Model == null)
{
<p>Loading...</p>
}
else
{
<nav class="navbar">
<div class="container">
<div class="logo">@Model.BusinessName</div>
<a href="@Model.PhoneNumberCallLink" class="nav-phone">📞 @Model.FormattedPhoneNumber</a>
</div>
</nav>
<header class="hero">
<div class="container">
<h1>@Model.HeaderLine1 <br><span>@Model.HeaderLine2</span></h1>
<p>@Model.HeaderText</p>
<div class="cta-group">
<a href="@Model.HeaderButton1Link" class="btn btn-primary">@Model.HeaderButton1Text</a>
<a href="@Model.HeaderButton2Link" class="btn btn-secondary">@Model.HeaderButton2Text</a>
</div>
</div>
</header>
<section class="services">
<div class="container">
<h2 class="section-title">@Model.SecondaryHeaderText</h2>
<div class="service-grid">
@if (Model.ServicesCards != null)
{
foreach (var serviceCard in Model.ServicesCards)
{
<div class="service-card">
<h3>@serviceCard.Header</h3>
<p>@serviceCard.Text</p>
</div>
}
}
</div>
</div>
</section>
<section class="trust">
<div class="container">
@if (Model.TrustCards != null)
{
foreach (var trustCard in Model.TrustCards)
{
<div class="trust-item">
<strong>@trustCard.Header</strong>
<p>@trustCard.Text</p>
</div>
}
}
</div>
</section>
<footer>
<p>@Model.CopyrightText</p>
</footer>
}

View File

@@ -0,0 +1,148 @@
/* General Styles */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.container {
max-width: 1100px;
margin: 0 auto;
padding: 0 20px;
}
/* Navbar */
.navbar {
background: #fff;
padding: 1rem 0;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
position: sticky;
top: 0;
z-index: 1000;
}
.navbar .container {
display: flex;
justify-content: space-between;
align-items: center;
}
.logo {
font-size: 1.5rem;
font-weight: bold;
color: #0056b3;
}
.logo span {
color: #e63946;
}
.nav-phone {
text-decoration: none;
color: #0056b3;
font-weight: bold;
}
/* Hero Section */
.hero {
background: linear-gradient(rgba(0,0,0,0.6), rgba(0,0,0,0.6)), url('https://images.unsplash.com/photo-1581092918056-0c4c3acd3789?auto=format&fit=crop&w=1200&q=80');
background-size: cover;
background-position: center;
color: #fff;
padding: 100px 0;
text-align: center;
}
.hero h1 {
font-size: 3rem;
margin-bottom: 1rem;
}
.hero h1 span {
color: #4cc9f0;
}
/* Buttons */
.btn {
display: inline-block;
padding: 12px 25px;
text-decoration: none;
border-radius: 5px;
font-weight: bold;
margin: 10px;
transition: 0.3s;
}
.btn-primary {
background: #e63946;
color: #fff;
}
.btn-secondary {
background: #fff;
color: #333;
}
.btn:hover {
opacity: 0.9;
transform: translateY(-2px);
}
/* Services */
.services {
padding: 60px 0;
}
.section-title {
text-align: center;
margin-bottom: 40px;
font-size: 2rem;
}
.service-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
}
.service-card {
background: #fff;
padding: 30px;
border-radius: 8px;
text-align: center;
border-bottom: 4px solid #0056b3;
box-shadow: 0 4px 6px rgba(0,0,0,0.05);
}
/* Trust Section */
.trust {
background: #0056b3;
color: #fff;
padding: 40px 0;
text-align: center;
}
.trust .container {
display: flex;
justify-content: space-around;
flex-wrap: wrap;
}
.trust-item {
margin: 20px;
}
footer {
text-align: center;
padding: 20px;
font-size: 0.9rem;
color: #777;
}
/* Mobile Responsive */
@media (max-width: 768px) {
.hero h1 {
font-size: 2rem;
}
}