Files
PowderCoatingLogix/src/PowderCoating.Web/Views/PlatformSubscription/Edit.cshtml
T
spouliot 6569d9c4ea Add SMS gating, TCPA terms agreement, and compose-before-send modal
- Three-tier SMS gate: platform kill-switch → admin force-disable → plan AllowSms → company opt-in
- CompanySmsAgreement entity records admin acceptance of TCPA terms with IP, user agent, and terms version
- SMS terms of service modal on Company Settings with versioned re-agreement (AppConstants.SmsTermsVersion)
- Dev redirect: non-production SMS routed to Twilio:DevRedirectPhone to protect real customer numbers
- Removed redundant Ready for Pickup SMS (Job Completed covers it)
- Role-based compose modal on job completion: Admin/Manager reviews and edits before send; ShopFloor auto-sends
- Send SMS button on job details for ad-hoc messages (Admin/Manager only)
- SendJobSmsAsync auto-appends STOP opt-out language if missing
- Migrations: AddSmsGating, AddCompanySmsAgreement

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 22:29:39 -04:00

238 lines
14 KiB
Plaintext

@using PowderCoating.Application.DTOs.Subscription
@model UpdateSubscriptionPlanConfigDto
@{
ViewData["Title"] = $"Edit {ViewBag.PlanName} Plan";
ViewData["PageIcon"] = "bi-pencil-square";
}
<div class="d-flex justify-content-start mb-4">
<a asp-action="Index" class="btn btn-outline-secondary">
<i class="bi bi-arrow-left"></i>
</a>
</div>
<div class="row justify-content-center">
<div class="col-lg-8">
<div class="card border-0 shadow-sm">
<div class="card-body">
<form asp-action="Edit" method="post">
@Html.AntiForgeryToken()
<input type="hidden" asp-for="Id" />
<h5 class="mb-3 pb-2 border-bottom">Plan Details</h5>
<div class="mb-3">
<label asp-for="Description" class="form-label">Description</label>
<textarea asp-for="Description" class="form-control" rows="2"></textarea>
<span asp-validation-for="Description" class="text-danger"></span>
</div>
<div class="mb-3">
<div class="form-check">
<input asp-for="IsActive" class="form-check-input" type="checkbox" />
<label asp-for="IsActive" class="form-check-label">Available for new signups &amp; upgrades</label>
</div>
<div class="form-text text-muted">
Uncheck to retire this plan. It will be hidden from registration and upgrade options,
but companies already on this plan will continue to see it in their billing page and
their subscription will not be affected.
</div>
</div>
<h5 class="mb-3 pb-2 border-bottom mt-4">Usage Limits</h5>
<p class="text-muted small">Use <strong>-1</strong> for unlimited.</p>
<div class="row g-3 mb-4">
<div class="col-md-4">
<label asp-for="MaxUsers" class="form-label">Max Users</label>
<input asp-for="MaxUsers" class="form-control" type="number" min="-1" />
<span asp-validation-for="MaxUsers" class="text-danger"></span>
</div>
<div class="col-md-4">
<label asp-for="MaxActiveJobs" class="form-label">Max Active Jobs</label>
<input asp-for="MaxActiveJobs" class="form-control" type="number" min="-1" />
<span asp-validation-for="MaxActiveJobs" class="text-danger"></span>
</div>
<div class="col-md-4">
<label asp-for="MaxCustomers" class="form-label">Max Customers</label>
<input asp-for="MaxCustomers" class="form-control" type="number" min="-1" />
<span asp-validation-for="MaxCustomers" class="text-danger"></span>
</div>
<div class="col-md-4">
<label asp-for="MaxQuotes" class="form-label">Max Quotes Per Month</label>
<input asp-for="MaxQuotes" class="form-control" type="number" min="-1" />
<span asp-validation-for="MaxQuotes" class="text-danger"></span>
</div>
<div class="col-md-4">
<label asp-for="MaxCatalogItems" class="form-label">Max Catalog Items</label>
<input asp-for="MaxCatalogItems" class="form-control" type="number" min="-1" />
<span asp-validation-for="MaxCatalogItems" class="text-danger"></span>
</div>
<div class="col-md-4">
<label asp-for="MaxJobPhotos" class="form-label">Max Photos Per Job</label>
<input asp-for="MaxJobPhotos" class="form-control" type="number" min="-1" />
<span asp-validation-for="MaxJobPhotos" class="text-danger"></span>
</div>
<div class="col-md-4">
<label asp-for="MaxQuotePhotos" class="form-label">Max Photos Per Quote</label>
<input asp-for="MaxQuotePhotos" class="form-control" type="number" min="-1" />
<span asp-validation-for="MaxQuotePhotos" class="text-danger"></span>
<div class="form-text">-1 = unlimited. 0 = disabled for this plan.</div>
</div>
<div class="col-md-4">
<label asp-for="MaxAiPhotoQuotesPerMonth" class="form-label">AI Photo Quotes / Month</label>
<input asp-for="MaxAiPhotoQuotesPerMonth" class="form-control" type="number" min="-1" />
<span asp-validation-for="MaxAiPhotoQuotesPerMonth" class="text-danger"></span>
<div class="form-text">-1 = unlimited. 0 = disabled for this plan.</div>
</div>
</div>
<h5 class="mb-3 pb-2 border-bottom mt-4">Pricing</h5>
<div class="row g-3 mb-4">
<div class="col-md-6">
<label asp-for="MonthlyPrice" class="form-label">Monthly Price ($)</label>
<div class="input-group">
<span class="input-group-text">$</span>
<input asp-for="MonthlyPrice" class="form-control" type="number" min="0" step="0.01" />
</div>
<span asp-validation-for="MonthlyPrice" class="text-danger"></span>
</div>
<div class="col-md-6">
<label asp-for="AnnualPrice" class="form-label">Annual Price ($)</label>
<div class="input-group">
<span class="input-group-text">$</span>
<input asp-for="AnnualPrice" class="form-control" type="number" min="0" step="0.01" />
</div>
<span asp-validation-for="AnnualPrice" class="text-danger"></span>
</div>
</div>
<h5 class="mb-3 pb-2 border-bottom mt-4">Online Payments</h5>
<div class="mb-4">
<div class="form-check form-switch">
<input asp-for="AllowOnlinePayments" class="form-check-input" type="checkbox" role="switch" />
<label asp-for="AllowOnlinePayments" class="form-check-label fw-medium">Allow Online Payments</label>
</div>
<div class="form-text">
When enabled, companies on this plan can connect Stripe and accept online invoice payments via payment link.
</div>
</div>
<h5 class="mb-3 pb-2 border-bottom mt-4">Accounting</h5>
<div class="mb-4">
<div class="form-check form-switch">
<input asp-for="AllowAccounting" class="form-check-input" type="checkbox" role="switch" />
<label asp-for="AllowAccounting" class="form-check-label fw-medium">Allow Accounting Features</label>
</div>
<div class="form-text">
When enabled, companies on this plan can access Chart of Accounts, Bills, Expenses, and Accounting Export.
</div>
</div>
<h5 class="mb-3 pb-2 border-bottom mt-4">SMS Notifications</h5>
<div class="mb-4">
<div class="form-check form-switch">
<input asp-for="AllowSms" class="form-check-input" type="checkbox" role="switch" />
<label asp-for="AllowSms" class="form-check-label fw-medium">Allow SMS Notifications</label>
</div>
<div class="form-text">
When enabled, companies on this plan can send SMS job-status notifications to customers
(subject to the platform SMS kill-switch and the company's own opt-in setting).
</div>
</div>
<h5 class="mb-3 pb-2 border-bottom mt-4">AI Features</h5>
<div class="mb-3">
<div class="form-check form-switch">
<input asp-for="AllowAiPhotoQuotes" class="form-check-input" type="checkbox" role="switch" />
<label asp-for="AllowAiPhotoQuotes" class="form-check-label fw-medium">Allow AI Photo Quotes</label>
</div>
<div class="form-text">
When enabled, companies on this plan can use AI photo-based quote analysis (subject to the monthly quota above).
</div>
</div>
<div class="mb-3">
<div class="form-check form-switch">
<input asp-for="AllowAiInventoryAssist" class="form-check-input" type="checkbox" role="switch" />
<label asp-for="AllowAiInventoryAssist" class="form-check-label fw-medium">Allow AI Inventory Assist</label>
</div>
<div class="form-text">
When enabled, companies on this plan can use AI-powered product lookup when creating or editing inventory items.
</div>
</div>
<div class="mb-4">
<div class="form-check form-switch">
<input asp-for="AllowAiCatalogPriceCheck" class="form-check-input" type="checkbox" role="switch" />
<label asp-for="AllowAiCatalogPriceCheck" class="form-check-label fw-medium">Allow AI Catalog Price Check</label>
</div>
<div class="form-text">
When enabled, companies on this plan can run AI-powered catalog price analysis (once per quarter).
</div>
</div>
<h5 class="mb-3 pb-2 border-bottom mt-4">Stripe Integration</h5>
<div class="alert alert-info small mb-3" role="alert">
<i class="bi bi-info-circle me-2"></i>
<strong>Where to find price IDs:</strong>
In your <a href="https://dashboard.stripe.com/products" target="_blank" class="alert-link">Stripe Dashboard</a>,
open the product, then look in the <strong>Pricing</strong> section for the specific price.
The ID starts with <code>price_</code> — <em>not</em> the product ID which starts with <code>prod_</code>.
</div>
<div class="row g-3 mb-4">
<div class="col-md-6">
<label asp-for="StripePriceIdMonthly" class="form-label">Stripe Price ID (Monthly)</label>
<input asp-for="StripePriceIdMonthly" class="form-control font-monospace" placeholder="price_..." />
@{
var monthlyVal = Model.StripePriceIdMonthly;
if (!string.IsNullOrWhiteSpace(monthlyVal) && !monthlyVal.StartsWith("price_"))
{
<div class="text-danger small mt-1">
<i class="bi bi-exclamation-triangle me-1"></i>
This looks like a product ID (<code>@monthlyVal[..Math.Min(12, monthlyVal.Length)]...</code>). Price IDs must start with <code>price_</code>.
</div>
}
}
<span asp-validation-for="StripePriceIdMonthly" class="text-danger"></span>
</div>
<div class="col-md-6">
<label asp-for="StripePriceIdAnnual" class="form-label">Stripe Price ID (Annual)</label>
<input asp-for="StripePriceIdAnnual" class="form-control font-monospace" placeholder="price_..." />
@{
var annualVal = Model.StripePriceIdAnnual;
if (!string.IsNullOrWhiteSpace(annualVal) && !annualVal.StartsWith("price_"))
{
<div class="text-danger small mt-1">
<i class="bi bi-exclamation-triangle me-1"></i>
This looks like a product ID (<code>@annualVal[..Math.Min(12, annualVal.Length)]...</code>). Price IDs must start with <code>price_</code>.
</div>
}
}
<span asp-validation-for="StripePriceIdAnnual" class="text-danger"></span>
</div>
</div>
<div class="d-flex gap-2">
<button type="submit" class="btn btn-primary">
<i class="bi bi-check-lg me-1"></i>Save Changes
</button>
<a asp-action="Index" class="btn btn-outline-secondary">Cancel</a>
</div>
</form>
</div>
</div>
</div>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}