242 lines
8.6 KiB
C#
242 lines
8.6 KiB
C#
using System.ComponentModel.DataAnnotations;
|
|
using PlotLine.Models;
|
|
|
|
namespace PlotLine.ViewModels;
|
|
|
|
public sealed class ManageAccountViewModel
|
|
{
|
|
public AppUser User { get; set; } = new();
|
|
public UserSubscriptionDetail? Subscription { get; set; }
|
|
public TrialVisibilityViewModel? Trial { get; set; }
|
|
public bool HasPaidBillingRisk { get; set; }
|
|
}
|
|
|
|
public sealed class ManageSubscriptionViewModel
|
|
{
|
|
public AppUser User { get; set; } = new();
|
|
public UserSubscriptionDetail? CurrentSubscription { get; set; }
|
|
public TrialVisibilityViewModel? Trial { get; set; }
|
|
public SubscriptionUsageSummary? Usage { get; set; }
|
|
public IReadOnlyList<SubscriptionLevel> AvailableLevels { get; set; } = [];
|
|
}
|
|
|
|
public sealed class TrialVisibilityViewModel
|
|
{
|
|
public const int TrialDurationDays = 30;
|
|
|
|
public string PlanName { get; init; } = string.Empty;
|
|
public DateTime StartDateUtc { get; init; }
|
|
public DateTime ExpiryDateUtc { get; init; }
|
|
public int DaysRemaining { get; init; }
|
|
public int DaysElapsed { get; init; }
|
|
public int ProgressPercentage { get; init; }
|
|
public bool IsExpired { get; init; }
|
|
public int? WarningThresholdDays { get; init; }
|
|
|
|
public string StatusLabel => IsExpired
|
|
? "Trial expired"
|
|
: DaysRemaining == 1 ? "1 day remaining" : $"{DaysRemaining} days remaining";
|
|
|
|
public string WarningCssClass => WarningThresholdDays switch
|
|
{
|
|
<= 1 => "trial-status--one-day",
|
|
<= 3 => "trial-status--three-days",
|
|
<= 7 => "trial-status--seven-days",
|
|
<= 14 => "trial-status--fourteen-days",
|
|
_ => "trial-status--steady"
|
|
};
|
|
|
|
public static TrialVisibilityViewModel? FromSubscription(UserSubscriptionDetail? subscription, DateTime utcNow)
|
|
{
|
|
if (subscription is null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var isTrialPlan = subscription.IsTrial
|
|
|| (!subscription.RequiresStripeSubscription && string.IsNullOrWhiteSpace(subscription.StripeSubscriptionId));
|
|
if (!isTrialPlan)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var start = subscription.StartDateUTC;
|
|
var expiry = subscription.TrialExpiryDateUTC ?? start.AddDays(TrialDurationDays);
|
|
var totalDays = Math.Max(1, (int)Math.Ceiling((expiry - start).TotalDays));
|
|
var rawRemaining = (int)Math.Ceiling((expiry - utcNow).TotalDays);
|
|
var daysRemaining = Math.Max(0, rawRemaining);
|
|
var daysElapsed = Math.Clamp(totalDays - daysRemaining, 0, totalDays);
|
|
var progress = Math.Clamp((int)Math.Round(daysElapsed * 100m / totalDays), 0, 100);
|
|
|
|
return new TrialVisibilityViewModel
|
|
{
|
|
PlanName = subscription.DisplayName,
|
|
StartDateUtc = start,
|
|
ExpiryDateUtc = expiry,
|
|
DaysRemaining = daysRemaining,
|
|
DaysElapsed = daysElapsed,
|
|
ProgressPercentage = progress,
|
|
IsExpired = utcNow >= expiry,
|
|
WarningThresholdDays = daysRemaining <= 1 ? 1
|
|
: daysRemaining <= 3 ? 3
|
|
: daysRemaining <= 7 ? 7
|
|
: daysRemaining <= 14 ? 14
|
|
: null
|
|
};
|
|
}
|
|
}
|
|
|
|
public sealed class PricingViewModel
|
|
{
|
|
public IReadOnlyList<SubscriptionLevel> Plans { get; set; } = [];
|
|
public int? CurrentSubscriptionLevelID { get; set; }
|
|
public bool IsSignedIn { get; set; }
|
|
}
|
|
|
|
public sealed class PublicPageViewModel
|
|
{
|
|
public string SupportEmail { get; set; } = "support@plotdirector.com";
|
|
public string LastUpdated { get; set; } = "June 7, 2026";
|
|
}
|
|
|
|
public sealed class RegisterViewModel
|
|
{
|
|
[Required(ErrorMessage = "Enter your email address.")]
|
|
[EmailAddress(ErrorMessage = "Enter a valid email address.")]
|
|
[StringLength(256, ErrorMessage = "Email must be 256 characters or fewer.")]
|
|
public string Email { get; set; } = string.Empty;
|
|
|
|
[Required(ErrorMessage = "Enter a display name.")]
|
|
[StringLength(200, ErrorMessage = "Display name must be 200 characters or fewer.")]
|
|
public string DisplayName { get; set; } = string.Empty;
|
|
|
|
[Required(ErrorMessage = "Enter a password.")]
|
|
[StringLength(200, MinimumLength = 12, ErrorMessage = "Password must be at least 12 characters.")]
|
|
[DataType(DataType.Password)]
|
|
public string Password { get; set; } = string.Empty;
|
|
|
|
[Required(ErrorMessage = "Confirm your password.")]
|
|
[DataType(DataType.Password)]
|
|
[Compare(nameof(Password), ErrorMessage = "Passwords do not match.")]
|
|
public string ConfirmPassword { get; set; } = string.Empty;
|
|
}
|
|
|
|
public sealed class LoginViewModel
|
|
{
|
|
[Required(ErrorMessage = "Enter your email address.")]
|
|
[EmailAddress(ErrorMessage = "Enter a valid email address.")]
|
|
[StringLength(256, ErrorMessage = "Email must be 256 characters or fewer.")]
|
|
public string Email { get; set; } = string.Empty;
|
|
|
|
[Required(ErrorMessage = "Enter your password.")]
|
|
[DataType(DataType.Password)]
|
|
public string Password { get; set; } = string.Empty;
|
|
|
|
public bool RememberMe { get; set; }
|
|
|
|
public string? ReturnUrl { get; set; }
|
|
|
|
public bool IsCompanionLogin { get; set; }
|
|
}
|
|
|
|
public sealed class ForgotPasswordViewModel
|
|
{
|
|
[Required(ErrorMessage = "Enter your email address.")]
|
|
[EmailAddress(ErrorMessage = "Enter a valid email address.")]
|
|
[StringLength(256, ErrorMessage = "Email must be 256 characters or fewer.")]
|
|
public string Email { get; set; } = string.Empty;
|
|
}
|
|
|
|
public sealed class ResendVerificationEmailViewModel
|
|
{
|
|
[Required(ErrorMessage = "Enter your email address.")]
|
|
[EmailAddress(ErrorMessage = "Enter a valid email address.")]
|
|
[StringLength(256, ErrorMessage = "Email must be 256 characters or fewer.")]
|
|
public string Email { get; set; } = string.Empty;
|
|
}
|
|
|
|
public sealed class ResetPasswordViewModel
|
|
{
|
|
[Required(ErrorMessage = "Password reset token is missing.")]
|
|
public Guid Token { get; set; }
|
|
|
|
[Required(ErrorMessage = "Enter a new password.")]
|
|
[StringLength(200, MinimumLength = 12, ErrorMessage = "Password must be at least 12 characters.")]
|
|
[DataType(DataType.Password)]
|
|
public string Password { get; set; } = string.Empty;
|
|
|
|
[Required(ErrorMessage = "Confirm your new password.")]
|
|
[DataType(DataType.Password)]
|
|
[Compare(nameof(Password), ErrorMessage = "Passwords do not match.")]
|
|
public string ConfirmPassword { get; set; } = string.Empty;
|
|
}
|
|
|
|
public sealed class AccountStatusViewModel
|
|
{
|
|
public bool Success { get; set; }
|
|
public string Title { get; set; } = string.Empty;
|
|
public string Message { get; set; } = string.Empty;
|
|
}
|
|
|
|
public sealed class TwoFactorCodeViewModel
|
|
{
|
|
[Required(ErrorMessage = "Enter the code from your authenticator app.")]
|
|
[StringLength(20, ErrorMessage = "Authentication code is too long.")]
|
|
public string Code { get; set; } = string.Empty;
|
|
|
|
public string? ReturnUrl { get; set; }
|
|
}
|
|
|
|
public sealed class TwoFactorRecoveryViewModel
|
|
{
|
|
[Required(ErrorMessage = "Enter a recovery code.")]
|
|
[StringLength(20, ErrorMessage = "Recovery code is too long.")]
|
|
public string RecoveryCode { get; set; } = string.Empty;
|
|
|
|
public string? ReturnUrl { get; set; }
|
|
}
|
|
|
|
public sealed class EnableTwoFactorViewModel
|
|
{
|
|
[Required(ErrorMessage = "Authenticator secret is missing.")]
|
|
public string SecretKey { get; set; } = string.Empty;
|
|
|
|
public string QrCodeUri { get; set; } = string.Empty;
|
|
|
|
public string? QrCodeImageDataUri { get; set; }
|
|
|
|
[Required(ErrorMessage = "Enter the code from your authenticator app.")]
|
|
[StringLength(20, ErrorMessage = "Authentication code is too long.")]
|
|
public string Code { get; set; } = string.Empty;
|
|
}
|
|
|
|
public sealed class DisableTwoFactorViewModel
|
|
{
|
|
[Required(ErrorMessage = "Enter your current password.")]
|
|
[DataType(DataType.Password)]
|
|
public string CurrentPassword { get; set; } = string.Empty;
|
|
}
|
|
|
|
public sealed class RecoveryCodesViewModel
|
|
{
|
|
public IReadOnlyList<string> RecoveryCodes { get; set; } = Array.Empty<string>();
|
|
public bool HasNewCodes => RecoveryCodes.Any();
|
|
}
|
|
|
|
public sealed class ChangePasswordViewModel
|
|
{
|
|
[Required(ErrorMessage = "Enter your current password.")]
|
|
[DataType(DataType.Password)]
|
|
public string CurrentPassword { get; set; } = string.Empty;
|
|
|
|
[Required(ErrorMessage = "Enter a new password.")]
|
|
[StringLength(200, MinimumLength = 12, ErrorMessage = "Password must be at least 12 characters.")]
|
|
[DataType(DataType.Password)]
|
|
public string NewPassword { get; set; } = string.Empty;
|
|
|
|
[Required(ErrorMessage = "Confirm your new password.")]
|
|
[DataType(DataType.Password)]
|
|
[Compare(nameof(NewPassword), ErrorMessage = "Passwords do not match.")]
|
|
public string ConfirmNewPassword { get; set; } = string.Empty;
|
|
}
|