6569d9c4ea
- 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>
238 lines
14 KiB
Plaintext
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 & 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");}
|
|
}
|