Add booking flow, business hours, and dark mode support
Features: - Complete multi-step booking flow with service selection, date/time picker, auth (login/signup with email verification), payment, and confirmation - Business hours settings page for defining when business is open - TimeBlock purpose field (BUSINESS_HOURS, CLOSURE, UNAVAILABLE) - Service resource assignment with prep/takedown time buffers - Availability checking respects business hours and service buffers - Customer registration via email verification code UI/UX: - Full dark mode support for all booking components - Separate first/last name fields in signup form - Back buttons on each wizard step - Removed auto-redirect from confirmation page API: - Public endpoints for services, availability, business hours - Customer verification and registration endpoints - Tenant lookup from X-Business-Subdomain header 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -9,47 +9,57 @@ import FeaturesPermissionsEditor, { PERMISSION_DEFINITIONS, getPermissionKey } f
|
||||
const TIER_DEFAULTS: Record<string, {
|
||||
max_users: number;
|
||||
max_resources: number;
|
||||
max_pages: number;
|
||||
can_manage_oauth_credentials: boolean;
|
||||
can_accept_payments: boolean;
|
||||
can_use_custom_domain: boolean;
|
||||
can_white_label: boolean;
|
||||
can_api_access: boolean;
|
||||
can_customize_booking_page: boolean;
|
||||
}> = {
|
||||
FREE: {
|
||||
max_users: 2,
|
||||
max_resources: 5,
|
||||
max_pages: 1,
|
||||
can_manage_oauth_credentials: false,
|
||||
can_accept_payments: false,
|
||||
can_use_custom_domain: false,
|
||||
can_white_label: false,
|
||||
can_api_access: false,
|
||||
can_customize_booking_page: false,
|
||||
},
|
||||
STARTER: {
|
||||
max_users: 5,
|
||||
max_resources: 15,
|
||||
max_pages: 3,
|
||||
can_manage_oauth_credentials: false,
|
||||
can_accept_payments: true,
|
||||
can_use_custom_domain: false,
|
||||
can_white_label: false,
|
||||
can_api_access: false,
|
||||
can_customize_booking_page: true,
|
||||
},
|
||||
PROFESSIONAL: {
|
||||
max_users: 15,
|
||||
max_resources: 50,
|
||||
max_pages: 10,
|
||||
can_manage_oauth_credentials: false,
|
||||
can_accept_payments: true,
|
||||
can_use_custom_domain: true,
|
||||
can_white_label: false,
|
||||
can_api_access: true,
|
||||
can_customize_booking_page: true,
|
||||
},
|
||||
ENTERPRISE: {
|
||||
max_users: -1, // unlimited
|
||||
max_resources: -1, // unlimited
|
||||
max_pages: -1, // unlimited
|
||||
can_manage_oauth_credentials: true,
|
||||
can_accept_payments: true,
|
||||
can_use_custom_domain: true,
|
||||
can_white_label: true,
|
||||
can_api_access: true,
|
||||
can_customize_booking_page: true,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -70,6 +80,7 @@ const BusinessEditModal: React.FC<BusinessEditModalProps> = ({ business, isOpen,
|
||||
// Limits
|
||||
max_users: 5,
|
||||
max_resources: 10,
|
||||
max_pages: 1,
|
||||
// Platform Permissions (flat, matching backend model)
|
||||
can_manage_oauth_credentials: false,
|
||||
can_accept_payments: false,
|
||||
@@ -123,6 +134,7 @@ const BusinessEditModal: React.FC<BusinessEditModalProps> = ({ business, isOpen,
|
||||
// Limits
|
||||
max_users: plan.limits?.max_users ?? staticDefaults.max_users,
|
||||
max_resources: plan.limits?.max_resources ?? staticDefaults.max_resources,
|
||||
max_pages: plan.limits?.max_pages ?? staticDefaults.max_pages,
|
||||
// Platform Permissions
|
||||
can_manage_oauth_credentials: plan.permissions?.can_manage_oauth_credentials ?? staticDefaults.can_manage_oauth_credentials,
|
||||
can_accept_payments: plan.permissions?.can_accept_payments ?? staticDefaults.can_accept_payments,
|
||||
@@ -201,6 +213,7 @@ const BusinessEditModal: React.FC<BusinessEditModalProps> = ({ business, isOpen,
|
||||
// Limits
|
||||
max_users: business.max_users || 5,
|
||||
max_resources: business.max_resources || 10,
|
||||
max_pages: business.max_pages || 1,
|
||||
// Platform Permissions (flat, matching backend)
|
||||
can_manage_oauth_credentials: business.can_manage_oauth_credentials || false,
|
||||
can_accept_payments: business.can_accept_payments || false,
|
||||
@@ -347,7 +360,7 @@ const BusinessEditModal: React.FC<BusinessEditModalProps> = ({ business, isOpen,
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400">
|
||||
Use -1 for unlimited. These limits control what this business can create.
|
||||
</p>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
Max Users
|
||||
@@ -372,9 +385,42 @@ const BusinessEditModal: React.FC<BusinessEditModalProps> = ({ business, isOpen,
|
||||
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
Max Pages
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
min="-1"
|
||||
value={editForm.max_pages}
|
||||
onChange={(e) => setEditForm({ ...editForm, max_pages: parseInt(e.target.value) || 0 })}
|
||||
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Site Builder Access */}
|
||||
<div className="space-y-3 pt-4 border-t border-gray-200 dark:border-gray-700">
|
||||
<h3 className="text-sm font-medium text-gray-900 dark:text-white">
|
||||
Site Builder
|
||||
</h3>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400">
|
||||
Access the public-facing website builder for this business. Current limit: {editForm.max_pages === -1 ? 'unlimited' : editForm.max_pages} page{editForm.max_pages !== 1 ? 's' : ''}.
|
||||
</p>
|
||||
<a
|
||||
href={`http://${business.subdomain}.lvh.me:5173/site-editor`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-flex items-center gap-2 px-4 py-2 bg-indigo-50 dark:bg-indigo-900/20 text-indigo-600 dark:text-indigo-400 rounded-lg hover:bg-indigo-100 dark:hover:bg-indigo-900/30 font-medium text-sm transition-colors"
|
||||
>
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
|
||||
</svg>
|
||||
Open Site Builder
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{/* Features & Permissions - Using unified FeaturesPermissionsEditor */}
|
||||
<div className="space-y-4 pt-4 border-t border-gray-200 dark:border-gray-700">
|
||||
<FeaturesPermissionsEditor
|
||||
|
||||
Reference in New Issue
Block a user