@bailierich/booking-components 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +319 -0
- package/TENANT_DATA_INTEGRATION.md +402 -0
- package/TENANT_SETUP.md +316 -0
- package/components/BookingFlow/BookingFlow.tsx +790 -0
- package/components/BookingFlow/index.ts +5 -0
- package/components/BookingFlow/steps/AddonsSelection.tsx +118 -0
- package/components/BookingFlow/steps/Confirmation.tsx +185 -0
- package/components/BookingFlow/steps/ContactForm.tsx +292 -0
- package/components/BookingFlow/steps/CycleAwareDateSelection.tsx +277 -0
- package/components/BookingFlow/steps/DateSelection.tsx +473 -0
- package/components/BookingFlow/steps/ServiceSelection.tsx +315 -0
- package/components/BookingFlow/steps/TimeSelection.tsx +230 -0
- package/components/BookingFlow/steps/index.ts +10 -0
- package/components/BottomSheet/index.tsx +120 -0
- package/components/Forms/FormBlock.tsx +283 -0
- package/components/Forms/FormField.tsx +385 -0
- package/components/Forms/FormRenderer.tsx +216 -0
- package/components/Forms/FormValidation.ts +122 -0
- package/components/Forms/index.ts +4 -0
- package/components/HoldTimer/HoldTimer.tsx +266 -0
- package/components/HoldTimer/index.ts +2 -0
- package/components/SectionRenderer.tsx +558 -0
- package/components/Sections/About.tsx +145 -0
- package/components/Sections/BeforeAfter.tsx +81 -0
- package/components/Sections/BookingSection.tsx +76 -0
- package/components/Sections/Contact.tsx +103 -0
- package/components/Sections/FAQSection.tsx +239 -0
- package/components/Sections/FeatureContent.tsx +113 -0
- package/components/Sections/FeaturedLink.tsx +103 -0
- package/components/Sections/FixedInfoCard.tsx +189 -0
- package/components/Sections/Gallery.tsx +83 -0
- package/components/Sections/Header.tsx +78 -0
- package/components/Sections/Hero.tsx +178 -0
- package/components/Sections/ImageSection.tsx +147 -0
- package/components/Sections/InstagramFeed.tsx +38 -0
- package/components/Sections/LinkList.tsx +76 -0
- package/components/Sections/LocationMap.tsx +202 -0
- package/components/Sections/Logo.tsx +61 -0
- package/components/Sections/MinimalFooter.tsx +78 -0
- package/components/Sections/MinimalHeader.tsx +81 -0
- package/components/Sections/MinimalNavigation.tsx +63 -0
- package/components/Sections/Navbar.tsx +258 -0
- package/components/Sections/PricingTable.tsx +106 -0
- package/components/Sections/ScrollingTextDivider.tsx +138 -0
- package/components/Sections/ScrollingTextDivider.tsx.bak +138 -0
- package/components/Sections/ServicesPreview.tsx +129 -0
- package/components/Sections/SocialBar.tsx +177 -0
- package/components/Sections/Team.tsx +80 -0
- package/components/Sections/Testimonials.tsx +92 -0
- package/components/Sections/TextSection.tsx +116 -0
- package/components/Sections/VideoSection.tsx +178 -0
- package/components/Sections/index.ts +57 -0
- package/components/index.ts +21 -0
- package/dist/index-DAai7Glf.d.mts +474 -0
- package/dist/index-DAai7Glf.d.ts +474 -0
- package/dist/index.d.mts +1075 -0
- package/dist/index.d.ts +1075 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +22 -0
- package/dist/index.mjs.map +1 -0
- package/dist/styles/index.d.mts +1 -0
- package/dist/styles/index.d.ts +1 -0
- package/dist/styles/index.js +2 -0
- package/dist/styles/index.js.map +1 -0
- package/dist/styles/index.mjs +2 -0
- package/dist/styles/index.mjs.map +1 -0
- package/docs/API.md +849 -0
- package/docs/CALLBACKS.md +760 -0
- package/docs/COMPLETE_SESSION_SUMMARY.md +404 -0
- package/docs/DATA_SHAPES.md +684 -0
- package/docs/MIGRATION.md +662 -0
- package/docs/PAYMENT_INTEGRATION.md +766 -0
- package/docs/SESSION_SUMMARY.md +185 -0
- package/docs/STYLING.md +735 -0
- package/index.ts +4 -0
- package/lib/storage.ts +239 -0
- package/package.json +59 -0
- package/styles/animations.ts +210 -0
- package/styles/index.ts +1 -0
- package/tsconfig.json +32 -0
- package/tsup.config.ts +13 -0
- package/types/index.ts +369 -0
package/README.md
ADDED
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
# @oviah/booking-components
|
|
2
|
+
|
|
3
|
+
Reusable booking and section components for the OVIAH multi-tenant booking platform.
|
|
4
|
+
|
|
5
|
+
## 📦 What's Included
|
|
6
|
+
|
|
7
|
+
- **21 Section Components** - Logo, Hero, Gallery, Testimonials, and more
|
|
8
|
+
- **Complete Booking Flow** - Multi-step booking with validation and animations
|
|
9
|
+
- **Universal Renderers** - SectionRenderer and BookingFlow orchestrator
|
|
10
|
+
- **UI Components** - BottomSheet, modals, and form elements
|
|
11
|
+
- **Animation Utilities** - Pre-built Framer Motion animations
|
|
12
|
+
- **Full TypeScript Support** - Complete type definitions
|
|
13
|
+
|
|
14
|
+
## 🚀 Quick Start
|
|
15
|
+
|
|
16
|
+
```tsx
|
|
17
|
+
import {
|
|
18
|
+
SectionRenderer,
|
|
19
|
+
BookingFlow,
|
|
20
|
+
Hero,
|
|
21
|
+
Gallery
|
|
22
|
+
} from '@oviah/booking-components';
|
|
23
|
+
|
|
24
|
+
// Render any section dynamically
|
|
25
|
+
<SectionRenderer
|
|
26
|
+
section={{
|
|
27
|
+
id: 'hero-1',
|
|
28
|
+
type: 'hero',
|
|
29
|
+
settings: {
|
|
30
|
+
headline: 'Welcome',
|
|
31
|
+
subheadline: 'Book your appointment today'
|
|
32
|
+
}
|
|
33
|
+
}}
|
|
34
|
+
theme={{
|
|
35
|
+
colors: { primary: '#D8C4FF', secondary: '#014421', text: '#000000' },
|
|
36
|
+
typography: { headingFont: 'Geist Sans' }
|
|
37
|
+
}}
|
|
38
|
+
/>
|
|
39
|
+
|
|
40
|
+
// Complete booking flow
|
|
41
|
+
<BookingFlow
|
|
42
|
+
config={config}
|
|
43
|
+
colors={colors}
|
|
44
|
+
services={services}
|
|
45
|
+
onComplete={(bookingData) => {
|
|
46
|
+
console.log('Booking complete!', bookingData);
|
|
47
|
+
}}
|
|
48
|
+
/>
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## 📚 Documentation
|
|
52
|
+
|
|
53
|
+
- **[API Reference](./docs/API.md)** - Complete component API
|
|
54
|
+
- **[Data Shapes](./docs/DATA_SHAPES.md)** - Data structures and interfaces
|
|
55
|
+
- **[Callbacks](./docs/CALLBACKS.md)** - Event handling patterns
|
|
56
|
+
- **[Styling](./docs/STYLING.md)** - Theme customization guide
|
|
57
|
+
- **[Payment Integration](./docs/PAYMENT_INTEGRATION.md)** - Payment setup guide
|
|
58
|
+
- **[Migration Guide](./docs/MIGRATION.md)** - Migrate from inline components
|
|
59
|
+
|
|
60
|
+
## 🎨 Components
|
|
61
|
+
|
|
62
|
+
### Section Components (21 total)
|
|
63
|
+
|
|
64
|
+
All section components accept `colors` and `typography` props for theming:
|
|
65
|
+
|
|
66
|
+
- **Logo** - Logo/brand mark display
|
|
67
|
+
- **Header** - Text header with name, title, bio
|
|
68
|
+
- **FeaturedLink** - Large CTA button
|
|
69
|
+
- **LinkList** - List of clickable links
|
|
70
|
+
- **SocialBar** - Social media icons
|
|
71
|
+
- **InstagramFeed** - Instagram grid
|
|
72
|
+
- **MinimalHeader** - Sticky header
|
|
73
|
+
- **MinimalNavigation** - Navigation links
|
|
74
|
+
- **MinimalFooter** - Footer section
|
|
75
|
+
- **FeatureContent** - Feature with image/text
|
|
76
|
+
- **Hero** - Hero section with image
|
|
77
|
+
- **About** - About section
|
|
78
|
+
- **ServicesPreview** - Services grid/list
|
|
79
|
+
- **Gallery** - Image gallery
|
|
80
|
+
- **Testimonials** - Client testimonials
|
|
81
|
+
- **Team** - Team members
|
|
82
|
+
- **Contact** - Contact info/form
|
|
83
|
+
- **BeforeAfter** - Before/after comparisons
|
|
84
|
+
- **PricingTable** - Pricing plans
|
|
85
|
+
- **BookingSection** - Booking flow wrapper
|
|
86
|
+
- **FixedInfoCard** - Fixed corner card
|
|
87
|
+
|
|
88
|
+
### Booking Flow Components
|
|
89
|
+
|
|
90
|
+
- **BookingFlow** - Main orchestrator
|
|
91
|
+
- **ServiceSelection** - Service picker
|
|
92
|
+
- **DateSelection** - Date picker
|
|
93
|
+
- **TimeSelection** - Time slot picker
|
|
94
|
+
- **AddonsSelection** - Add-ons picker
|
|
95
|
+
- **ContactForm** - Contact information
|
|
96
|
+
- **Confirmation** - Review and payment
|
|
97
|
+
|
|
98
|
+
### UI Components
|
|
99
|
+
|
|
100
|
+
- **BottomSheet** - Slide-up modal
|
|
101
|
+
- **SectionRenderer** - Universal section renderer
|
|
102
|
+
|
|
103
|
+
### Animation Utilities
|
|
104
|
+
|
|
105
|
+
- `createEntranceAnimation()` - Entry animations
|
|
106
|
+
- `createStaggerAnimation()` - Stagger lists
|
|
107
|
+
- `getSlideVariants()` - Slide transitions
|
|
108
|
+
- `getFadeVariants()` - Fade transitions
|
|
109
|
+
|
|
110
|
+
## 🔧 Installation
|
|
111
|
+
|
|
112
|
+
### For This Project (stylink-saas-dashboard)
|
|
113
|
+
|
|
114
|
+
This package is part of the workspace and is automatically linked:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
npm install
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### For Your Tenant Template Project
|
|
121
|
+
|
|
122
|
+
You have **three options** to use this package in your other project:
|
|
123
|
+
|
|
124
|
+
#### Option 1: Local Link (Best for Active Development)
|
|
125
|
+
|
|
126
|
+
**Step 1 - In this project:**
|
|
127
|
+
```bash
|
|
128
|
+
cd packages/booking-components
|
|
129
|
+
npm run build
|
|
130
|
+
npm link
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
**Step 2 - In your tenant template project:**
|
|
134
|
+
```bash
|
|
135
|
+
npm link @oviah/booking-components
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
**To unlink later:**
|
|
139
|
+
```bash
|
|
140
|
+
npm unlink @oviah/booking-components
|
|
141
|
+
npm install
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
#### Option 2: File Path (Best for Local Development)
|
|
145
|
+
|
|
146
|
+
**In your tenant template's package.json:**
|
|
147
|
+
```json
|
|
148
|
+
{
|
|
149
|
+
"dependencies": {
|
|
150
|
+
"@oviah/booking-components": "file:../stylink-saas-dashboard/packages/booking-components"
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Then run:
|
|
156
|
+
```bash
|
|
157
|
+
npm install
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
**Note:** Adjust the path based on where your projects are located relative to each other.
|
|
161
|
+
|
|
162
|
+
#### Option 3: GitHub Packages (Best for Production)
|
|
163
|
+
|
|
164
|
+
**Step 1 - Update this package's package.json:**
|
|
165
|
+
```json
|
|
166
|
+
{
|
|
167
|
+
"repository": {
|
|
168
|
+
"type": "git",
|
|
169
|
+
"url": "https://github.com/YOUR_ORG/stylink-saas-dashboard.git",
|
|
170
|
+
"directory": "packages/booking-components"
|
|
171
|
+
},
|
|
172
|
+
"publishConfig": {
|
|
173
|
+
"registry": "https://npm.pkg.github.com"
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
**Step 2 - Create .npmrc in packages/booking-components:**
|
|
179
|
+
```
|
|
180
|
+
@oviah:registry=https://npm.pkg.github.com
|
|
181
|
+
//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
**Step 3 - Build and publish:**
|
|
185
|
+
```bash
|
|
186
|
+
cd packages/booking-components
|
|
187
|
+
npm run build
|
|
188
|
+
npm publish
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
**Step 4 - In your tenant template, create .npmrc:**
|
|
192
|
+
```
|
|
193
|
+
@oviah:registry=https://npm.pkg.github.com
|
|
194
|
+
//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
**Step 5 - Install:**
|
|
198
|
+
```bash
|
|
199
|
+
npm install @oviah/booking-components@1.0.0
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## 💡 Usage Examples
|
|
203
|
+
|
|
204
|
+
### Render Sections Dynamically
|
|
205
|
+
|
|
206
|
+
```tsx
|
|
207
|
+
const sections = [
|
|
208
|
+
{ id: '1', type: 'logo', settings: { logoUrl: '/logo.png' } },
|
|
209
|
+
{ id: '2', type: 'hero', settings: { headline: 'Welcome' } },
|
|
210
|
+
{ id: '3', type: 'gallery', settings: { images: [...] } }
|
|
211
|
+
];
|
|
212
|
+
|
|
213
|
+
{sections.map(section => (
|
|
214
|
+
<SectionRenderer
|
|
215
|
+
key={section.id}
|
|
216
|
+
section={section}
|
|
217
|
+
theme={theme}
|
|
218
|
+
/>
|
|
219
|
+
))}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### Complete Booking Flow
|
|
223
|
+
|
|
224
|
+
```tsx
|
|
225
|
+
<BookingFlow
|
|
226
|
+
config={{
|
|
227
|
+
steps: {
|
|
228
|
+
service_selection: { enabled: true, settings: {...} },
|
|
229
|
+
date_selection: { enabled: true, settings: {...} },
|
|
230
|
+
time_selection: { enabled: true, settings: {...} },
|
|
231
|
+
contact_form: { enabled: true, settings: {...} }
|
|
232
|
+
},
|
|
233
|
+
progressBar: { style: 'dots' },
|
|
234
|
+
transitions: { style: 'slide', speed: 'normal' }
|
|
235
|
+
}}
|
|
236
|
+
colors={{
|
|
237
|
+
primary: '#D8C4FF',
|
|
238
|
+
secondary: '#014421',
|
|
239
|
+
text: '#000000'
|
|
240
|
+
}}
|
|
241
|
+
services={services}
|
|
242
|
+
dates={availableDates}
|
|
243
|
+
times={availableTimes}
|
|
244
|
+
onComplete={(bookingData) => {
|
|
245
|
+
// Handle booking submission
|
|
246
|
+
}}
|
|
247
|
+
/>
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### Individual Components
|
|
251
|
+
|
|
252
|
+
```tsx
|
|
253
|
+
import { Hero, Gallery, Testimonials } from '@oviah/booking-components';
|
|
254
|
+
|
|
255
|
+
<Hero
|
|
256
|
+
headline="Welcome to Our Salon"
|
|
257
|
+
subheadline="Professional beauty services"
|
|
258
|
+
heroImage="/hero.jpg"
|
|
259
|
+
ctaButton={{ text: 'Book Now', url: '/book' }}
|
|
260
|
+
layout="split"
|
|
261
|
+
colors={{ primary: '#D8C4FF', text: '#000000' }}
|
|
262
|
+
typography={{ headingFont: 'Geist Sans' }}
|
|
263
|
+
/>
|
|
264
|
+
|
|
265
|
+
<Gallery
|
|
266
|
+
title="Our Work"
|
|
267
|
+
images={galleryImages}
|
|
268
|
+
columns={3}
|
|
269
|
+
colors={{ primary: '#D8C4FF', text: '#000000' }}
|
|
270
|
+
typography={{ headingFont: 'Geist Sans' }}
|
|
271
|
+
/>
|
|
272
|
+
|
|
273
|
+
<Testimonials
|
|
274
|
+
title="What Clients Say"
|
|
275
|
+
testimonials={testimonials}
|
|
276
|
+
layout="grid"
|
|
277
|
+
colors={{ primary: '#D8C4FF', text: '#000000' }}
|
|
278
|
+
typography={{ headingFont: 'Geist Sans' }}
|
|
279
|
+
/>
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
## 🎯 Key Features
|
|
283
|
+
|
|
284
|
+
### ✅ Type-Safe
|
|
285
|
+
|
|
286
|
+
Full TypeScript support with complete type definitions:
|
|
287
|
+
|
|
288
|
+
```typescript
|
|
289
|
+
import type {
|
|
290
|
+
Section,
|
|
291
|
+
BookingFlowConfig,
|
|
292
|
+
ColorScheme,
|
|
293
|
+
TypographyConfig
|
|
294
|
+
} from '@oviah/booking-components';
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### ✅ Themeable
|
|
298
|
+
|
|
299
|
+
All components accept consistent color and typography props.
|
|
300
|
+
|
|
301
|
+
### ✅ Responsive
|
|
302
|
+
|
|
303
|
+
Mobile-first design with automatic breakpoints.
|
|
304
|
+
|
|
305
|
+
### ✅ Accessible
|
|
306
|
+
|
|
307
|
+
Semantic HTML, ARIA labels, keyboard navigation, screen reader support.
|
|
308
|
+
|
|
309
|
+
### ✅ Animated
|
|
310
|
+
|
|
311
|
+
Smooth animations with Framer Motion that respect `prefers-reduced-motion`.
|
|
312
|
+
|
|
313
|
+
## 🤝 Contributing
|
|
314
|
+
|
|
315
|
+
This is a private package for the OVIAH platform. For internal use only.
|
|
316
|
+
|
|
317
|
+
## 📝 License
|
|
318
|
+
|
|
319
|
+
UNLICENSED - Private package for OVIAH
|
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
# Tenant Data Integration Guide
|
|
2
|
+
|
|
3
|
+
Complete guide for integrating the configuration data structure in your tenant template project.
|
|
4
|
+
|
|
5
|
+
## 📊 Configuration Data Structure
|
|
6
|
+
|
|
7
|
+
### Storage Location
|
|
8
|
+
|
|
9
|
+
**Database Table**: `business_configs`
|
|
10
|
+
**Column**: `page_builder` (JSONB)
|
|
11
|
+
**Access**: Via API endpoint `/api/studio/config/[businessId]`
|
|
12
|
+
|
|
13
|
+
The entire page configuration is stored as a JSON object in the `page_builder` column of the `business_configs` table.
|
|
14
|
+
|
|
15
|
+
## 🗂️ Complete Config Object Structure
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
{
|
|
19
|
+
// Template metadata
|
|
20
|
+
template_id?: string;
|
|
21
|
+
version: string;
|
|
22
|
+
|
|
23
|
+
// Theme configuration
|
|
24
|
+
theme: {
|
|
25
|
+
colors: {
|
|
26
|
+
primary: string; // e.g., "#D8C4FF"
|
|
27
|
+
secondary: string; // e.g., "#014421"
|
|
28
|
+
text: string; // e.g., "#000000"
|
|
29
|
+
buttonText?: string; // e.g., "#FFFFFF"
|
|
30
|
+
linkBackground?: string;
|
|
31
|
+
linkText?: string;
|
|
32
|
+
bookingText?: string;
|
|
33
|
+
},
|
|
34
|
+
typography: {
|
|
35
|
+
headingFont: string; // e.g., "'Geist Sans', sans-serif"
|
|
36
|
+
bodyFont: string; // e.g., "'Inter', sans-serif"
|
|
37
|
+
bodySize: string; // e.g., "16px"
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
// Page background
|
|
42
|
+
background: {
|
|
43
|
+
type: 'color' | 'gradient' | 'image';
|
|
44
|
+
value: string; // Color hex, gradient CSS, or image URL
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
// Page sections array
|
|
48
|
+
sections: Array<{
|
|
49
|
+
id: string; // Unique identifier
|
|
50
|
+
type: string; // Section type (hero, about, etc.)
|
|
51
|
+
enabled: boolean; // Show/hide toggle
|
|
52
|
+
position: number; // Sort order
|
|
53
|
+
settings: {
|
|
54
|
+
// Type-specific settings
|
|
55
|
+
marginTop?: number;
|
|
56
|
+
marginBottom?: number;
|
|
57
|
+
paddingTop?: number;
|
|
58
|
+
paddingBottom?: number;
|
|
59
|
+
// ... section-specific props
|
|
60
|
+
}
|
|
61
|
+
}>,
|
|
62
|
+
|
|
63
|
+
// Booking flow configuration
|
|
64
|
+
booking_flow?: {
|
|
65
|
+
steps: {
|
|
66
|
+
service_selection: { enabled: boolean; settings: {...} },
|
|
67
|
+
date_selection: { enabled: boolean; settings: {...} },
|
|
68
|
+
time_selection: { enabled: boolean; settings: {...} },
|
|
69
|
+
addon_selection: { enabled: boolean; settings: {...} },
|
|
70
|
+
contact_form: { enabled: boolean; settings: {...} }
|
|
71
|
+
},
|
|
72
|
+
progressBar: {
|
|
73
|
+
type: 'stepped' | 'dots' | 'bar';
|
|
74
|
+
showStepNames?: boolean;
|
|
75
|
+
},
|
|
76
|
+
transitions: {
|
|
77
|
+
style: 'slide' | 'fade';
|
|
78
|
+
speed: 'fast' | 'normal' | 'slow';
|
|
79
|
+
},
|
|
80
|
+
background?: {
|
|
81
|
+
type: 'color' | 'gradient' | 'image';
|
|
82
|
+
value: string;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## 🎨 How Background & Theme Are Handled
|
|
89
|
+
|
|
90
|
+
### In the Studio
|
|
91
|
+
|
|
92
|
+
**Storage**: Config is saved to `business_configs.page_builder` as JSONB
|
|
93
|
+
|
|
94
|
+
**ThemeEditor Component** (`src/components/studio/editor/ThemeEditor.tsx`):
|
|
95
|
+
- Manages `config.theme.colors` (primary, secondary, text, buttonText)
|
|
96
|
+
- Manages `config.theme.typography` (headingFont, bodyFont, bodySize)
|
|
97
|
+
- Manages `config.background` (type, value)
|
|
98
|
+
|
|
99
|
+
**Auto-save**: Changes are debounced and saved automatically every 2 seconds
|
|
100
|
+
|
|
101
|
+
### In the Preview/Tenant Page
|
|
102
|
+
|
|
103
|
+
**Fetching**: `GET /api/studio/config/[businessId]` returns the full config
|
|
104
|
+
|
|
105
|
+
**Application** (`src/app/[businessId]/page.tsx` lines 377-412):
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
// 1. Extract theme and background from config
|
|
109
|
+
const { theme, background } = config;
|
|
110
|
+
const themeColors = theme?.colors || {
|
|
111
|
+
primary: '#D8C4FF',
|
|
112
|
+
secondary: '#014421',
|
|
113
|
+
text: '#000000'
|
|
114
|
+
};
|
|
115
|
+
const themeTypography = theme?.typography || {
|
|
116
|
+
headingFont: 'Geist Sans',
|
|
117
|
+
bodyFont: 'Inter',
|
|
118
|
+
bodySize: '16px'
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
// 2. Calculate background style based on type
|
|
122
|
+
const backgroundStyle: React.CSSProperties =
|
|
123
|
+
background?.type === 'gradient'
|
|
124
|
+
? { background: background.value }
|
|
125
|
+
: background?.type === 'image'
|
|
126
|
+
? {
|
|
127
|
+
backgroundImage: `url(${background.value})`,
|
|
128
|
+
backgroundSize: 'cover',
|
|
129
|
+
backgroundPosition: 'center'
|
|
130
|
+
}
|
|
131
|
+
: { backgroundColor: background?.value || '#FFFFFF' };
|
|
132
|
+
|
|
133
|
+
// 3. Apply to root container
|
|
134
|
+
<div
|
|
135
|
+
className="min-h-screen"
|
|
136
|
+
style={{
|
|
137
|
+
...backgroundStyle,
|
|
138
|
+
color: themeColors.text,
|
|
139
|
+
fontFamily: themeTypography.bodyFont,
|
|
140
|
+
fontSize: themeTypography.bodySize
|
|
141
|
+
}}
|
|
142
|
+
>
|
|
143
|
+
{/* Sections rendered here */}
|
|
144
|
+
</div>
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## 🚀 Integration in Your Tenant Template
|
|
148
|
+
|
|
149
|
+
### Step 1: Create API Endpoint
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
// app/api/config/[slug]/route.ts
|
|
153
|
+
import { createClient } from '@supabase/supabase-js';
|
|
154
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
155
|
+
|
|
156
|
+
const supabase = createClient(
|
|
157
|
+
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
|
158
|
+
process.env.SUPABASE_SERVICE_ROLE_KEY!
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
export async function GET(
|
|
162
|
+
request: NextRequest,
|
|
163
|
+
{ params }: { params: Promise<{ slug: string }> }
|
|
164
|
+
) {
|
|
165
|
+
const { slug } = await params;
|
|
166
|
+
|
|
167
|
+
const { data, error } = await supabase
|
|
168
|
+
.from('business_configs')
|
|
169
|
+
.select('page_builder, business_name, client_id')
|
|
170
|
+
.eq('client_id', slug)
|
|
171
|
+
.single();
|
|
172
|
+
|
|
173
|
+
if (error || !data) {
|
|
174
|
+
return NextResponse.json(
|
|
175
|
+
{ error: 'Configuration not found' },
|
|
176
|
+
{ status: 404 }
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return NextResponse.json({
|
|
181
|
+
config: data.page_builder,
|
|
182
|
+
businessName: data.business_name,
|
|
183
|
+
clientId: data.client_id
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Step 2: Create Tenant Landing Page
|
|
189
|
+
|
|
190
|
+
```typescript
|
|
191
|
+
// app/[slug]/page.tsx
|
|
192
|
+
'use client';
|
|
193
|
+
|
|
194
|
+
import { use, useEffect, useState } from 'react';
|
|
195
|
+
import { SectionRenderer } from '@oviah/booking-components';
|
|
196
|
+
|
|
197
|
+
export default function TenantLandingPage({ params }) {
|
|
198
|
+
const { slug } = use(params);
|
|
199
|
+
const [config, setConfig] = useState(null);
|
|
200
|
+
const [loading, setLoading] = useState(true);
|
|
201
|
+
|
|
202
|
+
useEffect(() => {
|
|
203
|
+
async function loadConfig() {
|
|
204
|
+
try {
|
|
205
|
+
const res = await fetch(`/api/config/${slug}`);
|
|
206
|
+
if (!res.ok) throw new Error('Failed to load');
|
|
207
|
+
|
|
208
|
+
const data = await res.json();
|
|
209
|
+
setConfig(data.config);
|
|
210
|
+
} catch (error) {
|
|
211
|
+
console.error('Failed to load config:', error);
|
|
212
|
+
} finally {
|
|
213
|
+
setLoading(false);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
loadConfig();
|
|
218
|
+
}, [slug]);
|
|
219
|
+
|
|
220
|
+
if (loading) return <div>Loading...</div>;
|
|
221
|
+
if (!config) return <div>Page not found</div>;
|
|
222
|
+
|
|
223
|
+
// Extract theme and background
|
|
224
|
+
const { theme, background, sections } = config;
|
|
225
|
+
const themeColors = theme?.colors || {
|
|
226
|
+
primary: '#D8C4FF',
|
|
227
|
+
secondary: '#014421',
|
|
228
|
+
text: '#000000'
|
|
229
|
+
};
|
|
230
|
+
const themeTypography = theme?.typography || {
|
|
231
|
+
headingFont: 'Geist Sans',
|
|
232
|
+
bodyFont: 'Inter',
|
|
233
|
+
bodySize: '16px'
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
// Calculate background style
|
|
237
|
+
const backgroundStyle =
|
|
238
|
+
background?.type === 'gradient'
|
|
239
|
+
? { background: background.value }
|
|
240
|
+
: background?.type === 'image'
|
|
241
|
+
? {
|
|
242
|
+
backgroundImage: `url(${background.value})`,
|
|
243
|
+
backgroundSize: 'cover',
|
|
244
|
+
backgroundPosition: 'center'
|
|
245
|
+
}
|
|
246
|
+
: { backgroundColor: background?.value || '#FFFFFF' };
|
|
247
|
+
|
|
248
|
+
// Filter and sort sections
|
|
249
|
+
const enabledSections = sections
|
|
250
|
+
.filter(s => s.enabled)
|
|
251
|
+
.sort((a, b) => a.position - b.position);
|
|
252
|
+
|
|
253
|
+
return (
|
|
254
|
+
<div
|
|
255
|
+
className="min-h-screen"
|
|
256
|
+
style={{
|
|
257
|
+
...backgroundStyle,
|
|
258
|
+
color: themeColors.text,
|
|
259
|
+
fontFamily: themeTypography.bodyFont,
|
|
260
|
+
fontSize: themeTypography.bodySize
|
|
261
|
+
}}
|
|
262
|
+
>
|
|
263
|
+
{enabledSections.map((section) => {
|
|
264
|
+
const marginTop = section.settings?.marginTop || 0;
|
|
265
|
+
const marginBottom = section.settings?.marginBottom || 0;
|
|
266
|
+
const paddingTop = section.settings?.paddingTop || 0;
|
|
267
|
+
const paddingBottom = section.settings?.paddingBottom || 0;
|
|
268
|
+
|
|
269
|
+
return (
|
|
270
|
+
<div
|
|
271
|
+
key={section.id}
|
|
272
|
+
style={{
|
|
273
|
+
marginTop: `${marginTop}px`,
|
|
274
|
+
marginBottom: `${marginBottom}px`,
|
|
275
|
+
paddingTop: `${paddingTop}px`,
|
|
276
|
+
paddingBottom: `${paddingBottom}px`
|
|
277
|
+
}}
|
|
278
|
+
>
|
|
279
|
+
<SectionRenderer
|
|
280
|
+
section={section}
|
|
281
|
+
theme={theme}
|
|
282
|
+
enableInlineEditing={false}
|
|
283
|
+
/>
|
|
284
|
+
</div>
|
|
285
|
+
);
|
|
286
|
+
})}
|
|
287
|
+
</div>
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### Step 3: Create Booking Page
|
|
293
|
+
|
|
294
|
+
```typescript
|
|
295
|
+
// app/[slug]/book/page.tsx
|
|
296
|
+
'use client';
|
|
297
|
+
|
|
298
|
+
import { use, useEffect, useState } from 'react';
|
|
299
|
+
import { BookingFlow } from '@oviah/booking-components';
|
|
300
|
+
|
|
301
|
+
export default function BookingPage({ params }) {
|
|
302
|
+
const { slug } = use(params);
|
|
303
|
+
const [config, setConfig] = useState(null);
|
|
304
|
+
|
|
305
|
+
useEffect(() => {
|
|
306
|
+
async function loadConfig() {
|
|
307
|
+
const res = await fetch(`/api/config/${slug}`);
|
|
308
|
+
const data = await res.json();
|
|
309
|
+
setConfig(data.config);
|
|
310
|
+
}
|
|
311
|
+
loadConfig();
|
|
312
|
+
}, [slug]);
|
|
313
|
+
|
|
314
|
+
if (!config) return <div>Loading...</div>;
|
|
315
|
+
|
|
316
|
+
const themeColors = config.theme?.colors || {};
|
|
317
|
+
|
|
318
|
+
return (
|
|
319
|
+
<div className="container mx-auto px-4 py-16">
|
|
320
|
+
<BookingFlow
|
|
321
|
+
config={config.booking_flow || {}}
|
|
322
|
+
colors={themeColors}
|
|
323
|
+
services={[]} // Fetch from your API
|
|
324
|
+
dates={[]} // Fetch available dates
|
|
325
|
+
times={[]} // Fetch available times
|
|
326
|
+
addons={[]} // Fetch available addons
|
|
327
|
+
onComplete={(bookingData) => {
|
|
328
|
+
// Submit booking to your API
|
|
329
|
+
console.log('Booking submitted:', bookingData);
|
|
330
|
+
}}
|
|
331
|
+
/>
|
|
332
|
+
</div>
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
## 📋 Background Type Examples
|
|
338
|
+
|
|
339
|
+
### Solid Color
|
|
340
|
+
```json
|
|
341
|
+
{
|
|
342
|
+
"background": {
|
|
343
|
+
"type": "color",
|
|
344
|
+
"value": "#FFFFFF"
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### Gradient
|
|
350
|
+
```json
|
|
351
|
+
{
|
|
352
|
+
"background": {
|
|
353
|
+
"type": "gradient",
|
|
354
|
+
"value": "linear-gradient(135deg, #D8C4FF 0%, #014421 100%)"
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
### Image
|
|
360
|
+
```json
|
|
361
|
+
{
|
|
362
|
+
"background": {
|
|
363
|
+
"type": "image",
|
|
364
|
+
"value": "https://your-storage-url.com/backgrounds/image.jpg"
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
## 🎯 Key Points
|
|
370
|
+
|
|
371
|
+
1. **All styling is centralized** in the `config` object
|
|
372
|
+
2. **Background is separate** from theme (at `config.background`)
|
|
373
|
+
3. **Theme contains** colors and typography (at `config.theme`)
|
|
374
|
+
4. **Sections have individual spacing** (marginTop, marginBottom, paddingTop, paddingBottom)
|
|
375
|
+
5. **Typography cascades** from the root container to all sections
|
|
376
|
+
6. **Package components** automatically use theme colors and typography
|
|
377
|
+
|
|
378
|
+
## 🔧 Environment Variables Needed
|
|
379
|
+
|
|
380
|
+
```env
|
|
381
|
+
# Supabase
|
|
382
|
+
NEXT_PUBLIC_SUPABASE_URL=your_supabase_url
|
|
383
|
+
SUPABASE_SERVICE_ROLE_KEY=your_service_role_key
|
|
384
|
+
|
|
385
|
+
# App URL
|
|
386
|
+
NEXT_PUBLIC_APP_URL=https://your-domain.com
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
## 🚨 Common Gotchas
|
|
390
|
+
|
|
391
|
+
1. **Don't forget defaults**: Always provide fallback values for theme colors and typography
|
|
392
|
+
2. **Filter enabled sections**: Only render sections where `enabled: true`
|
|
393
|
+
3. **Sort by position**: Always sort sections by the `position` field
|
|
394
|
+
4. **Apply spacing correctly**: Margin/padding are stored in pixels (number), apply as `${value}px`
|
|
395
|
+
5. **Background style calculation**: Handle all three types (color, gradient, image) differently
|
|
396
|
+
|
|
397
|
+
## 📖 See Also
|
|
398
|
+
|
|
399
|
+
- [README.md](./README.md) - Package installation and usage
|
|
400
|
+
- [TENANT_SETUP.md](./TENANT_SETUP.md) - Step-by-step tenant setup
|
|
401
|
+
- [API.md](./docs/API.md) - Complete API reference
|
|
402
|
+
- [DATA_SHAPES.md](./docs/DATA_SHAPES.md) - Data structures reference
|