Initial commit

This commit is contained in:
2026-04-23 21:38:24 -04:00
commit 63e12a9636
1762 changed files with 1672620 additions and 0 deletions
@@ -0,0 +1,185 @@
@using PowderCoating.Application.DTOs.Subscription
@model IEnumerable<SubscriptionPlanConfigDto>
@{
ViewData["Title"] = "Subscription Plan Configuration";
ViewData["PageIcon"] = "bi-layers";
// Badge color by position in the sorted list (SortOrder already applied in controller)
var planList = Model.ToList();
string PlanBadge(int index) => index switch {
0 => "bg-secondary",
1 => "bg-primary",
2 => "bg-info",
_ => "bg-success"
};
}
@section Styles {
<style>
/* Dark mode: table-light section headers inside plan cards */
[data-bs-theme="dark"] .card .table .table-light td {
--bs-table-bg: var(--bs-tertiary-bg);
color: var(--bs-secondary-color);
}
/* Dark mode: card headers */
[data-bs-theme="dark"] .card .card-header {
background-color: var(--bs-tertiary-bg);
border-color: var(--bs-border-color);
}
</style>
}
<div class="mb-4"></div>
@if (TempData["Success"] != null)
{
<div class="alert alert-success alert-dismissible fade show" role="alert">
<i class="bi bi-check-circle me-2"></i>@TempData["Success"]
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
}
<div class="row g-4">
@for (int i = 0; i < planList.Count; i++)
{
var plan = planList[i];
<div class="col-xl-3 col-md-6">
<div class="card border-0 shadow-sm h-100">
<div class="card-header d-flex justify-content-between align-items-center">
<span class="badge @PlanBadge(i) fs-6">@plan.DisplayName</span>
@if (!plan.IsActive)
{
<span class="badge bg-danger">Inactive</span>
}
</div>
<div class="card-body">
@if (!string.IsNullOrEmpty(plan.Description))
{
<p class="text-muted small mb-3">@plan.Description</p>
}
<table class="table table-sm">
<tbody>
<tr class="table-light">
<td colspan="2" class="fw-semibold small text-uppercase text-muted py-1">Limits</td>
</tr>
<tr>
<td class="text-muted">Users</td>
<td class="fw-semibold">@(plan.MaxUsers == -1 ? "Unlimited" : plan.MaxUsers.ToString())</td>
</tr>
<tr>
<td class="text-muted">Active Jobs</td>
<td class="fw-semibold">@(plan.MaxActiveJobs == -1 ? "Unlimited" : plan.MaxActiveJobs.ToString())</td>
</tr>
<tr>
<td class="text-muted">Customers</td>
<td class="fw-semibold">@(plan.MaxCustomers == -1 ? "Unlimited" : plan.MaxCustomers.ToString())</td>
</tr>
<tr>
<td class="text-muted">Quotes / Month</td>
<td class="fw-semibold">@(plan.MaxQuotes == -1 ? "Unlimited" : plan.MaxQuotes.ToString())</td>
</tr>
<tr>
<td class="text-muted">Catalog Items</td>
<td class="fw-semibold">@(plan.MaxCatalogItems == -1 ? "Unlimited" : plan.MaxCatalogItems.ToString())</td>
</tr>
<tr>
<td class="text-muted">Photos / Job</td>
<td class="fw-semibold">@(plan.MaxJobPhotos == -1 ? "Unlimited" : plan.MaxJobPhotos == 0 ? "None" : plan.MaxJobPhotos.ToString())</td>
</tr>
<tr>
<td class="text-muted">Photos / Quote</td>
<td class="fw-semibold">@(plan.MaxQuotePhotos == -1 ? "Unlimited" : plan.MaxQuotePhotos == 0 ? "None" : plan.MaxQuotePhotos.ToString())</td>
</tr>
<tr>
<td class="text-muted">AI Photo Quotes / Month</td>
<td class="fw-semibold">@(plan.MaxAiPhotoQuotesPerMonth == -1 ? "Unlimited" : plan.MaxAiPhotoQuotesPerMonth == 0 ? "Disabled" : plan.MaxAiPhotoQuotesPerMonth.ToString())</td>
</tr>
<tr class="table-light">
<td colspan="2" class="fw-semibold small text-uppercase text-muted py-1">Pricing</td>
</tr>
<tr>
<td class="text-muted">Monthly</td>
<td class="fw-semibold">$@plan.MonthlyPrice.ToString("F2")</td>
</tr>
<tr>
<td class="text-muted">Annual</td>
<td class="fw-semibold">$@plan.AnnualPrice.ToString("F2")</td>
</tr>
<tr>
<td class="text-muted">Online Payments</td>
<td>
@if (plan.AllowOnlinePayments)
{
<span class="badge bg-success">Enabled</span>
}
else
{
<span class="badge bg-secondary">Disabled</span>
}
</td>
</tr>
<tr>
<td class="text-muted">Accounting</td>
<td>
@if (plan.AllowAccounting)
{
<span class="badge bg-success">Enabled</span>
}
else
{
<span class="badge bg-secondary">Disabled</span>
}
</td>
</tr>
<tr>
<td class="text-muted">AI Photo Quotes</td>
<td>
@if (plan.AllowAiPhotoQuotes)
{
<span class="badge bg-success">Enabled</span>
}
else
{
<span class="badge bg-secondary">Disabled</span>
}
</td>
</tr>
<tr>
<td class="text-muted">AI Inventory Assist</td>
<td>
@if (plan.AllowAiInventoryAssist)
{
<span class="badge bg-success">Enabled</span>
}
else
{
<span class="badge bg-secondary">Disabled</span>
}
</td>
</tr>
<tr class="table-light">
<td colspan="2" class="fw-semibold small text-uppercase text-muted py-1">Stripe</td>
</tr>
<tr>
<td class="text-muted">Monthly ID</td>
<td class="font-monospace small text-truncate" style="max-width: 120px;" title="@plan.StripePriceIdMonthly">
@(string.IsNullOrEmpty(plan.StripePriceIdMonthly) ? "—" : plan.StripePriceIdMonthly)
</td>
</tr>
<tr>
<td class="text-muted">Annual ID</td>
<td class="font-monospace small text-truncate" style="max-width: 120px;" title="@plan.StripePriceIdAnnual">
@(string.IsNullOrEmpty(plan.StripePriceIdAnnual) ? "—" : plan.StripePriceIdAnnual)
</td>
</tr>
</tbody>
</table>
</div>
<div class="card-footer bg-transparent">
<a asp-action="Edit" asp-route-id="@plan.Id" class="btn btn-outline-primary btn-sm w-100">
<i class="bi bi-pencil me-1"></i>Edit Configuration
</a>
</div>
</div>
</div>
}
</div>