@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
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
export interface FeaturedLinkProps {
|
|
4
|
+
text?: string;
|
|
5
|
+
url?: string;
|
|
6
|
+
style?: 'gradient' | 'solid' | 'outline';
|
|
7
|
+
size?: 'small' | 'medium' | 'large';
|
|
8
|
+
colors: {
|
|
9
|
+
primary: string;
|
|
10
|
+
secondary: string;
|
|
11
|
+
};
|
|
12
|
+
enableInlineEditing?: boolean;
|
|
13
|
+
sectionId?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function FeaturedLink({
|
|
17
|
+
text,
|
|
18
|
+
url,
|
|
19
|
+
style = 'gradient',
|
|
20
|
+
size = 'large',
|
|
21
|
+
colors,
|
|
22
|
+
enableInlineEditing = false,
|
|
23
|
+
sectionId = ''
|
|
24
|
+
}: FeaturedLinkProps) {
|
|
25
|
+
const getClassName = () => {
|
|
26
|
+
let sizeClasses = '';
|
|
27
|
+
switch (size) {
|
|
28
|
+
case 'small':
|
|
29
|
+
sizeClasses = 'p-3 text-base';
|
|
30
|
+
break;
|
|
31
|
+
case 'medium':
|
|
32
|
+
sizeClasses = 'p-4 text-lg';
|
|
33
|
+
break;
|
|
34
|
+
case 'large':
|
|
35
|
+
sizeClasses = 'p-5 text-xl';
|
|
36
|
+
break;
|
|
37
|
+
default:
|
|
38
|
+
sizeClasses = 'p-5 text-xl';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
let styleClasses = '';
|
|
42
|
+
switch (style) {
|
|
43
|
+
case 'gradient':
|
|
44
|
+
styleClasses = 'rounded-full';
|
|
45
|
+
break;
|
|
46
|
+
case 'solid':
|
|
47
|
+
styleClasses = 'rounded-lg';
|
|
48
|
+
break;
|
|
49
|
+
case 'outline':
|
|
50
|
+
styleClasses = 'rounded-full border-2';
|
|
51
|
+
break;
|
|
52
|
+
default:
|
|
53
|
+
styleClasses = 'rounded-full';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return `block max-w-2xl mx-auto ${sizeClasses} ${styleClasses} text-center font-semibold transition-all hover:scale-105 hover:shadow-xl`;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const getStyle = () => {
|
|
60
|
+
switch (style) {
|
|
61
|
+
case 'gradient':
|
|
62
|
+
return {
|
|
63
|
+
background: `linear-gradient(135deg, ${colors.primary} 0%, ${colors.secondary} 100%)`,
|
|
64
|
+
color: '#FFFFFF'
|
|
65
|
+
};
|
|
66
|
+
case 'solid':
|
|
67
|
+
return {
|
|
68
|
+
backgroundColor: colors.primary,
|
|
69
|
+
color: '#FFFFFF'
|
|
70
|
+
};
|
|
71
|
+
case 'outline':
|
|
72
|
+
return {
|
|
73
|
+
backgroundColor: 'transparent',
|
|
74
|
+
borderColor: colors.primary,
|
|
75
|
+
color: colors.primary
|
|
76
|
+
};
|
|
77
|
+
default:
|
|
78
|
+
return {
|
|
79
|
+
backgroundColor: colors.primary,
|
|
80
|
+
color: '#FFFFFF'
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<div className="container mx-auto px-4">
|
|
87
|
+
<a
|
|
88
|
+
href={url || '/book'}
|
|
89
|
+
className={getClassName()}
|
|
90
|
+
style={getStyle()}
|
|
91
|
+
{...(enableInlineEditing && {
|
|
92
|
+
'data-editable': true,
|
|
93
|
+
'data-section-id': sectionId,
|
|
94
|
+
'data-field-path': 'settings.text'
|
|
95
|
+
})}
|
|
96
|
+
>
|
|
97
|
+
{text || '💅 Book Now'}
|
|
98
|
+
</a>
|
|
99
|
+
</div>
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export default FeaturedLink;
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
export interface CardLink {
|
|
4
|
+
id: string;
|
|
5
|
+
label: string;
|
|
6
|
+
url: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface FixedInfoCardProps {
|
|
10
|
+
name?: string;
|
|
11
|
+
subheading?: string;
|
|
12
|
+
bio?: string;
|
|
13
|
+
links?: CardLink[];
|
|
14
|
+
bookingButtonText?: string;
|
|
15
|
+
bookingButtonUrl?: string;
|
|
16
|
+
showBookingButton?: boolean;
|
|
17
|
+
position?: 'bottom-left' | 'bottom-right' | 'bottom-center';
|
|
18
|
+
cardWidth?: 'small' | 'medium' | 'large';
|
|
19
|
+
cardColor?: string;
|
|
20
|
+
colors: {
|
|
21
|
+
primary: string;
|
|
22
|
+
text: string;
|
|
23
|
+
buttonText?: string;
|
|
24
|
+
};
|
|
25
|
+
typography: {
|
|
26
|
+
headingFont?: string;
|
|
27
|
+
};
|
|
28
|
+
enableInlineEditing?: boolean;
|
|
29
|
+
sectionId?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function FixedInfoCard({
|
|
33
|
+
name,
|
|
34
|
+
subheading,
|
|
35
|
+
bio,
|
|
36
|
+
links = [],
|
|
37
|
+
bookingButtonText,
|
|
38
|
+
bookingButtonUrl,
|
|
39
|
+
showBookingButton = true,
|
|
40
|
+
position = 'bottom-left',
|
|
41
|
+
cardWidth = 'medium',
|
|
42
|
+
cardColor = '#FFFFFF',
|
|
43
|
+
colors,
|
|
44
|
+
typography,
|
|
45
|
+
enableInlineEditing = false,
|
|
46
|
+
sectionId = ''
|
|
47
|
+
}: FixedInfoCardProps) {
|
|
48
|
+
const hexToRgba = (hex: string, alpha: number) => {
|
|
49
|
+
const r = parseInt(hex.slice(1, 3), 16);
|
|
50
|
+
const g = parseInt(hex.slice(3, 5), 16);
|
|
51
|
+
const b = parseInt(hex.slice(5, 7), 16);
|
|
52
|
+
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const widthMap = {
|
|
56
|
+
small: '280px',
|
|
57
|
+
medium: '320px',
|
|
58
|
+
large: '400px'
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const positionClasses = {
|
|
62
|
+
'bottom-left': 'bottom-0 left-0',
|
|
63
|
+
'bottom-right': 'bottom-0 right-0',
|
|
64
|
+
'bottom-center': 'bottom-0 left-1/2 -translate-x-1/2'
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const textAlignClass = position === 'bottom-left' ? 'text-left' : position === 'bottom-right' ? 'text-right' : 'text-center';
|
|
68
|
+
const subheadingAlign = position === 'bottom-left' ? 'justify-start' : position === 'bottom-right' ? 'justify-end' : 'justify-center';
|
|
69
|
+
|
|
70
|
+
const getRoundedClass = () => {
|
|
71
|
+
if (position === 'bottom-left') {
|
|
72
|
+
return 'rounded-tr-3xl rounded-tl-none';
|
|
73
|
+
} else if (position === 'bottom-right') {
|
|
74
|
+
return 'rounded-tl-3xl rounded-tr-none';
|
|
75
|
+
} else {
|
|
76
|
+
return 'rounded-t-3xl';
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<div
|
|
82
|
+
className={`fixed z-50 ${positionClasses[position]}`}
|
|
83
|
+
style={{ width: widthMap[cardWidth] }}
|
|
84
|
+
>
|
|
85
|
+
<div
|
|
86
|
+
className={`${getRoundedClass()} shadow-2xl border-t border-x`}
|
|
87
|
+
style={{
|
|
88
|
+
backgroundColor: hexToRgba(cardColor, 0.9),
|
|
89
|
+
borderColor: hexToRgba(cardColor, 0.3),
|
|
90
|
+
boxShadow: '0 -10px 40px rgba(0, 0, 0, 0.1)'
|
|
91
|
+
}}
|
|
92
|
+
>
|
|
93
|
+
<div className={`px-8 pt-8 pb-3 ${textAlignClass}`}>
|
|
94
|
+
<h3
|
|
95
|
+
className="text-2xl font-bold"
|
|
96
|
+
style={{
|
|
97
|
+
fontFamily: typography.headingFont,
|
|
98
|
+
color: colors.text
|
|
99
|
+
}}
|
|
100
|
+
{...(enableInlineEditing && {
|
|
101
|
+
'data-editable': true,
|
|
102
|
+
'data-section-id': sectionId,
|
|
103
|
+
'data-field-path': 'settings.name'
|
|
104
|
+
})}
|
|
105
|
+
>
|
|
106
|
+
{name || 'Your Name'}
|
|
107
|
+
</h3>
|
|
108
|
+
</div>
|
|
109
|
+
|
|
110
|
+
<div className="mb-6">
|
|
111
|
+
{subheading ? (
|
|
112
|
+
<div className={`flex items-center gap-3 px-8 ${subheadingAlign}`}>
|
|
113
|
+
<span
|
|
114
|
+
className="text-xs font-medium whitespace-nowrap"
|
|
115
|
+
style={{ color: colors.primary }}
|
|
116
|
+
{...(enableInlineEditing && {
|
|
117
|
+
'data-editable': true,
|
|
118
|
+
'data-section-id': sectionId,
|
|
119
|
+
'data-field-path': 'settings.subheading'
|
|
120
|
+
})}
|
|
121
|
+
>
|
|
122
|
+
{subheading}
|
|
123
|
+
</span>
|
|
124
|
+
<div className="flex-1 h-px" style={{ backgroundColor: colors.primary, opacity: 0.3 }} />
|
|
125
|
+
</div>
|
|
126
|
+
) : (
|
|
127
|
+
<div className="w-full h-px" style={{ backgroundColor: colors.primary, opacity: 0.3 }} />
|
|
128
|
+
)}
|
|
129
|
+
</div>
|
|
130
|
+
|
|
131
|
+
<div className={`px-8 pb-8 ${textAlignClass}`}>
|
|
132
|
+
<p
|
|
133
|
+
className="text-sm opacity-80 mb-4"
|
|
134
|
+
style={{ color: colors.text }}
|
|
135
|
+
{...(enableInlineEditing && {
|
|
136
|
+
'data-editable': true,
|
|
137
|
+
'data-section-id': sectionId,
|
|
138
|
+
'data-field-path': 'settings.bio'
|
|
139
|
+
})}
|
|
140
|
+
>
|
|
141
|
+
{bio || 'Transforming beauty, one client at a time ✨'}
|
|
142
|
+
</p>
|
|
143
|
+
|
|
144
|
+
{links.length > 0 && (
|
|
145
|
+
<div className="space-y-2 mb-4">
|
|
146
|
+
{links.map((link, index) => (
|
|
147
|
+
<a
|
|
148
|
+
key={link.id}
|
|
149
|
+
href={link.url}
|
|
150
|
+
target="_blank"
|
|
151
|
+
rel="noopener noreferrer"
|
|
152
|
+
className="block text-sm font-medium hover:opacity-70 transition-opacity"
|
|
153
|
+
style={{ color: colors.primary }}
|
|
154
|
+
{...(enableInlineEditing && {
|
|
155
|
+
'data-editable': true,
|
|
156
|
+
'data-section-id': sectionId,
|
|
157
|
+
'data-field-path': `settings.links.${index}.label`
|
|
158
|
+
})}
|
|
159
|
+
>
|
|
160
|
+
{link.label}
|
|
161
|
+
</a>
|
|
162
|
+
))}
|
|
163
|
+
</div>
|
|
164
|
+
)}
|
|
165
|
+
|
|
166
|
+
{showBookingButton && (
|
|
167
|
+
<a
|
|
168
|
+
href={bookingButtonUrl || '/book'}
|
|
169
|
+
className="block w-full py-3 px-4 rounded-xl text-center font-semibold transition-all hover:scale-105 hover:shadow-lg"
|
|
170
|
+
style={{
|
|
171
|
+
backgroundColor: colors.primary,
|
|
172
|
+
color: colors.buttonText || '#FFFFFF'
|
|
173
|
+
}}
|
|
174
|
+
{...(enableInlineEditing && {
|
|
175
|
+
'data-editable': true,
|
|
176
|
+
'data-section-id': sectionId,
|
|
177
|
+
'data-field-path': 'settings.bookingButtonText'
|
|
178
|
+
})}
|
|
179
|
+
>
|
|
180
|
+
{bookingButtonText || 'Book Appointment'}
|
|
181
|
+
</a>
|
|
182
|
+
)}
|
|
183
|
+
</div>
|
|
184
|
+
</div>
|
|
185
|
+
</div>
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export default FixedInfoCard;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
export interface GalleryImage {
|
|
4
|
+
id: string | number;
|
|
5
|
+
url?: string;
|
|
6
|
+
alt?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface GalleryProps {
|
|
10
|
+
title?: string;
|
|
11
|
+
images?: GalleryImage[];
|
|
12
|
+
columns?: 2 | 3 | 4;
|
|
13
|
+
colors: {
|
|
14
|
+
primary: string;
|
|
15
|
+
text: string;
|
|
16
|
+
};
|
|
17
|
+
typography: {
|
|
18
|
+
headingFont?: string;
|
|
19
|
+
};
|
|
20
|
+
enableInlineEditing?: boolean;
|
|
21
|
+
sectionId?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function Gallery({
|
|
25
|
+
title,
|
|
26
|
+
images = [],
|
|
27
|
+
columns = 3,
|
|
28
|
+
colors,
|
|
29
|
+
typography,
|
|
30
|
+
enableInlineEditing = false,
|
|
31
|
+
sectionId = ''
|
|
32
|
+
}: GalleryProps) {
|
|
33
|
+
const galleryGridMap = {
|
|
34
|
+
2: 'grid-cols-2',
|
|
35
|
+
3: 'grid-cols-2 md:grid-cols-3',
|
|
36
|
+
4: 'grid-cols-2 md:grid-cols-4'
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const imagesToShow = images.length > 0 ? images : [1, 2, 3, 4, 5, 6].map(i => ({ id: i, url: undefined }));
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<section className="py-16 px-6">
|
|
43
|
+
<div className="max-w-7xl mx-auto">
|
|
44
|
+
<h2
|
|
45
|
+
className="text-3xl md:text-4xl font-bold text-center mb-12"
|
|
46
|
+
style={{ fontFamily: typography.headingFont, color: colors.text }}
|
|
47
|
+
{...(enableInlineEditing && {
|
|
48
|
+
'data-editable': true,
|
|
49
|
+
'data-section-id': sectionId,
|
|
50
|
+
'data-field-path': 'settings.title'
|
|
51
|
+
})}
|
|
52
|
+
>
|
|
53
|
+
{title || 'Gallery'}
|
|
54
|
+
</h2>
|
|
55
|
+
<div className={`grid ${galleryGridMap[columns]} gap-4`}>
|
|
56
|
+
{imagesToShow.map((img, i) => (
|
|
57
|
+
<div
|
|
58
|
+
key={img.id || i}
|
|
59
|
+
className="relative aspect-square overflow-hidden rounded-lg group"
|
|
60
|
+
>
|
|
61
|
+
{img.url ? (
|
|
62
|
+
<img
|
|
63
|
+
src={img.url}
|
|
64
|
+
alt={img.alt || ''}
|
|
65
|
+
className="w-full h-full object-cover transition-transform group-hover:scale-110"
|
|
66
|
+
/>
|
|
67
|
+
) : (
|
|
68
|
+
<div
|
|
69
|
+
className="w-full h-full flex items-center justify-center transition-transform group-hover:scale-110"
|
|
70
|
+
style={{ backgroundColor: colors.primary, opacity: 0.2 }}
|
|
71
|
+
>
|
|
72
|
+
<span className="text-sm opacity-50">Add image</span>
|
|
73
|
+
</div>
|
|
74
|
+
)}
|
|
75
|
+
</div>
|
|
76
|
+
))}
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
</section>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export default Gallery;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
export interface HeaderProps {
|
|
4
|
+
name?: string;
|
|
5
|
+
title?: string;
|
|
6
|
+
bio?: string;
|
|
7
|
+
showName?: boolean;
|
|
8
|
+
showTitle?: boolean;
|
|
9
|
+
showBio?: boolean;
|
|
10
|
+
colors: {
|
|
11
|
+
text: string;
|
|
12
|
+
};
|
|
13
|
+
typography: {
|
|
14
|
+
headingFont?: string;
|
|
15
|
+
};
|
|
16
|
+
enableInlineEditing?: boolean;
|
|
17
|
+
sectionId?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function Header({
|
|
21
|
+
name,
|
|
22
|
+
title,
|
|
23
|
+
bio,
|
|
24
|
+
showName = true,
|
|
25
|
+
showTitle = true,
|
|
26
|
+
showBio = true,
|
|
27
|
+
colors,
|
|
28
|
+
typography,
|
|
29
|
+
enableInlineEditing = false,
|
|
30
|
+
sectionId = ''
|
|
31
|
+
}: HeaderProps) {
|
|
32
|
+
return (
|
|
33
|
+
<div className="container mx-auto px-4 text-center">
|
|
34
|
+
{showName && (
|
|
35
|
+
<h1
|
|
36
|
+
className="text-4xl font-bold mb-3"
|
|
37
|
+
style={{
|
|
38
|
+
fontFamily: typography.headingFont,
|
|
39
|
+
color: colors.text
|
|
40
|
+
}}
|
|
41
|
+
{...(enableInlineEditing && {
|
|
42
|
+
'data-editable': true,
|
|
43
|
+
'data-section-id': sectionId,
|
|
44
|
+
'data-field-path': 'settings.name'
|
|
45
|
+
})}
|
|
46
|
+
>
|
|
47
|
+
{name || 'Your Name'}
|
|
48
|
+
</h1>
|
|
49
|
+
)}
|
|
50
|
+
{showTitle && (
|
|
51
|
+
<p
|
|
52
|
+
className="text-lg opacity-80 mb-2"
|
|
53
|
+
{...(enableInlineEditing && {
|
|
54
|
+
'data-editable': true,
|
|
55
|
+
'data-section-id': sectionId,
|
|
56
|
+
'data-field-path': 'settings.title'
|
|
57
|
+
})}
|
|
58
|
+
>
|
|
59
|
+
{title || 'Beauty Professional'}
|
|
60
|
+
</p>
|
|
61
|
+
)}
|
|
62
|
+
{showBio && (
|
|
63
|
+
<p
|
|
64
|
+
className="text-base opacity-70 max-w-md mx-auto"
|
|
65
|
+
{...(enableInlineEditing && {
|
|
66
|
+
'data-editable': true,
|
|
67
|
+
'data-section-id': sectionId,
|
|
68
|
+
'data-field-path': 'settings.bio'
|
|
69
|
+
})}
|
|
70
|
+
>
|
|
71
|
+
{bio || 'Transform your look ✨'}
|
|
72
|
+
</p>
|
|
73
|
+
)}
|
|
74
|
+
</div>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export default Header;
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
export interface HeroProps {
|
|
4
|
+
headline?: string;
|
|
5
|
+
subheadline?: string;
|
|
6
|
+
heroImage?: string;
|
|
7
|
+
ctaButton?: {
|
|
8
|
+
text: string;
|
|
9
|
+
url: string;
|
|
10
|
+
};
|
|
11
|
+
layout?: 'split' | 'full';
|
|
12
|
+
imagePosition?: 'left' | 'right';
|
|
13
|
+
contentDisplay?: 'side' | 'overlay';
|
|
14
|
+
colors: {
|
|
15
|
+
primary: string;
|
|
16
|
+
text: string;
|
|
17
|
+
};
|
|
18
|
+
typography: {
|
|
19
|
+
headingFont?: string;
|
|
20
|
+
};
|
|
21
|
+
enableInlineEditing?: boolean;
|
|
22
|
+
sectionId?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function Hero({
|
|
26
|
+
headline,
|
|
27
|
+
subheadline,
|
|
28
|
+
heroImage,
|
|
29
|
+
ctaButton,
|
|
30
|
+
layout = 'split',
|
|
31
|
+
imagePosition = 'right',
|
|
32
|
+
contentDisplay = 'side',
|
|
33
|
+
colors,
|
|
34
|
+
typography,
|
|
35
|
+
enableInlineEditing = false,
|
|
36
|
+
sectionId = ''
|
|
37
|
+
}: HeroProps) {
|
|
38
|
+
const heroContent = (
|
|
39
|
+
<div className={layout === 'full' ? 'text-center max-w-4xl z-10' : 'text-left'}>
|
|
40
|
+
<h1
|
|
41
|
+
className={layout === 'full' ? 'text-5xl md:text-6xl font-bold mb-6' : 'text-5xl font-bold mb-4'}
|
|
42
|
+
style={{
|
|
43
|
+
fontFamily: typography.headingFont,
|
|
44
|
+
color: layout === 'full' && heroImage ? '#FFFFFF' : colors.text,
|
|
45
|
+
textShadow: layout === 'full' && heroImage ? '0 2px 10px rgba(0,0,0,0.3)' : 'none'
|
|
46
|
+
}}
|
|
47
|
+
{...(enableInlineEditing && {
|
|
48
|
+
'data-editable': true,
|
|
49
|
+
'data-section-id': sectionId,
|
|
50
|
+
'data-field-path': 'settings.headline'
|
|
51
|
+
})}
|
|
52
|
+
>
|
|
53
|
+
{headline || 'Welcome'}
|
|
54
|
+
</h1>
|
|
55
|
+
{subheadline && (
|
|
56
|
+
<p
|
|
57
|
+
className={layout === 'full' ? 'text-xl md:text-2xl opacity-90 mb-12' : 'text-xl opacity-80 mb-8'}
|
|
58
|
+
style={{
|
|
59
|
+
color: layout === 'full' && heroImage ? '#FFFFFF' : colors.text,
|
|
60
|
+
textShadow: layout === 'full' && heroImage ? '0 2px 10px rgba(0,0,0,0.3)' : 'none'
|
|
61
|
+
}}
|
|
62
|
+
{...(enableInlineEditing && {
|
|
63
|
+
'data-editable': true,
|
|
64
|
+
'data-section-id': sectionId,
|
|
65
|
+
'data-field-path': 'settings.subheadline'
|
|
66
|
+
})}
|
|
67
|
+
>
|
|
68
|
+
{subheadline}
|
|
69
|
+
</p>
|
|
70
|
+
)}
|
|
71
|
+
{ctaButton && (
|
|
72
|
+
<a
|
|
73
|
+
href={ctaButton.url || '#'}
|
|
74
|
+
className="inline-block px-8 py-4 rounded-lg font-semibold text-lg transition-all hover:scale-105 shadow-lg"
|
|
75
|
+
style={{
|
|
76
|
+
backgroundColor: colors.primary,
|
|
77
|
+
color: '#FFFFFF'
|
|
78
|
+
}}
|
|
79
|
+
{...(enableInlineEditing && {
|
|
80
|
+
'data-editable': true,
|
|
81
|
+
'data-section-id': sectionId,
|
|
82
|
+
'data-field-path': 'settings.ctaButton.text'
|
|
83
|
+
})}
|
|
84
|
+
>
|
|
85
|
+
{ctaButton.text || 'Get Started'}
|
|
86
|
+
</a>
|
|
87
|
+
)}
|
|
88
|
+
</div>
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
const heroImageElement = heroImage ? (
|
|
92
|
+
<div className="w-full h-full rounded-2xl overflow-hidden">
|
|
93
|
+
<img
|
|
94
|
+
src={heroImage}
|
|
95
|
+
alt={headline || 'Hero'}
|
|
96
|
+
className="w-full h-full object-cover"
|
|
97
|
+
/>
|
|
98
|
+
</div>
|
|
99
|
+
) : (
|
|
100
|
+
<div
|
|
101
|
+
className="w-full h-full rounded-2xl flex items-center justify-center"
|
|
102
|
+
style={{ backgroundColor: colors.primary, opacity: 0.1 }}
|
|
103
|
+
>
|
|
104
|
+
<span className="text-sm opacity-50">Add hero image</span>
|
|
105
|
+
</div>
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
if (layout === 'full') {
|
|
109
|
+
return (
|
|
110
|
+
<div
|
|
111
|
+
className="relative py-20 md:py-24 px-4 flex items-center justify-center"
|
|
112
|
+
style={{
|
|
113
|
+
backgroundImage: heroImage ? `url(${heroImage})` : 'none',
|
|
114
|
+
backgroundSize: 'cover',
|
|
115
|
+
backgroundPosition: 'center',
|
|
116
|
+
backgroundColor: !heroImage ? colors.primary : 'transparent',
|
|
117
|
+
opacity: !heroImage ? 0.1 : 1
|
|
118
|
+
}}
|
|
119
|
+
>
|
|
120
|
+
{heroImage && (
|
|
121
|
+
<div className="absolute inset-0 bg-black/40" />
|
|
122
|
+
)}
|
|
123
|
+
{heroContent}
|
|
124
|
+
</div>
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (contentDisplay === 'overlay') {
|
|
129
|
+
return (
|
|
130
|
+
<div className="container mx-auto px-4 py-16">
|
|
131
|
+
<div className="relative max-w-6xl mx-auto rounded-2xl overflow-hidden" style={{ height: '500px' }}>
|
|
132
|
+
{heroImage ? (
|
|
133
|
+
<img
|
|
134
|
+
src={heroImage}
|
|
135
|
+
alt={headline || 'Hero'}
|
|
136
|
+
className="absolute inset-0 w-full h-full object-cover"
|
|
137
|
+
/>
|
|
138
|
+
) : (
|
|
139
|
+
<div
|
|
140
|
+
className="absolute inset-0"
|
|
141
|
+
style={{ backgroundColor: colors.primary, opacity: 0.1 }}
|
|
142
|
+
/>
|
|
143
|
+
)}
|
|
144
|
+
<div className="absolute inset-0 bg-gradient-to-r from-black/60 to-black/20" />
|
|
145
|
+
<div
|
|
146
|
+
className={`relative h-full flex items-center ${imagePosition === 'left' ? 'justify-end' : 'justify-start'} px-12`}
|
|
147
|
+
>
|
|
148
|
+
<div className="max-w-lg" style={{ color: '#FFFFFF', textShadow: '0 2px 10px rgba(0,0,0,0.3)' }}>
|
|
149
|
+
{heroContent}
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
</div>
|
|
153
|
+
</div>
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return (
|
|
158
|
+
<div className="container mx-auto px-4 py-16">
|
|
159
|
+
<div className="grid md:grid-cols-2 gap-12 items-center max-w-6xl mx-auto">
|
|
160
|
+
{imagePosition === 'left' && (
|
|
161
|
+
<div className="aspect-[4/3]">
|
|
162
|
+
{heroImageElement}
|
|
163
|
+
</div>
|
|
164
|
+
)}
|
|
165
|
+
<div>
|
|
166
|
+
{heroContent}
|
|
167
|
+
</div>
|
|
168
|
+
{imagePosition === 'right' && (
|
|
169
|
+
<div className="aspect-[4/3]">
|
|
170
|
+
{heroImageElement}
|
|
171
|
+
</div>
|
|
172
|
+
)}
|
|
173
|
+
</div>
|
|
174
|
+
</div>
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export default Hero;
|