initial commit
This commit is contained in:
19
Components/App.razor
Normal file
19
Components/App.razor
Normal file
@@ -0,0 +1,19 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<base href="/" />
|
||||
<link rel="stylesheet" href="@Assets["app.css"]" />
|
||||
<link rel="stylesheet" href="@Assets["ApplianceRepair.styles.css"]" />
|
||||
<ImportMap />
|
||||
<HeadOutlet />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<Routes />
|
||||
<script src="_framework/blazor.web.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
9
Components/Layout/MainLayout.razor
Normal file
9
Components/Layout/MainLayout.razor
Normal file
@@ -0,0 +1,9 @@
|
||||
@inherits LayoutComponentBase
|
||||
|
||||
@Body
|
||||
|
||||
<div id="blazor-error-ui" data-nosnippet>
|
||||
An unhandled error has occurred.
|
||||
<a href="." class="reload">Reload</a>
|
||||
<span class="dismiss">🗙</span>
|
||||
</div>
|
||||
20
Components/Layout/MainLayout.razor.css
Normal file
20
Components/Layout/MainLayout.razor.css
Normal file
@@ -0,0 +1,20 @@
|
||||
#blazor-error-ui {
|
||||
color-scheme: light only;
|
||||
background: lightyellow;
|
||||
bottom: 0;
|
||||
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
|
||||
box-sizing: border-box;
|
||||
display: none;
|
||||
left: 0;
|
||||
padding: 0.6rem 1.25rem 0.7rem 1.25rem;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
#blazor-error-ui .dismiss {
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
right: 0.75rem;
|
||||
top: 0.5rem;
|
||||
}
|
||||
36
Components/Pages/Error.razor
Normal file
36
Components/Pages/Error.razor
Normal 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;
|
||||
}
|
||||
48
Components/Pages/Home.Razor.cs
Normal file
48
Components/Pages/Home.Razor.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
67
Components/Pages/Home.razor
Normal file
67
Components/Pages/Home.razor
Normal 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>
|
||||
}
|
||||
|
||||
148
Components/Pages/Home.razor.css
Normal file
148
Components/Pages/Home.razor.css
Normal 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;
|
||||
}
|
||||
}
|
||||
6
Components/Routes.razor
Normal file
6
Components/Routes.razor
Normal file
@@ -0,0 +1,6 @@
|
||||
<Router AppAssembly="typeof(Program).Assembly">
|
||||
<Found Context="routeData">
|
||||
<RouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)" />
|
||||
<FocusOnNavigate RouteData="routeData" Selector="h1" />
|
||||
</Found>
|
||||
</Router>
|
||||
10
Components/_Imports.razor
Normal file
10
Components/_Imports.razor
Normal file
@@ -0,0 +1,10 @@
|
||||
@using System.Net.Http
|
||||
@using System.Net.Http.Json
|
||||
@using Microsoft.AspNetCore.Components.Forms
|
||||
@using Microsoft.AspNetCore.Components.Routing
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
@using static Microsoft.AspNetCore.Components.Web.RenderMode
|
||||
@using Microsoft.AspNetCore.Components.Web.Virtualization
|
||||
@using Microsoft.JSInterop
|
||||
@using ApplianceRepair
|
||||
@using ApplianceRepair.Components
|
||||
Reference in New Issue
Block a user