Initial commit: SmoothSchedule multi-tenant scheduling platform
This commit includes: - Django backend with multi-tenancy (django-tenants) - React + TypeScript frontend with Vite - Platform administration API with role-based access control - Authentication system with token-based auth - Quick login dev tools for testing different user roles - CORS and CSRF configuration for local development - Docker development environment setup 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
319
frontend/src/mockData.ts
Normal file
319
frontend/src/mockData.ts
Normal file
@@ -0,0 +1,319 @@
|
||||
import { Appointment, Business, Resource, User, Metric, Customer, PlatformMetric, Ticket, Blocker, Service } from './types';
|
||||
|
||||
export const CURRENT_BUSINESS: Business = {
|
||||
id: 'b1',
|
||||
name: 'Acme Auto Repair',
|
||||
subdomain: 'acme-auto',
|
||||
primaryColor: '#3B82F6', // Blue-500
|
||||
secondaryColor: '#10B981', // Emerald-500
|
||||
whitelabelEnabled: true,
|
||||
plan: 'Business',
|
||||
status: 'Active',
|
||||
joinedAt: new Date('2023-01-15'),
|
||||
resourcesCanReschedule: false,
|
||||
requirePaymentMethodToBook: true,
|
||||
cancellationWindowHours: 24,
|
||||
lateCancellationFeePercent: 50,
|
||||
};
|
||||
|
||||
// Tenant Users
|
||||
export const CURRENT_USER: User = {
|
||||
id: 'u1',
|
||||
name: 'John Owner',
|
||||
email: 'john@acme-auto.com',
|
||||
role: 'owner',
|
||||
avatarUrl: 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
|
||||
};
|
||||
|
||||
export const MANAGER_USER: User = {
|
||||
id: 'u_manager_acme',
|
||||
name: 'Manny Manager',
|
||||
email: 'manny@acme-auto.com',
|
||||
role: 'manager',
|
||||
avatarUrl: 'https://images.unsplash.com/photo-1560250097-0b93528c311a?ixlib=rb-1.2.1&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
|
||||
};
|
||||
|
||||
export const STAFF_USER: User = {
|
||||
id: 'u_staff_main',
|
||||
name: 'Stacy Staff',
|
||||
email: 'stacy@acme-auto.com',
|
||||
role: 'staff',
|
||||
avatarUrl: 'https://images.unsplash.com/photo-1573496359142-b8d87734a5a2?ixlib=rb-1.2.1&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
|
||||
};
|
||||
|
||||
export const RESOURCE_USER: User = {
|
||||
id: 'u_res_main',
|
||||
username: 'bay3',
|
||||
name: 'Service Bay 3',
|
||||
email: 'bay3@internal.acme-auto.com',
|
||||
role: 'resource',
|
||||
avatarUrl: 'https://images.unsplash.com/photo-1581092918056-0c9c7e344934?ixlib=rb-1.2.1&auto=format&fit=crop&w=256&h=256&q=60',
|
||||
};
|
||||
|
||||
export const CUSTOMER_USER: User = {
|
||||
id: 'u_cust1',
|
||||
username: 'alice',
|
||||
name: 'Alice Smith',
|
||||
email: 'alice@example.com',
|
||||
role: 'customer',
|
||||
avatarUrl: 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
|
||||
};
|
||||
|
||||
// Platform Users
|
||||
export const SUPERUSER_USER: User = {
|
||||
id: 'u_super',
|
||||
name: 'Sarah Super',
|
||||
email: 'sarah@smoothschedule.com',
|
||||
role: 'superuser',
|
||||
avatarUrl: 'https://images.unsplash.com/photo-1438761681033-6461ffad8d80?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
|
||||
};
|
||||
|
||||
export const PLATFORM_MANAGER_USER: User = {
|
||||
id: 'u_manager',
|
||||
name: 'Mike Manager',
|
||||
email: 'mike@smoothschedule.com',
|
||||
role: 'platform_manager',
|
||||
avatarUrl: 'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
|
||||
};
|
||||
|
||||
export const PLATFORM_SUPPORT_USER: User = {
|
||||
id: 'u_support',
|
||||
name: 'Sam Support',
|
||||
email: 'sam@smoothschedule.com',
|
||||
role: 'platform_support',
|
||||
avatarUrl: 'https://images.unsplash.com/photo-1599566150163-29194dcaad36?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
|
||||
};
|
||||
|
||||
const staffUserAcme: User = { id: 'u_staff_acme', name: 'Mike Mechanic', email: 'mike@acme-auto.com', role: 'staff', avatarUrl: 'https://randomuser.me/api/portraits/men/45.jpg' };
|
||||
const staffUserTech: User = { id: 'u_staff_tech', name: 'Jen IT', email: 'jen@techsol.com', role: 'staff', avatarUrl: 'https://randomuser.me/api/portraits/women/44.jpg' };
|
||||
|
||||
|
||||
export const RESOURCES: Resource[] = [
|
||||
{ id: 'r1', name: 'Bay 1 (Lift)', type: 'ROOM' },
|
||||
{ id: 'r2', name: 'Bay 2 (Lift)', type: 'ROOM' },
|
||||
{ id: 'r3', name: 'Mike (Senior Mech)', type: 'STAFF', userId: staffUserAcme.id },
|
||||
{ id: 'r4', name: 'Stacy Staff (Diag Tech)', type: 'STAFF', userId: STAFF_USER.id },
|
||||
{ id: 'r5', name: 'Alignment Machine', type: 'EQUIPMENT' },
|
||||
{ id: 'r6', name: 'Service Bay 3', type: 'ROOM', userId: RESOURCE_USER.id },
|
||||
];
|
||||
|
||||
export const SERVICES: Service[] = [
|
||||
{ id: 's1', name: 'Full Synthetic Oil Change', durationMinutes: 60, price: 89.99, description: 'Premium oil and filter change.' },
|
||||
{ id: 's2', name: 'Brake Pad Replacement', durationMinutes: 120, price: 245.00, description: 'Front and rear brake pad replacement.' },
|
||||
{ id: 's3', name: 'Engine Diagnostics', durationMinutes: 90, price: 120.00, description: 'Full computer diagnostics of engine.' },
|
||||
{ id: 's4', name: 'Tire Rotation', durationMinutes: 45, price: 40.00, description: 'Rotate and balance all four tires.' },
|
||||
{ id: 's5', name: '4-Wheel Alignment', durationMinutes: 60, price: 95.50, description: 'Precision laser alignment.' },
|
||||
{ id: 's6', name: 'Tire Patch', durationMinutes: 30, price: 25.00, description: 'Repair minor tire punctures.' },
|
||||
{ id: 's7', name: 'Vehicle Inspection', durationMinutes: 60, price: 75.00, description: 'Comprehensive multi-point vehicle inspection.' },
|
||||
];
|
||||
|
||||
const dayOffset = (days: number) => {
|
||||
const d = new Date();
|
||||
d.setDate(d.getDate() + days);
|
||||
return d;
|
||||
}
|
||||
|
||||
const setTimeOnDate = (date: Date, hours: number, minutes: number) => {
|
||||
const d = new Date(date);
|
||||
d.setHours(hours, minutes, 0, 0);
|
||||
return d;
|
||||
};
|
||||
|
||||
const today = dayOffset(0);
|
||||
const yesterday = dayOffset(-1);
|
||||
const tomorrow = dayOffset(1);
|
||||
const lastWeek = dayOffset(-7);
|
||||
|
||||
|
||||
export const APPOINTMENTS: Appointment[] = [
|
||||
// Today for other resources
|
||||
{ id: 'a1', resourceId: 'r1', customerId: 'c1', customerName: 'Alice Smith', serviceId: 's1', startTime: setTimeOnDate(today, 9, 0), durationMinutes: 60, status: 'CONFIRMED' },
|
||||
{ id: 'a2', resourceId: 'r3', customerId: 'c1', customerName: 'Alice Smith', serviceId: 's7', startTime: setTimeOnDate(today, 9, 0), durationMinutes: 60, status: 'CONFIRMED' },
|
||||
{ id: 'a3', resourceId: 'r2', customerId: 'c2', customerName: 'Bob Jones', serviceId: 's2', startTime: setTimeOnDate(today, 10, 30), durationMinutes: 120, status: 'CONFIRMED' },
|
||||
{ id: 'a4', resourceId: 'r4', customerId: 'c3', customerName: 'Charlie Day', serviceId: 's3', startTime: setTimeOnDate(today, 13, 0), durationMinutes: 90, status: 'COMPLETED' },
|
||||
{ id: 'a5', resourceId: null, customerId: 'c4', customerName: 'Dana White', serviceId: 's4', startTime: setTimeOnDate(today, 14, 0), durationMinutes: 45, status: 'PENDING' },
|
||||
{ id: 'a6', resourceId: 'r5', customerId: 'c5', customerName: 'Evan Stone', serviceId: 's5', startTime: setTimeOnDate(today, 11, 0), durationMinutes: 60, status: 'CONFIRMED' },
|
||||
|
||||
// Appointments for our Resource User (r6 / Service Bay 3)
|
||||
// Today
|
||||
{ id: 'a7', resourceId: 'r6', customerId: 'c6', customerName: 'Fiona Gallagher', serviceId: 's6', startTime: setTimeOnDate(today, 15, 0), durationMinutes: 30, status: 'CONFIRMED' },
|
||||
{ id: 'a8', resourceId: 'r6', customerId: 'c7', customerName: 'George Costanza', serviceId: 's7', startTime: setTimeOnDate(today, 10, 0), durationMinutes: 45, status: 'CONFIRMED' },
|
||||
{ id: 'a9', resourceId: 'r6', customerId: 'c8', customerName: 'Harry Potter', serviceId: 's3', startTime: setTimeOnDate(today, 14, 0), durationMinutes: 60, status: 'CONFIRMED' },
|
||||
// Yesterday
|
||||
{ id: 'a10', resourceId: 'r6', customerId: 'c9', customerName: 'Iris West', serviceId: 's5', startTime: setTimeOnDate(yesterday, 11, 0), durationMinutes: 90, status: 'COMPLETED' },
|
||||
{ id: 'a11', resourceId: 'r6', customerId: 'c10', customerName: 'Jack Sparrow', serviceId: 's6', startTime: setTimeOnDate(yesterday, 14, 30), durationMinutes: 30, status: 'COMPLETED' },
|
||||
{ id: 'a12', resourceId: 'r6', customerId: 'c11', customerName: 'Kara Danvers', serviceId: 's2', startTime: setTimeOnDate(yesterday, 9, 0), durationMinutes: 120, status: 'NO_SHOW' },
|
||||
// Tomorrow
|
||||
{ id: 'a13', resourceId: 'r6', customerId: 'c12', customerName: 'Luke Skywalker', serviceId: 's7', startTime: setTimeOnDate(tomorrow, 10, 0), durationMinutes: 180, status: 'CONFIRMED' },
|
||||
|
||||
// Past appointment for Alice Smith (CUSTOMER_USER)
|
||||
{ id: 'a14', resourceId: 'r1', customerId: 'c1', customerName: 'Alice Smith', serviceId: 's1', startTime: setTimeOnDate(lastWeek, 10, 0), durationMinutes: 60, status: 'COMPLETED' }
|
||||
];
|
||||
|
||||
export const BLOCKERS: Blocker[] = [
|
||||
{ id: 'b1', resourceId: 'r6', startTime: setTimeOnDate(today, 12, 0), durationMinutes: 60, title: 'Lunch Break' },
|
||||
{ id: 'b2', resourceId: 'r6', startTime: setTimeOnDate(tomorrow, 16, 0), durationMinutes: 30, title: 'Inventory Check' },
|
||||
];
|
||||
|
||||
export const DASHBOARD_METRICS: Metric[] = [
|
||||
{ label: 'Total Revenue', value: '$12,450', change: '+12%', trend: 'up' },
|
||||
{ label: 'Appointments', value: '145', change: '+5%', trend: 'up' },
|
||||
{ label: 'New Customers', value: '24', change: '-2%', trend: 'down' },
|
||||
{ label: 'Avg. Ticket', value: '$85.90', change: '+8%', trend: 'up' },
|
||||
];
|
||||
|
||||
const customerUserBob: User = { id: 'u_cust_bob', name: 'Bob Jones', email: 'bob@example.com', role: 'customer', avatarUrl: 'https://randomuser.me/api/portraits/men/12.jpg' };
|
||||
const customerUserCharlie: User = { id: 'u_cust_charlie', name: 'Charlie Day', email: 'charlie@paddys.com', role: 'customer', avatarUrl: 'https://randomuser.me/api/portraits/men/22.jpg' };
|
||||
|
||||
|
||||
export const CUSTOMERS: Customer[] = [
|
||||
{
|
||||
id: 'c1',
|
||||
userId: CUSTOMER_USER.id,
|
||||
name: 'Alice Smith',
|
||||
email: 'alice@example.com',
|
||||
phone: '(555) 123-4567',
|
||||
city: 'New York',
|
||||
state: 'NY',
|
||||
zip: '10001',
|
||||
totalSpend: 1250.50,
|
||||
lastVisit: new Date('2023-10-15'),
|
||||
status: 'Active',
|
||||
avatarUrl: 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
|
||||
tags: ['VIP', 'Regular'],
|
||||
paymentMethods: [
|
||||
{ id: 'pm_1', brand: 'Visa', last4: '4242', isDefault: true },
|
||||
{ id: 'pm_2', brand: 'Mastercard', last4: '5555', isDefault: false },
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'c2',
|
||||
userId: customerUserBob.id,
|
||||
name: 'Bob Jones',
|
||||
email: 'bob.j@example.com',
|
||||
phone: '(555) 987-6543',
|
||||
city: 'Austin',
|
||||
state: 'TX',
|
||||
zip: '78701',
|
||||
totalSpend: 450.00,
|
||||
lastVisit: new Date('2023-09-20'),
|
||||
status: 'Active',
|
||||
avatarUrl: 'https://images.unsplash.com/photo-1519244703995-f4e0f30006d5?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
|
||||
paymentMethods: [],
|
||||
},
|
||||
{
|
||||
id: 'c3',
|
||||
userId: customerUserCharlie.id,
|
||||
name: 'Charlie Day',
|
||||
email: 'charlie@paddys.com',
|
||||
phone: '(555) 444-3333',
|
||||
city: 'Philadelphia',
|
||||
state: 'PA',
|
||||
zip: '19103',
|
||||
totalSpend: 89.99,
|
||||
lastVisit: new Date('2023-10-01'),
|
||||
status: 'Inactive',
|
||||
tags: ['New'],
|
||||
paymentMethods: [
|
||||
{ id: 'pm_3', brand: 'Amex', last4: '0005', isDefault: true },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'c4',
|
||||
name: 'Dana White',
|
||||
email: 'dana@ufc.fake',
|
||||
phone: '(555) 777-8888',
|
||||
city: 'Las Vegas',
|
||||
state: 'NV',
|
||||
zip: '89109',
|
||||
totalSpend: 3200.00,
|
||||
lastVisit: new Date('2023-10-25'),
|
||||
status: 'Active',
|
||||
avatarUrl: 'https://images.unsplash.com/photo-1438761681033-6461ffad8d80?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
|
||||
tags: ['Fleet'],
|
||||
paymentMethods: [],
|
||||
},
|
||||
{
|
||||
id: 'c5',
|
||||
name: 'Evan Stone',
|
||||
email: 'evan@stone.com',
|
||||
phone: '(555) 222-1111',
|
||||
totalSpend: 150.00,
|
||||
lastVisit: null,
|
||||
status: 'Active',
|
||||
tags: ['Referral'],
|
||||
paymentMethods: [],
|
||||
},
|
||||
{
|
||||
id: 'c6',
|
||||
name: 'Fiona Gallagher',
|
||||
email: 'fiona@chicago.net',
|
||||
phone: '(555) 666-9999',
|
||||
city: 'Chicago',
|
||||
state: 'IL',
|
||||
zip: '60601',
|
||||
totalSpend: 0.00,
|
||||
lastVisit: null,
|
||||
status: 'Blocked',
|
||||
paymentMethods: [],
|
||||
},
|
||||
{
|
||||
id: 'c7',
|
||||
name: 'George Costanza',
|
||||
email: 'george@vandelay.com',
|
||||
phone: '(555) 555-5555',
|
||||
city: 'New York',
|
||||
state: 'NY',
|
||||
zip: '10001',
|
||||
totalSpend: 12.50,
|
||||
lastVisit: new Date('2023-01-10'),
|
||||
status: 'Inactive',
|
||||
paymentMethods: [],
|
||||
}
|
||||
];
|
||||
|
||||
// --- Platform Mock Data ---
|
||||
|
||||
const createBiz = (overrides: Partial<Business>): Business => ({
|
||||
...CURRENT_BUSINESS,
|
||||
...overrides,
|
||||
});
|
||||
|
||||
|
||||
export const PLATFORM_METRICS: PlatformMetric[] = [
|
||||
{ label: 'Monthly Recurring Revenue', value: '$425,900', change: '+15.2%', trend: 'up', color: 'blue' },
|
||||
{ label: 'Active Businesses', value: '1,240', change: '+8.1%', trend: 'up', color: 'green' },
|
||||
{ label: 'Avg. Revenue Per User', value: '$343', change: '+4.5%', trend: 'up', color: 'purple' },
|
||||
{ label: 'Churn Rate', value: '2.4%', change: '-0.5%', trend: 'down', color: 'orange' },
|
||||
];
|
||||
|
||||
export const ALL_BUSINESSES: Business[] = [
|
||||
createBiz({ ...CURRENT_BUSINESS, plan: 'Business', status: 'Active' }),
|
||||
createBiz({ id: 'b2', name: 'Prestige Worldwide', subdomain: 'prestige', primaryColor: '#000000', secondaryColor: '#ffffff', whitelabelEnabled: true, plan: 'Enterprise', status: 'Active', joinedAt: new Date('2022-11-10'), requirePaymentMethodToBook: false, cancellationWindowHours: 48, lateCancellationFeePercent: 100 }),
|
||||
createBiz({ id: 'b3', name: 'Mom & Pop Shop', subdomain: 'mompop', primaryColor: '#e11d48', secondaryColor: '#fbbf24', whitelabelEnabled: false, plan: 'Free', status: 'Active', joinedAt: new Date('2023-03-05'), requirePaymentMethodToBook: false, cancellationWindowHours: 0, lateCancellationFeePercent: 0 }),
|
||||
createBiz({ id: 'b4', name: 'Tech Solutions', subdomain: 'techsol', primaryColor: '#6366f1', secondaryColor: '#8b5cf6', whitelabelEnabled: true, plan: 'Professional', status: 'Trial', joinedAt: new Date('2023-10-20'), requirePaymentMethodToBook: true, cancellationWindowHours: 12, lateCancellationFeePercent: 25 }),
|
||||
createBiz({ id: 'b5', name: 'Inactive Biz', subdomain: 'inactive', primaryColor: '#9ca3af', secondaryColor: '#d1d5db', whitelabelEnabled: false, plan: 'Free', status: 'Suspended', joinedAt: new Date('2021-05-15'), requirePaymentMethodToBook: false, cancellationWindowHours: 24, lateCancellationFeePercent: 0 }),
|
||||
];
|
||||
|
||||
export const SUPPORT_TICKETS: Ticket[] = [
|
||||
{ id: 't101', subject: 'Cannot connect custom domain', businessName: 'Prestige Worldwide', priority: 'High', status: 'Open', createdAt: new Date('2023-10-26T09:00:00') },
|
||||
{ id: 't102', subject: 'Question about invoice #4022', businessName: 'Acme Auto Repair', priority: 'Low', status: 'In Progress', createdAt: new Date('2023-10-25T14:30:00') },
|
||||
{ id: 't103', subject: 'Feature request: Group bookings', businessName: 'Tech Solutions', priority: 'Medium', status: 'Open', createdAt: new Date('2023-10-26T11:15:00') },
|
||||
{ id: 't104', subject: 'Login issues for staff member', businessName: 'Mom & Pop Shop', priority: 'High', status: 'Resolved', createdAt: new Date('2023-10-24T16:45:00') },
|
||||
];
|
||||
|
||||
export const ALL_USERS: User[] = [
|
||||
SUPERUSER_USER,
|
||||
PLATFORM_MANAGER_USER,
|
||||
PLATFORM_SUPPORT_USER,
|
||||
CURRENT_USER, // Owner of Acme
|
||||
MANAGER_USER, // Manager of Acme
|
||||
STAFF_USER, // Staff of Acme
|
||||
RESOURCE_USER, // Resource of Acme
|
||||
{ id: 'u_owner_prestige', name: 'Brennan Huff', email: 'brennan@prestige.com', role: 'owner', avatarUrl: 'https://randomuser.me/api/portraits/men/32.jpg' },
|
||||
staffUserAcme,
|
||||
staffUserTech,
|
||||
CUSTOMER_USER, // Alice
|
||||
customerUserBob,
|
||||
customerUserCharlie,
|
||||
];
|
||||
Reference in New Issue
Block a user