// 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; can_create_plugins: boolean; tasks: boolean; export_data: boolean; video_conferencing: boolean; two_factor_auth: boolean; masked_calling: boolean; pos_system: boolean; mobile_app: boolean; contracts: 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 timezone?: string; // IANA timezone (e.g., 'America/New_York') timezoneDisplayMode?: 'business' | 'viewer'; // How times are displayed to users 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; bookingReturnUrl?: string; // URL to redirect customers after booking completion stripeConnectAccountId?: string; websitePages?: Record; customerDashboardContent?: PageComponent[]; // Booking page customization serviceSelectionHeading?: string; // Custom heading for service selection (default: "Choose your experience") serviceSelectionSubheading?: string; // Custom subheading (default: "Select a service to begin your booking.") 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 QuotaOverage { id: number; quota_type: string; display_name: string; current_usage: number; allowed_limit: number; overage_amount: number; days_remaining: number; grace_period_ends_at: string; } 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; can_send_messages?: boolean; can_edit_schedule?: boolean; linked_resource_id?: number; linked_resource_name?: string; permissions?: Record; quota_overages?: QuotaOverage[]; } 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 created_at?: string; // Used for quota overage calculation (oldest archived first) is_archived_by_quota?: boolean; // True if archived due to quota overage userCanEditSchedule?: boolean; // Allow linked user to edit their schedule regardless of role } // Backend uses: SCHEDULED, EN_ROUTE, IN_PROGRESS, CANCELED, COMPLETED, AWAITING_PAYMENT, PAID, NOSHOW // Frontend aliases: PENDING (for SCHEDULED), CONFIRMED (for SCHEDULED), CANCELLED (for CANCELED), NO_SHOW (for NOSHOW) export type AppointmentStatus = | 'SCHEDULED' | 'EN_ROUTE' | 'IN_PROGRESS' | 'CANCELED' | 'COMPLETED' | 'AWAITING_PAYMENT' | 'PAID' | 'NOSHOW' // Legacy aliases for frontend compatibility | 'PENDING' | 'CONFIRMED' | '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; duration?: number; // Duration in minutes (backend field name) price: number; price_cents?: number; // Price in cents description: string; displayOrder: number; display_order?: number; photos?: string[]; is_active?: boolean; created_at?: string; // Used for quota overage calculation (oldest archived first) updated_at?: string; is_archived_by_quota?: boolean; // True if archived due to quota overage // Pricing fields variable_pricing?: boolean; // If true, final price is determined after service completion deposit_amount?: number | null; // Fixed deposit amount in dollars deposit_amount_cents?: number | null; // Fixed deposit amount in cents deposit_percent?: number | null; // Deposit as percentage (only for fixed pricing) requires_deposit?: boolean; // True if deposit configured (computed) requires_saved_payment_method?: boolean; // True if deposit > 0 or variable pricing (computed) deposit_display?: string | null; // Human-readable deposit description // Resource assignment all_resources?: boolean; resource_ids?: string[]; resource_names?: string[]; // Buffer time (frontend-only for now) prep_time?: number; takedown_time?: number; // Notification settings (frontend-only for now) reminder_enabled?: boolean; reminder_hours_before?: number; reminder_email?: boolean; reminder_sms?: boolean; thank_you_email_enabled?: boolean; // Category (future feature) category?: string | null; } 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; 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; configValues?: Record; 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; 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[]; } // --- Contract Types --- export type ContractScope = 'CUSTOMER' | 'APPOINTMENT'; export type ContractStatus = 'PENDING' | 'SIGNED' | 'EXPIRED' | 'VOIDED'; export type ContractTemplateStatus = 'DRAFT' | 'ACTIVE' | 'ARCHIVED'; export interface ContractTemplate { id: string; name: string; description: string; content: string; scope: ContractScope; status: ContractTemplateStatus; expires_after_days: number | null; version: number; version_notes: string; services: { id: string; name: string }[]; created_by: string | null; created_by_name: string | null; created_at: string; updated_at: string; } export interface Contract { id: string; template: string; template_name: string; template_version: number; scope: ContractScope; status: ContractStatus; content: string; customer?: string; customer_name?: string; customer_email?: string; appointment?: string; appointment_service_name?: string; appointment_start_time?: string; service?: string; service_name?: string; sent_at: string | null; signed_at: string | null; expires_at: string | null; voided_at: string | null; voided_reason: string | null; public_token: string; created_at: string; updated_at: string; } export interface ContractSignature { id: string; contract: string; signer_name: string; signer_email: string; signature_data: string; ip_address: string; user_agent: string; signed_at: string; } export interface ContractPublicView { contract: Contract; template: { name: string; content: string; }; business: { name: string; logo_url?: string; }; customer?: { name: string; email: string; }; appointment?: { service_name: string; start_time: string; }; is_expired: boolean; can_sign: boolean; signature?: ContractSignature; } // --- Time Blocking Types --- export type BlockType = 'HARD' | 'SOFT'; export type BlockPurpose = 'CLOSURE' | 'UNAVAILABLE' | 'BUSINESS_HOURS' | 'OTHER'; export type RecurrenceType = 'NONE' | 'WEEKLY' | 'MONTHLY' | 'YEARLY' | 'HOLIDAY'; export type TimeBlockLevel = 'business' | 'resource'; export type HolidayType = 'FIXED' | 'FLOATING' | 'CALCULATED'; export interface Holiday { code: string; name: string; country: string; holiday_type?: HolidayType; month?: number; day?: number; week_of_month?: number; day_of_week?: number; calculation_rule?: string; is_active?: boolean; next_occurrence?: string; // ISO date string } export interface RecurrencePattern { days_of_week?: number[]; // 0=Mon, 6=Sun (for WEEKLY) days_of_month?: number[]; // 1-31 (for MONTHLY) month?: number; // 1-12 (for YEARLY) day?: number; // 1-31 (for YEARLY) holiday_code?: string; // holiday code (for HOLIDAY) } export interface TimeBlock { id: string; title: string; description?: string; resource?: string | null; // Resource ID or null for business-level resource_name?: string; level: TimeBlockLevel; block_type: BlockType; purpose: BlockPurpose; recurrence_type: RecurrenceType; start_date?: string; // ISO date string (for NONE type) end_date?: string; // ISO date string (for NONE type) all_day: boolean; start_time?: string; // HH:MM:SS (if not all_day) end_time?: string; // HH:MM:SS (if not all_day) recurrence_pattern?: RecurrencePattern; pattern_display?: string; // Human-readable pattern description holiday_name?: string; // Holiday name if HOLIDAY type recurrence_start?: string; // ISO date string recurrence_end?: string; // ISO date string is_active: boolean; created_by?: string; created_by_name?: string; conflict_count?: number; created_at: string; updated_at?: string; } export type ApprovalStatus = 'APPROVED' | 'PENDING' | 'DENIED'; export interface TimeBlockListItem { id: string; title: string; description?: string; resource?: string | null; resource_name?: string; level: TimeBlockLevel; block_type: BlockType; purpose: BlockPurpose; recurrence_type: RecurrenceType; start_date?: string; end_date?: string; all_day?: boolean; start_time?: string; end_time?: string; recurrence_pattern?: RecurrencePattern; recurrence_start?: string; recurrence_end?: string; pattern_display?: string; is_active: boolean; created_at: string; approval_status?: ApprovalStatus; reviewed_by?: number; reviewed_by_name?: string; reviewed_at?: string; review_notes?: string; created_by_name?: string; } export interface BlockedDate { date: string; // ISO date string block_type: BlockType; purpose: BlockPurpose; title: string; resource_id: string | null; all_day: boolean; start_time: string | null; end_time: string | null; time_block_id: string; } export interface TimeBlockConflict { event_id: string; title: string; start_time: string; end_time: string; } export interface TimeBlockConflictCheck { has_conflicts: boolean; conflict_count: number; conflicts: TimeBlockConflict[]; } export interface MyBlocksResponse { business_blocks: TimeBlockListItem[]; my_blocks: TimeBlockListItem[]; resource_id: string | null; resource_name: string | null; can_self_approve: boolean; }