Backend: - Add HasQuota() permission factory for quota limits (resources, users, services, appointments, email templates, automated tasks) - Add HasFeaturePermission() factory for feature-based permissions (SMS, masked calling, custom domains, white label, plugins, webhooks, calendar sync, analytics) - Add has_feature() method to Tenant model for flexible permission checking - Add new tenant permission fields: can_create_plugins, can_use_webhooks, can_use_calendar_sync, can_export_data - Create Data Export API with CSV/JSON support for appointments, customers, resources, services - Create Analytics API with dashboard, appointments, revenue endpoints - Add calendar sync views and URL configuration Frontend: - Add usePlanFeatures hook for checking feature availability - Add UpgradePrompt components (inline, banner, overlay variants) - Add LockedSection wrapper and LockedButton for feature gating - Update settings pages with permission checks 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
424 lines
10 KiB
TypeScript
424 lines
10 KiB
TypeScript
// Domain models based on the Game Plan
|
|
|
|
// FIX: Added PageComponent types and updated Business interface for website editor feature.
|
|
export type PageComponentType = 'HEADING' | 'TEXT' | 'IMAGE' | 'BUTTON' | 'SERVICE' | 'COLUMNS';
|
|
|
|
export interface PageComponent {
|
|
id: string;
|
|
type: PageComponentType;
|
|
content?: {
|
|
text?: string;
|
|
level?: 1 | 2 | 3;
|
|
src?: string;
|
|
alt?: string;
|
|
buttonText?: string;
|
|
href?: string;
|
|
serviceId?: string;
|
|
};
|
|
children?: PageComponent[][];
|
|
}
|
|
|
|
export interface CustomDomain {
|
|
id: number;
|
|
domain: string;
|
|
is_verified: boolean;
|
|
ssl_provisioned: boolean;
|
|
is_primary: boolean;
|
|
verification_token: string;
|
|
dns_txt_record: string;
|
|
dns_txt_record_name: string;
|
|
created_at: string;
|
|
verified_at?: string;
|
|
}
|
|
|
|
export interface PlanPermissions {
|
|
sms_reminders: boolean;
|
|
webhooks: boolean;
|
|
api_access: boolean;
|
|
custom_domain: boolean;
|
|
white_label: boolean;
|
|
custom_oauth: boolean;
|
|
plugins: boolean;
|
|
export_data: boolean;
|
|
video_conferencing: boolean;
|
|
two_factor_auth: boolean;
|
|
masked_calling: boolean;
|
|
pos_system: boolean;
|
|
mobile_app: boolean;
|
|
}
|
|
|
|
export interface Business {
|
|
id: string;
|
|
name: string;
|
|
subdomain: string;
|
|
primaryColor: string;
|
|
secondaryColor: string;
|
|
logoUrl?: string;
|
|
emailLogoUrl?: string;
|
|
logoDisplayMode?: 'logo-only' | 'text-only' | 'logo-and-text'; // How to display branding
|
|
whitelabelEnabled: boolean;
|
|
plan?: 'Free' | 'Professional' | 'Business' | 'Enterprise';
|
|
status?: 'Active' | 'Suspended' | 'Trial';
|
|
joinedAt?: Date;
|
|
resourcesCanReschedule?: boolean;
|
|
paymentsEnabled: boolean;
|
|
requirePaymentMethodToBook: boolean;
|
|
cancellationWindowHours: number;
|
|
lateCancellationFeePercent: number;
|
|
initialSetupComplete?: boolean;
|
|
customDomain?: string;
|
|
customDomainVerified?: boolean;
|
|
stripeConnectAccountId?: string;
|
|
websitePages?: Record<string, { name: string; content: PageComponent[] }>;
|
|
customerDashboardContent?: PageComponent[];
|
|
trialStart?: string;
|
|
trialEnd?: string;
|
|
isTrialActive?: boolean;
|
|
isTrialExpired?: boolean;
|
|
daysLeftInTrial?: number;
|
|
resourceTypes?: ResourceTypeDefinition[]; // Custom resource types
|
|
// Platform-controlled permissions
|
|
canManageOAuthCredentials?: boolean;
|
|
// Plan permissions (what features are available based on subscription)
|
|
planPermissions?: PlanPermissions;
|
|
}
|
|
|
|
export type UserRole = 'superuser' | 'platform_manager' | 'platform_support' | 'owner' | 'manager' | 'staff' | 'resource' | 'customer';
|
|
|
|
export interface NotificationPreferences {
|
|
email: boolean;
|
|
sms: boolean;
|
|
in_app: boolean;
|
|
appointment_reminders: boolean;
|
|
marketing: boolean;
|
|
}
|
|
|
|
export interface User {
|
|
id: string | number;
|
|
username?: string;
|
|
name: string;
|
|
email: string;
|
|
role: UserRole;
|
|
avatarUrl?: string;
|
|
phone?: string;
|
|
email_verified?: boolean;
|
|
two_factor_enabled?: boolean;
|
|
timezone?: string;
|
|
locale?: string;
|
|
notification_preferences?: NotificationPreferences;
|
|
can_invite_staff?: boolean;
|
|
can_access_tickets?: boolean;
|
|
permissions?: Record<string, boolean>;
|
|
}
|
|
|
|
export type ResourceType = 'STAFF' | 'ROOM' | 'EQUIPMENT';
|
|
|
|
export type ResourceTypeCategory = 'STAFF' | 'OTHER';
|
|
|
|
export interface ResourceTypeDefinition {
|
|
id: string;
|
|
name: string; // User-facing name like "Stylist", "Massage Therapist", "Treatment Room"
|
|
description?: string; // Description of this resource type
|
|
category: ResourceTypeCategory; // STAFF (requires staff assignment) or OTHER
|
|
isDefault: boolean; // Cannot be deleted
|
|
iconName?: string; // Optional icon identifier
|
|
}
|
|
|
|
export interface Resource {
|
|
id: string;
|
|
name: string;
|
|
type: ResourceType; // Legacy field - will be deprecated
|
|
typeId?: string; // New field - references ResourceTypeDefinition
|
|
userId?: string;
|
|
maxConcurrentEvents: number;
|
|
savedLaneCount?: number; // Remembered lane count when multilane is disabled
|
|
}
|
|
|
|
export type AppointmentStatus = 'PENDING' | 'CONFIRMED' | 'COMPLETED' | 'CANCELLED' | 'NO_SHOW';
|
|
|
|
export interface Appointment {
|
|
id: string;
|
|
resourceId: string | null; // null if unassigned
|
|
customerId?: string; // optional for walk-in appointments
|
|
customerName: string;
|
|
serviceId: string;
|
|
startTime: Date; // For MVP, we will assume a specific date
|
|
durationMinutes: number;
|
|
status: AppointmentStatus;
|
|
notes?: string;
|
|
}
|
|
|
|
export interface Blocker {
|
|
id: string;
|
|
resourceId: string;
|
|
startTime: Date;
|
|
durationMinutes: number;
|
|
title: string;
|
|
}
|
|
|
|
export interface PaymentMethod {
|
|
id: string;
|
|
brand: 'Visa' | 'Mastercard' | 'Amex';
|
|
last4: string;
|
|
isDefault: boolean;
|
|
}
|
|
|
|
|
|
export interface Customer {
|
|
id: string;
|
|
name: string;
|
|
email: string;
|
|
phone: string;
|
|
city?: string;
|
|
state?: string;
|
|
zip?: string;
|
|
totalSpend: number;
|
|
lastVisit: Date | null;
|
|
status: 'Active' | 'Inactive' | 'Blocked';
|
|
avatarUrl?: string;
|
|
tags?: string[];
|
|
userId?: string;
|
|
paymentMethods: PaymentMethod[];
|
|
}
|
|
|
|
export interface Service {
|
|
id: string;
|
|
name: string;
|
|
durationMinutes: number;
|
|
price: number;
|
|
description: string;
|
|
displayOrder: number;
|
|
photos?: string[];
|
|
}
|
|
|
|
export interface Metric {
|
|
label: string;
|
|
value: string;
|
|
change: string;
|
|
trend: 'up' | 'down' | 'neutral';
|
|
}
|
|
|
|
// --- Platform Types ---
|
|
|
|
export type TicketType = 'PLATFORM' | 'CUSTOMER' | 'STAFF_REQUEST' | 'INTERNAL';
|
|
export type TicketStatus = 'OPEN' | 'IN_PROGRESS' | 'RESOLVED' | 'CLOSED' | 'AWAITING_RESPONSE';
|
|
export type TicketPriority = 'LOW' | 'MEDIUM' | 'HIGH' | 'URGENT';
|
|
export type TicketCategory =
|
|
| 'BILLING'
|
|
| 'TECHNICAL'
|
|
| 'FEATURE_REQUEST'
|
|
| 'ACCOUNT'
|
|
| 'APPOINTMENT'
|
|
| 'REFUND'
|
|
| 'COMPLAINT'
|
|
| 'GENERAL_INQUIRY'
|
|
| 'TIME_OFF'
|
|
| 'SCHEDULE_CHANGE'
|
|
| 'EQUIPMENT'
|
|
| 'OTHER';
|
|
|
|
export interface TicketComment {
|
|
id: string;
|
|
ticket: string; // Ticket ID
|
|
author: string; // User ID
|
|
authorEmail: string;
|
|
authorFullName: string;
|
|
commentText: string;
|
|
createdAt: string; // Date string
|
|
isInternal: boolean;
|
|
}
|
|
|
|
export interface TicketEmailAddressListItem {
|
|
id: number;
|
|
display_name: string;
|
|
email_address: string;
|
|
color: string;
|
|
is_active: boolean;
|
|
is_default: boolean;
|
|
}
|
|
|
|
export interface Ticket {
|
|
id: string;
|
|
tenant?: string; // Tenant ID, optional for platform tickets
|
|
creator: string; // User ID
|
|
creatorEmail: string;
|
|
creatorFullName: string;
|
|
assignee?: string; // User ID, optional
|
|
assigneeEmail?: string;
|
|
assigneeFullName?: string;
|
|
ticketType: TicketType;
|
|
status: TicketStatus;
|
|
priority: TicketPriority;
|
|
subject: string;
|
|
description: string;
|
|
category: TicketCategory;
|
|
relatedAppointmentId?: string; // Appointment ID, optional
|
|
dueAt?: string; // Date string
|
|
firstResponseAt?: string; // Date string
|
|
isOverdue?: boolean;
|
|
createdAt: string; // Date string
|
|
updatedAt: string; // Date string
|
|
resolvedAt?: string; // Date string
|
|
comments?: TicketComment[]; // Nested comments
|
|
// External sender info (for tickets from non-registered users via email)
|
|
externalEmail?: string;
|
|
externalName?: string;
|
|
// Source email address (which email address received/sent this ticket)
|
|
source_email_address?: TicketEmailAddressListItem;
|
|
}
|
|
|
|
export interface TicketTemplate {
|
|
id: string;
|
|
tenant?: string; // Tenant ID, optional for platform templates
|
|
name: string;
|
|
description: string;
|
|
ticketType: TicketType;
|
|
category: TicketCategory;
|
|
defaultPriority: TicketPriority;
|
|
subjectTemplate: string;
|
|
descriptionTemplate: string;
|
|
isActive: boolean;
|
|
createdAt: string; // Date string
|
|
}
|
|
|
|
export interface CannedResponse {
|
|
id: string;
|
|
tenant?: string; // Tenant ID, optional for platform canned responses
|
|
title: string;
|
|
content: string;
|
|
category?: TicketCategory;
|
|
isActive: boolean;
|
|
useCount: number;
|
|
createdBy?: string; // User ID
|
|
createdAt: string; // Date string
|
|
}
|
|
|
|
export interface PlatformMetric {
|
|
label: string;
|
|
value: string;
|
|
change: string;
|
|
trend: 'up' | 'down' | 'neutral';
|
|
color: 'blue' | 'green' | 'purple' | 'orange';
|
|
}
|
|
|
|
// --- OAuth Settings Types ---
|
|
|
|
export interface OAuthProvider {
|
|
id: string;
|
|
name: string;
|
|
icon: string;
|
|
description: string;
|
|
}
|
|
|
|
export interface BusinessOAuthSettings {
|
|
enabledProviders: string[];
|
|
allowRegistration: boolean;
|
|
autoLinkByEmail: boolean;
|
|
useCustomCredentials: boolean;
|
|
}
|
|
|
|
export interface BusinessOAuthSettingsResponse {
|
|
settings: BusinessOAuthSettings;
|
|
availableProviders: OAuthProvider[];
|
|
}
|
|
|
|
// --- OAuth Credentials Types ---
|
|
|
|
export interface OAuthProviderCredential {
|
|
client_id: string;
|
|
client_secret: string;
|
|
has_secret: boolean;
|
|
}
|
|
|
|
export interface BusinessOAuthCredentialsResponse {
|
|
credentials: Record<string, OAuthProviderCredential>;
|
|
useCustomCredentials: boolean;
|
|
}
|
|
|
|
// --- Plugin Types ---
|
|
|
|
export type PluginCategory = 'EMAIL' | 'REPORTS' | 'CUSTOMER' | 'BOOKING' | 'INTEGRATION' | 'AUTOMATION' | 'OTHER';
|
|
|
|
export interface PluginTemplate {
|
|
id: string;
|
|
name: string;
|
|
description: string;
|
|
category: PluginCategory;
|
|
version: string;
|
|
author: string;
|
|
logoUrl?: string;
|
|
pluginCode?: string;
|
|
rating: number;
|
|
ratingCount: number;
|
|
installCount: number;
|
|
isVerified: boolean;
|
|
isFeatured: boolean;
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
}
|
|
|
|
export interface PluginInstallation {
|
|
id: string;
|
|
template: string;
|
|
templateName: string;
|
|
templateDescription: string;
|
|
category: PluginCategory;
|
|
version: string;
|
|
authorName?: string;
|
|
logoUrl?: string;
|
|
templateVariables?: Record<string, any>;
|
|
configValues?: Record<string, any>;
|
|
isActive: boolean;
|
|
installedAt: string;
|
|
hasUpdate: boolean;
|
|
rating?: number;
|
|
review?: string;
|
|
scheduledTaskId?: string;
|
|
}
|
|
|
|
// --- Email Template Types ---
|
|
|
|
export type EmailTemplateScope = 'BUSINESS' | 'PLATFORM';
|
|
|
|
export type EmailTemplateCategory =
|
|
| 'APPOINTMENT'
|
|
| 'REMINDER'
|
|
| 'CONFIRMATION'
|
|
| 'MARKETING'
|
|
| 'NOTIFICATION'
|
|
| 'REPORT'
|
|
| 'OTHER';
|
|
|
|
export interface EmailTemplate {
|
|
id: string;
|
|
name: string;
|
|
description: string;
|
|
subject: string;
|
|
htmlContent: string;
|
|
textContent: string;
|
|
scope: EmailTemplateScope;
|
|
isDefault: boolean;
|
|
category: EmailTemplateCategory;
|
|
previewContext?: Record<string, any>;
|
|
createdBy?: number;
|
|
createdByName?: string;
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
}
|
|
|
|
export interface EmailTemplatePreview {
|
|
subject: string;
|
|
htmlContent: string;
|
|
textContent: string;
|
|
forceFooter: boolean;
|
|
}
|
|
|
|
export interface EmailTemplateVariable {
|
|
code: string;
|
|
description: string;
|
|
}
|
|
|
|
export interface EmailTemplateVariableGroup {
|
|
category: string;
|
|
items: EmailTemplateVariable[];
|
|
} |