Add project files.
3
CtrlAltAssist.slnx
Normal file
@ -0,0 +1,3 @@
|
||||
<Solution>
|
||||
<Project Path="CtrlAltAssist/CtrlAltAssist.csproj" />
|
||||
</Solution>
|
||||
27
CtrlAltAssist/Components/App.razor
Normal file
@ -0,0 +1,27 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<base href="/" />
|
||||
<ResourcePreloader />
|
||||
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;700&family=Open+Sans:wght@300;400;600&display=swap" rel="stylesheet">
|
||||
|
||||
<link rel="stylesheet" href="@Assets["app.css"]" />
|
||||
<link rel="stylesheet" href="@Assets["CtrlAltAssist.styles.css"]" />
|
||||
|
||||
|
||||
<ImportMap />
|
||||
<HeadOutlet />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<Routes />
|
||||
<ReconnectModal />
|
||||
<script src="@Assets["_framework/blazor.web.js"]"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
8
CtrlAltAssist/Components/Layout/Footer.razor
Normal file
@ -0,0 +1,8 @@
|
||||
<footer class="footer text-center">
|
||||
<div class="container">
|
||||
<p>© <span id="currentYear"></span> Ctrl Alt Assist. All rights reserved. | Professional Virtual Office Assistance</p>
|
||||
<p>
|
||||
<a href="#">Privacy Policy</a> | <a href="#">Terms of Service</a>
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
13
CtrlAltAssist/Components/Layout/MainLayout.razor
Normal file
@ -0,0 +1,13 @@
|
||||
@inherits LayoutComponentBase
|
||||
|
||||
<Navbar />
|
||||
|
||||
@Body
|
||||
|
||||
<Footer />
|
||||
|
||||
<div id="blazor-error-ui" data-nosnippet>
|
||||
An unhandled error has occurred.
|
||||
<a href="." class="reload">Reload</a>
|
||||
<span class="dismiss">🗙</span>
|
||||
</div>
|
||||
20
CtrlAltAssist/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;
|
||||
}
|
||||
33
CtrlAltAssist/Components/Layout/Navbar.razor
Normal file
@ -0,0 +1,33 @@
|
||||
<nav class="navbar navbar-expand-lg sticky-top">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="#">
|
||||
<img src="/images/logo_small.png" alt="Ctrl Alt Assist Logo">
|
||||
Ctrl Alt Assist
|
||||
</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" aria-current="page" href="#home">Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#about">About Us</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#services">Services</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#founder">Founder</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#contact">Contact</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link btn btn-pastel ms-lg-3" href="/login">Client Login</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
17
CtrlAltAssist/Components/Layout/Navbar.razor.css
Normal file
@ -0,0 +1,17 @@
|
||||
.navbar {
|
||||
background-color: var(--primary-pastel-blue);
|
||||
}
|
||||
|
||||
.navbar-brand img {
|
||||
height: 60px; /* Adjust as needed */
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.navbar-nav .nav-link {
|
||||
color: var(--text-light);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.navbar-nav .nav-link:hover {
|
||||
color: #fff;
|
||||
}
|
||||
31
CtrlAltAssist/Components/Layout/ReconnectModal.razor
Normal file
@ -0,0 +1,31 @@
|
||||
<script type="module" src="@Assets["Components/Layout/ReconnectModal.razor.js"]"></script>
|
||||
|
||||
<dialog id="components-reconnect-modal" data-nosnippet>
|
||||
<div class="components-reconnect-container">
|
||||
<div class="components-rejoining-animation" aria-hidden="true">
|
||||
<div></div>
|
||||
<div></div>
|
||||
</div>
|
||||
<p class="components-reconnect-first-attempt-visible">
|
||||
Rejoining the server...
|
||||
</p>
|
||||
<p class="components-reconnect-repeated-attempt-visible">
|
||||
Rejoin failed... trying again in <span id="components-seconds-to-next-attempt"></span> seconds.
|
||||
</p>
|
||||
<p class="components-reconnect-failed-visible">
|
||||
Failed to rejoin.<br />Please retry or reload the page.
|
||||
</p>
|
||||
<button id="components-reconnect-button" class="components-reconnect-failed-visible">
|
||||
Retry
|
||||
</button>
|
||||
<p class="components-pause-visible">
|
||||
The session has been paused by the server.
|
||||
</p>
|
||||
<button id="components-resume-button" class="components-pause-visible">
|
||||
Resume
|
||||
</button>
|
||||
<p class="components-resume-failed-visible">
|
||||
Failed to resume the session.<br />Please reload the page.
|
||||
</p>
|
||||
</div>
|
||||
</dialog>
|
||||
157
CtrlAltAssist/Components/Layout/ReconnectModal.razor.css
Normal file
@ -0,0 +1,157 @@
|
||||
.components-reconnect-first-attempt-visible,
|
||||
.components-reconnect-repeated-attempt-visible,
|
||||
.components-reconnect-failed-visible,
|
||||
.components-pause-visible,
|
||||
.components-resume-failed-visible,
|
||||
.components-rejoining-animation {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#components-reconnect-modal.components-reconnect-show .components-reconnect-first-attempt-visible,
|
||||
#components-reconnect-modal.components-reconnect-show .components-rejoining-animation,
|
||||
#components-reconnect-modal.components-reconnect-paused .components-pause-visible,
|
||||
#components-reconnect-modal.components-reconnect-resume-failed .components-resume-failed-visible,
|
||||
#components-reconnect-modal.components-reconnect-retrying,
|
||||
#components-reconnect-modal.components-reconnect-retrying .components-reconnect-repeated-attempt-visible,
|
||||
#components-reconnect-modal.components-reconnect-retrying .components-rejoining-animation,
|
||||
#components-reconnect-modal.components-reconnect-failed,
|
||||
#components-reconnect-modal.components-reconnect-failed .components-reconnect-failed-visible {
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
||||
#components-reconnect-modal {
|
||||
background-color: white;
|
||||
width: 20rem;
|
||||
margin: 20vh auto;
|
||||
padding: 2rem;
|
||||
border: 0;
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: 0 3px 6px 2px rgba(0, 0, 0, 0.3);
|
||||
opacity: 0;
|
||||
transition: display 0.5s allow-discrete, overlay 0.5s allow-discrete;
|
||||
animation: components-reconnect-modal-fadeOutOpacity 0.5s both;
|
||||
&[open]
|
||||
|
||||
{
|
||||
animation: components-reconnect-modal-slideUp 1.5s cubic-bezier(.05, .89, .25, 1.02) 0.3s, components-reconnect-modal-fadeInOpacity 0.5s ease-in-out 0.3s;
|
||||
animation-fill-mode: both;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#components-reconnect-modal::backdrop {
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
animation: components-reconnect-modal-fadeInOpacity 0.5s ease-in-out;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@keyframes components-reconnect-modal-slideUp {
|
||||
0% {
|
||||
transform: translateY(30px) scale(0.95);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes components-reconnect-modal-fadeInOpacity {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes components-reconnect-modal-fadeOutOpacity {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.components-reconnect-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
#components-reconnect-modal p {
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#components-reconnect-modal button {
|
||||
border: 0;
|
||||
background-color: #6b9ed2;
|
||||
color: white;
|
||||
padding: 4px 24px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
#components-reconnect-modal button:hover {
|
||||
background-color: #3b6ea2;
|
||||
}
|
||||
|
||||
#components-reconnect-modal button:active {
|
||||
background-color: #6b9ed2;
|
||||
}
|
||||
|
||||
.components-rejoining-animation {
|
||||
position: relative;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
}
|
||||
|
||||
.components-rejoining-animation div {
|
||||
position: absolute;
|
||||
border: 3px solid #0087ff;
|
||||
opacity: 1;
|
||||
border-radius: 50%;
|
||||
animation: components-rejoining-animation 1.5s cubic-bezier(0, 0.2, 0.8, 1) infinite;
|
||||
}
|
||||
|
||||
.components-rejoining-animation div:nth-child(2) {
|
||||
animation-delay: -0.5s;
|
||||
}
|
||||
|
||||
@keyframes components-rejoining-animation {
|
||||
0% {
|
||||
top: 40px;
|
||||
left: 40px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
4.9% {
|
||||
top: 40px;
|
||||
left: 40px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
5% {
|
||||
top: 40px;
|
||||
left: 40px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
63
CtrlAltAssist/Components/Layout/ReconnectModal.razor.js
Normal file
@ -0,0 +1,63 @@
|
||||
// Set up event handlers
|
||||
const reconnectModal = document.getElementById("components-reconnect-modal");
|
||||
reconnectModal.addEventListener("components-reconnect-state-changed", handleReconnectStateChanged);
|
||||
|
||||
const retryButton = document.getElementById("components-reconnect-button");
|
||||
retryButton.addEventListener("click", retry);
|
||||
|
||||
const resumeButton = document.getElementById("components-resume-button");
|
||||
resumeButton.addEventListener("click", resume);
|
||||
|
||||
function handleReconnectStateChanged(event) {
|
||||
if (event.detail.state === "show") {
|
||||
reconnectModal.showModal();
|
||||
} else if (event.detail.state === "hide") {
|
||||
reconnectModal.close();
|
||||
} else if (event.detail.state === "failed") {
|
||||
document.addEventListener("visibilitychange", retryWhenDocumentBecomesVisible);
|
||||
} else if (event.detail.state === "rejected") {
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
|
||||
async function retry() {
|
||||
document.removeEventListener("visibilitychange", retryWhenDocumentBecomesVisible);
|
||||
|
||||
try {
|
||||
// Reconnect will asynchronously return:
|
||||
// - true to mean success
|
||||
// - false to mean we reached the server, but it rejected the connection (e.g., unknown circuit ID)
|
||||
// - exception to mean we didn't reach the server (this can be sync or async)
|
||||
const successful = await Blazor.reconnect();
|
||||
if (!successful) {
|
||||
// We have been able to reach the server, but the circuit is no longer available.
|
||||
// We'll reload the page so the user can continue using the app as quickly as possible.
|
||||
const resumeSuccessful = await Blazor.resumeCircuit();
|
||||
if (!resumeSuccessful) {
|
||||
location.reload();
|
||||
} else {
|
||||
reconnectModal.close();
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
// We got an exception, server is currently unavailable
|
||||
document.addEventListener("visibilitychange", retryWhenDocumentBecomesVisible);
|
||||
}
|
||||
}
|
||||
|
||||
async function resume() {
|
||||
try {
|
||||
const successful = await Blazor.resumeCircuit();
|
||||
if (!successful) {
|
||||
location.reload();
|
||||
}
|
||||
} catch {
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
|
||||
async function retryWhenDocumentBecomesVisible() {
|
||||
if (document.visibilityState === "visible") {
|
||||
await retry();
|
||||
}
|
||||
}
|
||||
36
CtrlAltAssist/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;
|
||||
}
|
||||
116
CtrlAltAssist/Components/Pages/Home.razor
Normal file
@ -0,0 +1,116 @@
|
||||
@page "/"
|
||||
|
||||
<PageTitle>Ctrl Alt Assist - Home</PageTitle>
|
||||
|
||||
<!-- Hero Section -->
|
||||
<header id="home" class="hero-section">
|
||||
<div class="container">
|
||||
<h1>Ctrl Alt Assist, Reboot Your Business Efficiency</h1>
|
||||
<p>Professional Executive & Financial Virtual Assistance tailored for the modern entrepreneur. </p>
|
||||
<p>We streamline your back office, ensure SARS compliance, and organise your administration so you can focus on what you do best - growing your business.</p>
|
||||
<a href="#services" class="btn btn-lg btn-pastel">Discover Our Services</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- About Us Section -->
|
||||
<section id="about" class="section-padding">
|
||||
<div class="container">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-lg-6 mb-4 mb-lg-0">
|
||||
<img src="Images/about-us.png" class="img-fluid rounded shadow" alt="About Ctrl Alt Assist">
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<h2>Who We Are</h2>
|
||||
<p>Since 2004, Ctrl Alt Assist has been providing exceptional virtual office assistance, empowering businesses to thrive by handling the administrative burden. We understand that your time is valuable, and our goal is to streamline your operations, allowing you to focus on growth and core competencies.</p>
|
||||
<p>Our team is dedicated to offering personalized, efficient, and reliable support, tailored to the unique needs of each client. We pride ourselves on our proactive approach and commitment to delivering peace of mind.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Services Section -->
|
||||
<section id="services" class="section-padding bg-light-green">
|
||||
<div class="container">
|
||||
<h2 class="text-center mb-5 text-light">Our Comprehensive Services</h2>
|
||||
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-4 service-list">
|
||||
<div class="col">
|
||||
<div class="card h-100 bg-light-blue text-light">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title text-light">Financial & Accounting Support</h5>
|
||||
<ul>
|
||||
<li>Bookkeeping, Budgets, Management Accounts</li>
|
||||
<li>SAGE, SAGE ONE, SAGE Online Tools, SAGE Payroll</li>
|
||||
<li>Reseller, Training & Support for SAGE products</li>
|
||||
<li>Monthly SARS Returns</li>
|
||||
<li>Bank, Debtors & Creditors Reconciliations</li>
|
||||
<li>Sorting & Capturing Invoices & Receipts</li>
|
||||
<li>Sending Statements & Payment Reminders</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="card h-100 bg-light-purple text-light">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title text-light">Office & Administrative Management</h5>
|
||||
<ul>
|
||||
<li>Complete office management – full admin service</li>
|
||||
<li>Creation and organizing of Business Systems & Procedures</li>
|
||||
<li>Product & Business Manuals</li>
|
||||
<li>Accounting System Setup, Training & Support</li>
|
||||
<li>Company Registrations</li>
|
||||
<li>Creating & Maintaining databases</li>
|
||||
<li>Payroll, Staff Hiring Assistance and process streamlining</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="card h-100 bg-light-blue text-light">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title text-light">Digital & Project Support</h5>
|
||||
<ul>
|
||||
<li>Report Writing, PowerPoint Presentations</li>
|
||||
<li>Customer Relations Management</li>
|
||||
<li>Project Management</li>
|
||||
<li>Typing & Transcribing Services</li>
|
||||
<li>Research</li>
|
||||
<li>Event Planning & Coordination</li>
|
||||
<li>Basic Websites, Domain Name Registrations</li>
|
||||
<li>Website, Email Hosting & Setup</li>
|
||||
<li>Cloud Backup Servers & Offsite Servers</li>
|
||||
<li>Computer Support, Training & Maintenance</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center mt-5">
|
||||
<p class="lead text-light">We offer both package, hourly or group rates customised to your personal needs.</p>
|
||||
<a href="#contact" class="btn btn-lg btn-pastel">Get a Custom Quote</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Founder Section -->
|
||||
<section id="founder" class="section-padding text-center">
|
||||
<div class="container">
|
||||
<h2>Meet Our Founder</h2>
|
||||
<img src="Images/Olivia.png" alt="Olivia, Founder of Ctrl Alt Assist" class="founder-img">
|
||||
<h3>Olivia</h3>
|
||||
<p class="lead">Loving wife, involved mom, and devoted pet parent.</p>
|
||||
<p>Olivia brings over 20 years of robust experience in both the Corporate and Private sector, working with JSE listed companies and privately owned businesses across diverse industries from transport to the arts. Her extensive knowledge is backed by a Bcompt, Business Studies Diploma, various Computer & Accounting software diplomas, and a Mini-MBA with Action Coach Ignite.</p>
|
||||
<p>A firm believer in continuous learning and growth, Olivia is a motivated and organized professional. She provides all necessary software, hardware, and office requirements, ensuring a seamless and efficient service. Clients also benefit from a personal login to our website, allowing them to view timesheets, track hours remaining, and more.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Call to Action for Login -->
|
||||
<section id="login-cta" class="call-to-action">
|
||||
<div class="container">
|
||||
<h2>Already a Client?</h2>
|
||||
<p class="lead">Access your personalized portal to manage your services and track progress.</p>
|
||||
<a href="/login" class="btn btn-lg btn-pastel">Client Login</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<CtrlAltAssist.Components.UI.ContactUsForm />
|
||||
88
CtrlAltAssist/Components/Pages/Home.razor.css
Normal file
@ -0,0 +1,88 @@
|
||||
.section-padding {
|
||||
padding: 80px 0;
|
||||
}
|
||||
|
||||
|
||||
.hero-section {
|
||||
background: linear-gradient(rgba(0, 0, 0, 0.4), rgba(0, 0, 0, 0.4)), url('/images/hero.png');
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-attachment: fixed;
|
||||
color: var(--text-light);
|
||||
padding: 100px 0;
|
||||
text-align: center;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.hero-section h1 {
|
||||
color: var(--text-light);
|
||||
font-size: 3.5rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.hero-section p {
|
||||
font-size: 1.5rem;
|
||||
max-width: 800px;
|
||||
margin: 0 auto 30px;
|
||||
}
|
||||
|
||||
.testimonial-carousel .carousel-item {
|
||||
padding: 40px;
|
||||
background-color: #fff;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.05);
|
||||
margin: 20px auto;
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
.testimonial-carousel .carousel-control-prev-icon,
|
||||
.testimonial-carousel .carousel-control-next-icon {
|
||||
filter: invert(100%);
|
||||
}
|
||||
|
||||
|
||||
.founder-img {
|
||||
max-width: 250px;
|
||||
border-radius: 50%;
|
||||
border: 5px solid var(--secondary-pastel-green);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.service-list ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.service-list ul li {
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px dashed rgba(255,255,255,0.3);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.service-list ul li:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.service-list ul li::before {
|
||||
content: '?';
|
||||
color: var(--quaternary-pastel-pink);
|
||||
font-weight: bold;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.call-to-action {
|
||||
background-color: var(--quaternary-pastel-pink);
|
||||
color: var(--text-dark);
|
||||
padding: 60px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.call-to-action h2 {
|
||||
color: var(--text-dark);
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
43
CtrlAltAssist/Components/Pages/Login.razor
Normal file
@ -0,0 +1,43 @@
|
||||
@page "/login"
|
||||
|
||||
<div class="d-flex align-items-center py-4 bg-body-tertiary">
|
||||
<main class="form-signin w-100 m-auto">
|
||||
<form>
|
||||
<div class="text-center">
|
||||
<img class="mb-4" src="/Images/Logo_medium.png" alt="" width="72" height="57">
|
||||
</div>
|
||||
|
||||
<h1 class="h3 mb-3 fw-normal text-center">Please sign in</h1>
|
||||
<div class="form-floating mb-3">
|
||||
<input type="email" class="form-control" id="floatingInput" placeholder="name@example.com">
|
||||
<label for="floatingInput">Email address</label>
|
||||
<div data-lastpass-icon-root="" style="position: relative !important; height: 0px !important; width: 0px !important; display: initial !important; float: left !important;">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-floating">
|
||||
<input type="password" class="form-control" id="floatingPassword" placeholder="Password">
|
||||
<label for="floatingPassword">Password</label>
|
||||
<div data-lastpass-icon-root="" style="position: relative !important; height: 0px !important; width: 0px !important; display: initial !important; float: left !important;">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-check text-start my-3">
|
||||
<input class="form-check-input" type="checkbox" value="remember-me" id="checkDefault">
|
||||
<label class="form-check-label" for="checkDefault">
|
||||
Remember me
|
||||
</label>
|
||||
</div>
|
||||
<button class="btn btn-primary w-100 py-2" type="submit">Sign in</button>
|
||||
|
||||
<div class="mt-3 text-center">
|
||||
<span class="text-muted">or</span>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-outline-secondary w-100 py-2 mt-3 google-sign-in" type="button">
|
||||
<img src="/Images/google-logo.svg" alt="Google logo" width="18" height="18" class="me-2">
|
||||
Sign in with Google
|
||||
</button>
|
||||
|
||||
<p class="mt-5 mb-3 text-body-secondary">© 2017–2025</p>
|
||||
</form>
|
||||
</main>
|
||||
</div>
|
||||
4
CtrlAltAssist/Components/Pages/Login.razor.css
Normal file
@ -0,0 +1,4 @@
|
||||
.form-signin {
|
||||
max-width: 330px;
|
||||
padding: 1rem;
|
||||
}
|
||||
5
CtrlAltAssist/Components/Pages/NotFound.razor
Normal file
@ -0,0 +1,5 @@
|
||||
@page "/not-found"
|
||||
@layout MainLayout
|
||||
|
||||
<h3>Not Found</h3>
|
||||
<p>Sorry, the content you are looking for does not exist.</p>
|
||||
6
CtrlAltAssist/Components/Routes.razor
Normal file
@ -0,0 +1,6 @@
|
||||
<Router AppAssembly="typeof(Program).Assembly" NotFoundPage="typeof(Pages.NotFound)">
|
||||
<Found Context="routeData">
|
||||
<RouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)" />
|
||||
<FocusOnNavigate RouteData="routeData" Selector="h1" />
|
||||
</Found>
|
||||
</Router>
|
||||
44
CtrlAltAssist/Components/UI/ContactUsForm.razor
Normal file
@ -0,0 +1,44 @@
|
||||
@attribute [StreamRendering]
|
||||
|
||||
<section id="contact" class="section-padding bg-light-purple">
|
||||
<div class="container">
|
||||
<h2 class="text-center mb-5 text-light">Partner with us</h2>
|
||||
<div class="row">
|
||||
<div class="col-lg-8 mx-auto">
|
||||
<p class="text-center lead text-light">Partner with an Executive Financial Virtual PA. Request your introductory consultation today and take the first step toward a streamlined office.</p>
|
||||
<ul class="list-unstyled text-center text-light fs-5">
|
||||
<li class="mb-3"><i class="bi bi-envelope-fill me-2"></i> Email: <a href="mailto:assistance@ctrlaltassist.co.za" class="text-light">assistance@ctrlaltassist.co.za</a></li>
|
||||
<li class="mb-3"><i class="bi bi-phone-fill me-2"></i> Cellphone: <a href="tel:+27827547524" class="text-light">+27 82 754 7524</a></li>
|
||||
</ul>
|
||||
<!-- Simple Contact Form (Optional, can be expanded) -->
|
||||
<EditForm Enhance FormName="ContactUs" Model="@model" OnValidSubmit="SubmitContactFormAsync" class="mt-4">
|
||||
<div class="mb-3 form-floating">
|
||||
<InputText type="text" class="form-control" placeholder="" id="form_name_field" @bind-Value="@model.name" />
|
||||
<label for="form_name_field">Name</label>
|
||||
<ValidationMessage For="@(() => model.name)" class="text-danger" />
|
||||
</div>
|
||||
<div class="mb-3 form-floating">
|
||||
<InputText type="email" class="form-control" placeholder="" id="form_email_field" @bind-Value="@model.email" />
|
||||
<label for="form_email_field">Email</label>
|
||||
<ValidationMessage For="@(() => model.email)" class="text-danger" />
|
||||
</div>
|
||||
<div class="mb-3 form-floating">
|
||||
<InputTextArea class="form-control" style="height:200px;" placeholder="" id="form_mesage_field" @bind-Value="@model.message"></InputTextArea>
|
||||
<label for="form_message_field">Message</label>
|
||||
<ValidationMessage For="@(() => model.message)" class="text-danger" />
|
||||
</div>
|
||||
<div class="d-grid">
|
||||
<button type="submit" class="btn btn-pastel" disabled="@isSubmitting" >Send Message</button>
|
||||
</div>
|
||||
|
||||
@if(!String.IsNullOrEmpty(feedback))
|
||||
{
|
||||
<div class="mt-3 alert alert-dismissible alert-@feedbackclass fade show" role="alert">@feedback</div>
|
||||
}
|
||||
|
||||
<DataAnnotationsValidator />
|
||||
</EditForm>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
42
CtrlAltAssist/Components/UI/ContactUsForm.razor.cs
Normal file
@ -0,0 +1,42 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Forms;
|
||||
using Microsoft.AspNetCore.Identity.UI.Services;
|
||||
using System.Net;
|
||||
|
||||
namespace CtrlAltAssist.Components.UI;
|
||||
|
||||
public partial class ContactUsForm
|
||||
{
|
||||
[Inject] IEmailSender EmailSender { get; set; }
|
||||
|
||||
[SupplyParameterFromForm(FormName = "ContactUs")]
|
||||
public ContactUsFormModel model { get; set; } = new();
|
||||
|
||||
private bool isSubmitting = false;
|
||||
private string feedback = String.Empty;
|
||||
private string feedbackclass = String.Empty;
|
||||
|
||||
private async Task SubmitContactFormAsync()
|
||||
{
|
||||
isSubmitting = true;
|
||||
feedback = "Sending email.\r\nPlease wait...";
|
||||
feedbackclass = "secondary";
|
||||
StateHasChanged();
|
||||
|
||||
await EmailSender.SendEmailAsync("olivia@ctrlaltassist.co.za",
|
||||
"Ctrl Alt Assist website - Contact Us Form Submission",
|
||||
$"Name: {model.name}<br/>Email: {model.email}<br/>Message: {model.message}");
|
||||
|
||||
feedback = "Thank you for contacting us! We will get back to you shortly.";
|
||||
feedbackclass = "success";
|
||||
StateHasChanged();
|
||||
|
||||
await Task.Delay(5000);
|
||||
|
||||
isSubmitting = false;
|
||||
model = new();
|
||||
feedback = "";
|
||||
StateHasChanged();
|
||||
|
||||
}
|
||||
}
|
||||
20
CtrlAltAssist/Components/UI/ContactUsFormModel.cs
Normal file
@ -0,0 +1,20 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace CtrlAltAssist.Components.UI;
|
||||
|
||||
public class ContactUsFormModel
|
||||
{
|
||||
[Required(ErrorMessage = "Name is required.")]
|
||||
[StringLength(100, MinimumLength = 3, ErrorMessage = "Name must be between 3 and 100 characters.")]
|
||||
public string name { get; set; }
|
||||
|
||||
|
||||
[Required(ErrorMessage = "Email is required.")]
|
||||
[EmailAddress(ErrorMessage = "Invalid email address.")]
|
||||
public string email { get; set; } = string.Empty;
|
||||
|
||||
|
||||
[Required(ErrorMessage = "Message is required.")]
|
||||
[StringLength(250, MinimumLength = 3, ErrorMessage = "Message must be between 3 and 250 characters.")]
|
||||
public string message { get; set; } = string.Empty;
|
||||
}
|
||||
11
CtrlAltAssist/Components/_Imports.razor
Normal file
@ -0,0 +1,11 @@
|
||||
@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 CtrlAltAssist
|
||||
@using CtrlAltAssist.Components
|
||||
@using CtrlAltAssist.Components.Layout
|
||||
10
CtrlAltAssist/CtrlAltAssist.csproj
Normal file
@ -0,0 +1,10 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<BlazorDisableThrowNavigationException>true</BlazorDisableThrowNavigationException>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
46
CtrlAltAssist/Program.cs
Normal file
@ -0,0 +1,46 @@
|
||||
using CtrlAltAssist.Components;
|
||||
using CtrlAltAssist.Services;
|
||||
using Microsoft.AspNetCore.Components.Forms;
|
||||
using Microsoft.AspNetCore.Identity.UI.Services;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// Add services to the container.
|
||||
builder.Services.AddRazorComponents()
|
||||
.AddInteractiveServerComponents();
|
||||
|
||||
builder.Services.AddHttpClient();
|
||||
|
||||
/*
|
||||
builder.Services.AddScoped(sp =>
|
||||
{
|
||||
var httpClient = new HttpClient() { BaseAddress = new Uri(builder.Host.HostEnvironment.BaseAddress) };
|
||||
|
||||
var Antiforgery = sp.GetRequiredService<AntiforgeryStateProvider>();
|
||||
httpClient.DefaultRequestHeaders.Add("RequestVerificationToken", Antiforgery.GetAntiforgeryToken().Value);
|
||||
|
||||
return httpClient;
|
||||
});
|
||||
*/
|
||||
|
||||
builder.Services.AddSingleton<IEmailSender, EmailSender>();
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
// Configure the HTTP request pipeline.
|
||||
if (!app.Environment.IsDevelopment())
|
||||
{
|
||||
app.UseExceptionHandler("/Error", createScopeForErrors: true);
|
||||
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
|
||||
app.UseHsts();
|
||||
}
|
||||
app.UseStatusCodePagesWithReExecute("/not-found", createScopeForStatusCodePages: true);
|
||||
app.UseHttpsRedirection();
|
||||
|
||||
app.UseAntiforgery();
|
||||
|
||||
app.MapStaticAssets();
|
||||
app.MapRazorComponents<App>()
|
||||
.AddInteractiveServerRenderMode();
|
||||
|
||||
app.Run();
|
||||
23
CtrlAltAssist/Properties/launchSettings.json
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
"dotnetRunMessages": true,
|
||||
"applicationUrl": "http://ctrlaltassist.dev.localhost:5012"
|
||||
},
|
||||
"https": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
"dotnetRunMessages": true,
|
||||
"applicationUrl": "https://ctrlaltassist.dev.localhost:7077;http://ctrlaltassist.dev.localhost:5012"
|
||||
}
|
||||
},
|
||||
"$schema": "https://json.schemastore.org/launchsettings.json"
|
||||
}
|
||||
66
CtrlAltAssist/Services/EmailSender.cs
Normal file
@ -0,0 +1,66 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Mail;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Microsoft.AspNetCore.Identity.UI.Services;
|
||||
|
||||
|
||||
namespace CtrlAltAssist.Services;
|
||||
|
||||
public class EmailSender : IEmailSender
|
||||
{
|
||||
public async Task SendEmailAsync(string email, string subject, string htmlMessage)
|
||||
{
|
||||
await SendEmailAsync(email, subject, htmlMessage, "", null);
|
||||
}
|
||||
|
||||
public async Task SendEmailAsync(string email, string subject, string htmlMessage,
|
||||
string fileName, MemoryStream memoryStream)
|
||||
{
|
||||
SmtpClient smtp = new SmtpClient("mail85.mailasp.net", 587);
|
||||
smtp.UseDefaultCredentials = false;
|
||||
smtp.Credentials = new NetworkCredential("mark@spiderink.co.za", "Inkey$123");
|
||||
smtp.EnableSsl = true;
|
||||
smtp.Timeout = 10000;
|
||||
|
||||
MailAddress from = new MailAddress("mark@spiderink.co.za", "Ctr Alt Assist - Website", System.Text.Encoding.UTF8);
|
||||
|
||||
MailMessage message = new MailMessage();
|
||||
|
||||
message.From = from;
|
||||
|
||||
string[] toAddrs = email.Split(';');
|
||||
|
||||
foreach (string toAddr in toAddrs)
|
||||
{
|
||||
if (toAddr != "")
|
||||
message.To.Add(new MailAddress(toAddr));
|
||||
}
|
||||
|
||||
message.Subject = subject;
|
||||
message.SubjectEncoding = System.Text.Encoding.UTF8;
|
||||
|
||||
message.IsBodyHtml = true;
|
||||
message.Body = htmlMessage;
|
||||
message.BodyEncoding = System.Text.Encoding.UTF8;
|
||||
|
||||
if ((fileName != "") && (memoryStream != null))
|
||||
{
|
||||
memoryStream.Position = 0;
|
||||
message.Attachments.Add(new Attachment(memoryStream, fileName));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await smtp.SendMailAsync(message);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine("Failed to send email. " + e.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
8
CtrlAltAssist/appsettings.Development.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
9
CtrlAltAssist/appsettings.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
BIN
CtrlAltAssist/wwwroot/Images/Logo.png
Normal file
|
After Width: | Height: | Size: 508 KiB |
1
CtrlAltAssist/wwwroot/Images/Logo.svg
Normal file
|
After Width: | Height: | Size: 144 KiB |
BIN
CtrlAltAssist/wwwroot/Images/Logo_medium.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
CtrlAltAssist/wwwroot/Images/Logo_small.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
CtrlAltAssist/wwwroot/Images/Olivia.png
Normal file
|
After Width: | Height: | Size: 112 KiB |
BIN
CtrlAltAssist/wwwroot/Images/about-us.png
Normal file
|
After Width: | Height: | Size: 396 KiB |
3
CtrlAltAssist/wwwroot/Images/google-logo.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 488 512">
|
||||
<path fill="#4285F4" d="M488 261.8C488 403.3 391.1 504 248 504 110.8 504 0 393.2 0 256S110.8 8 248 8c66.8 0 123 24.5 166.3 64.9l-67.5 64.9C258.5 52.6 94.3 116.6 94.3 256c0 86.5 69.1 156.6 153.7 156.6 98.2 0 135-70.4 140.8-106.9H248v-85.3h236.1c2.3 12.7 3.9 24.9 3.9 41.4z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 346 B |
BIN
CtrlAltAssist/wwwroot/Images/hero.png
Normal file
|
After Width: | Height: | Size: 1.8 MiB |
122
CtrlAltAssist/wwwroot/app.css
Normal file
@ -0,0 +1,122 @@
|
||||
:root {
|
||||
--primary-pastel-blue: #AEC6CF; /* Light Blue */
|
||||
--secondary-pastel-green: #B2D8D8; /* Mint Green */
|
||||
--tertiary-pastel-purple: #D8BFD8; /* Thistle Purple */
|
||||
--quaternary-pastel-pink: #FFDAB9; /* Peach */
|
||||
--text-dark: #333;
|
||||
--text-light: #f8f9fa;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
color: var(--text-dark);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-family: 'Montserrat', sans-serif;
|
||||
color: var(--text-dark);
|
||||
}
|
||||
|
||||
.bg-light-blue {
|
||||
background-color: var(--primary-pastel-blue);
|
||||
color: var(--text-light);
|
||||
}
|
||||
|
||||
.bg-light-green {
|
||||
background-color: var(--secondary-pastel-green);
|
||||
color: var(--text-light);
|
||||
}
|
||||
|
||||
.bg-light-purple {
|
||||
background-color: var(--tertiary-pastel-purple);
|
||||
color: var(--text-light);
|
||||
}
|
||||
|
||||
.card {
|
||||
border: none;
|
||||
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
|
||||
transition: transform 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
background-color: var(--primary-pastel-blue);
|
||||
color: var(--text-light);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.footer {
|
||||
background-color: var(--primary-pastel-blue);
|
||||
color: var(--text-light);
|
||||
padding: 40px 0;
|
||||
}
|
||||
|
||||
.footer a {
|
||||
color: var(--text-light);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.footer a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.btn-pastel {
|
||||
background-color: var(--tertiary-pastel-purple);
|
||||
border-color: var(--tertiary-pastel-purple);
|
||||
color: var(--text-light);
|
||||
font-weight: bold;
|
||||
padding: 12px 30px;
|
||||
border-radius: 5px;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
.btn-pastel:hover {
|
||||
background-color: darken(var(--tertiary-pastel-purple), 10%);
|
||||
border-color: darken(var(--tertiary-pastel-purple), 10%);
|
||||
color: var(--text-light);
|
||||
}
|
||||
|
||||
|
||||
.form-floating > label {
|
||||
color: var(--bs-secondary-color);
|
||||
}
|
||||
|
||||
/*
|
||||
Original styles from Blazor template below
|
||||
*/
|
||||
|
||||
|
||||
h1:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.valid.modified:not([type=checkbox]) {
|
||||
outline: 1px solid #26b050;
|
||||
}
|
||||
|
||||
.invalid {
|
||||
outline: 1px solid #e50000;
|
||||
}
|
||||
|
||||
.validation-message {
|
||||
color: #e50000;
|
||||
}
|
||||
|
||||
.blazor-error-boundary {
|
||||
background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
|
||||
padding: 1rem 1rem 1rem 3.7rem;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.blazor-error-boundary::after {
|
||||
content: "An error has occurred."
|
||||
}
|
||||
|
||||
.darker-border-checkbox.form-check-input {
|
||||
border-color: #929292;
|
||||
}
|
||||
|
||||