@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.
Files changed (83) hide show
  1. package/README.md +319 -0
  2. package/TENANT_DATA_INTEGRATION.md +402 -0
  3. package/TENANT_SETUP.md +316 -0
  4. package/components/BookingFlow/BookingFlow.tsx +790 -0
  5. package/components/BookingFlow/index.ts +5 -0
  6. package/components/BookingFlow/steps/AddonsSelection.tsx +118 -0
  7. package/components/BookingFlow/steps/Confirmation.tsx +185 -0
  8. package/components/BookingFlow/steps/ContactForm.tsx +292 -0
  9. package/components/BookingFlow/steps/CycleAwareDateSelection.tsx +277 -0
  10. package/components/BookingFlow/steps/DateSelection.tsx +473 -0
  11. package/components/BookingFlow/steps/ServiceSelection.tsx +315 -0
  12. package/components/BookingFlow/steps/TimeSelection.tsx +230 -0
  13. package/components/BookingFlow/steps/index.ts +10 -0
  14. package/components/BottomSheet/index.tsx +120 -0
  15. package/components/Forms/FormBlock.tsx +283 -0
  16. package/components/Forms/FormField.tsx +385 -0
  17. package/components/Forms/FormRenderer.tsx +216 -0
  18. package/components/Forms/FormValidation.ts +122 -0
  19. package/components/Forms/index.ts +4 -0
  20. package/components/HoldTimer/HoldTimer.tsx +266 -0
  21. package/components/HoldTimer/index.ts +2 -0
  22. package/components/SectionRenderer.tsx +558 -0
  23. package/components/Sections/About.tsx +145 -0
  24. package/components/Sections/BeforeAfter.tsx +81 -0
  25. package/components/Sections/BookingSection.tsx +76 -0
  26. package/components/Sections/Contact.tsx +103 -0
  27. package/components/Sections/FAQSection.tsx +239 -0
  28. package/components/Sections/FeatureContent.tsx +113 -0
  29. package/components/Sections/FeaturedLink.tsx +103 -0
  30. package/components/Sections/FixedInfoCard.tsx +189 -0
  31. package/components/Sections/Gallery.tsx +83 -0
  32. package/components/Sections/Header.tsx +78 -0
  33. package/components/Sections/Hero.tsx +178 -0
  34. package/components/Sections/ImageSection.tsx +147 -0
  35. package/components/Sections/InstagramFeed.tsx +38 -0
  36. package/components/Sections/LinkList.tsx +76 -0
  37. package/components/Sections/LocationMap.tsx +202 -0
  38. package/components/Sections/Logo.tsx +61 -0
  39. package/components/Sections/MinimalFooter.tsx +78 -0
  40. package/components/Sections/MinimalHeader.tsx +81 -0
  41. package/components/Sections/MinimalNavigation.tsx +63 -0
  42. package/components/Sections/Navbar.tsx +258 -0
  43. package/components/Sections/PricingTable.tsx +106 -0
  44. package/components/Sections/ScrollingTextDivider.tsx +138 -0
  45. package/components/Sections/ScrollingTextDivider.tsx.bak +138 -0
  46. package/components/Sections/ServicesPreview.tsx +129 -0
  47. package/components/Sections/SocialBar.tsx +177 -0
  48. package/components/Sections/Team.tsx +80 -0
  49. package/components/Sections/Testimonials.tsx +92 -0
  50. package/components/Sections/TextSection.tsx +116 -0
  51. package/components/Sections/VideoSection.tsx +178 -0
  52. package/components/Sections/index.ts +57 -0
  53. package/components/index.ts +21 -0
  54. package/dist/index-DAai7Glf.d.mts +474 -0
  55. package/dist/index-DAai7Glf.d.ts +474 -0
  56. package/dist/index.d.mts +1075 -0
  57. package/dist/index.d.ts +1075 -0
  58. package/dist/index.js +22 -0
  59. package/dist/index.js.map +1 -0
  60. package/dist/index.mjs +22 -0
  61. package/dist/index.mjs.map +1 -0
  62. package/dist/styles/index.d.mts +1 -0
  63. package/dist/styles/index.d.ts +1 -0
  64. package/dist/styles/index.js +2 -0
  65. package/dist/styles/index.js.map +1 -0
  66. package/dist/styles/index.mjs +2 -0
  67. package/dist/styles/index.mjs.map +1 -0
  68. package/docs/API.md +849 -0
  69. package/docs/CALLBACKS.md +760 -0
  70. package/docs/COMPLETE_SESSION_SUMMARY.md +404 -0
  71. package/docs/DATA_SHAPES.md +684 -0
  72. package/docs/MIGRATION.md +662 -0
  73. package/docs/PAYMENT_INTEGRATION.md +766 -0
  74. package/docs/SESSION_SUMMARY.md +185 -0
  75. package/docs/STYLING.md +735 -0
  76. package/index.ts +4 -0
  77. package/lib/storage.ts +239 -0
  78. package/package.json +59 -0
  79. package/styles/animations.ts +210 -0
  80. package/styles/index.ts +1 -0
  81. package/tsconfig.json +32 -0
  82. package/tsup.config.ts +13 -0
  83. package/types/index.ts +369 -0
@@ -0,0 +1,147 @@
1
+ 'use client';
2
+
3
+ export interface ImageSectionProps {
4
+ imageUrl: string;
5
+ altText?: string;
6
+ caption?: string;
7
+ maxWidth?: 'full' | 'large' | 'medium' | 'small';
8
+ alignment?: 'left' | 'center' | 'right';
9
+ aspectRatio?: 'auto' | '16:9' | '4:3' | '1:1' | '3:2';
10
+ borderRadius?: number;
11
+ linkUrl?: string;
12
+ openInNewTab?: boolean;
13
+ colors: {
14
+ primary: string;
15
+ text: string;
16
+ };
17
+ typography?: {
18
+ bodyFont?: string;
19
+ };
20
+ enableInlineEditing?: boolean;
21
+ sectionId?: string;
22
+ }
23
+
24
+ export function ImageSection({
25
+ imageUrl,
26
+ altText = '',
27
+ caption,
28
+ maxWidth = 'large',
29
+ alignment = 'center',
30
+ aspectRatio = 'auto',
31
+ borderRadius = 12,
32
+ linkUrl,
33
+ openInNewTab = true,
34
+ colors,
35
+ typography,
36
+ enableInlineEditing = false,
37
+ sectionId = ''
38
+ }: ImageSectionProps) {
39
+ // Max width classes
40
+ const maxWidthMap = {
41
+ full: 'max-w-full',
42
+ large: 'max-w-6xl',
43
+ medium: 'max-w-4xl',
44
+ small: 'max-w-2xl'
45
+ };
46
+
47
+ // Aspect ratio padding hack
48
+ const aspectRatioMap = {
49
+ auto: null,
50
+ '16:9': '56.25%',
51
+ '4:3': '75%',
52
+ '1:1': '100%',
53
+ '3:2': '66.67%'
54
+ };
55
+
56
+ const alignmentClass = alignment === 'center' ? 'mx-auto' : alignment === 'right' ? 'ml-auto' : 'mr-auto';
57
+
58
+ const imageContent = (
59
+ <div className={`${maxWidthMap[maxWidth]} ${alignmentClass}`}>
60
+ <div
61
+ className="relative w-full overflow-hidden"
62
+ style={{
63
+ borderRadius: `${borderRadius}px`,
64
+ ...(aspectRatioMap[aspectRatio] && { paddingBottom: aspectRatioMap[aspectRatio] })
65
+ }}
66
+ >
67
+ {imageUrl ? (
68
+ <img
69
+ src={imageUrl}
70
+ alt={altText || 'Image'}
71
+ className={aspectRatio === 'auto' ? 'w-full h-auto' : 'absolute top-0 left-0 w-full h-full object-cover'}
72
+ style={{
73
+ borderRadius: `${borderRadius}px`
74
+ }}
75
+ />
76
+ ) : (
77
+ // Placeholder when no image
78
+ <div
79
+ className="flex items-center justify-center bg-gray-100"
80
+ style={{
81
+ backgroundColor: `${colors.primary}10`,
82
+ borderRadius: `${borderRadius}px`,
83
+ minHeight: aspectRatio === 'auto' ? '300px' : undefined
84
+ }}
85
+ >
86
+ <div className="text-center px-4">
87
+ <svg
88
+ className="w-16 h-16 mx-auto mb-4"
89
+ fill="none"
90
+ stroke={colors.primary}
91
+ viewBox="0 0 24 24"
92
+ >
93
+ <path
94
+ strokeLinecap="round"
95
+ strokeLinejoin="round"
96
+ strokeWidth={2}
97
+ d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"
98
+ />
99
+ </svg>
100
+ <p style={{ color: colors.text, opacity: 0.6 }}>
101
+ Add an image URL to display
102
+ </p>
103
+ </div>
104
+ </div>
105
+ )}
106
+ </div>
107
+
108
+ {/* Caption */}
109
+ {caption && imageUrl && (
110
+ <p
111
+ className="mt-3 text-sm text-center"
112
+ style={{
113
+ color: colors.text,
114
+ opacity: 0.7,
115
+ fontFamily: typography?.bodyFont || 'inherit'
116
+ }}
117
+ {...(enableInlineEditing && {
118
+ 'data-editable': true,
119
+ 'data-section-id': sectionId,
120
+ 'data-field-path': 'settings.caption'
121
+ })}
122
+ >
123
+ {caption}
124
+ </p>
125
+ )}
126
+ </div>
127
+ );
128
+
129
+ return (
130
+ <div className={maxWidth === 'full' ? 'w-full' : 'container mx-auto px-4'}>
131
+ {linkUrl && imageUrl ? (
132
+ <a
133
+ href={linkUrl}
134
+ target={openInNewTab ? '_blank' : '_self'}
135
+ rel={openInNewTab ? 'noopener noreferrer' : undefined}
136
+ className="block"
137
+ >
138
+ {imageContent}
139
+ </a>
140
+ ) : (
141
+ imageContent
142
+ )}
143
+ </div>
144
+ );
145
+ }
146
+
147
+ export default ImageSection;
@@ -0,0 +1,38 @@
1
+ 'use client';
2
+
3
+ export interface InstagramFeedProps {
4
+ username?: string;
5
+ colors: {
6
+ primary: string;
7
+ };
8
+ enableInlineEditing?: boolean;
9
+ sectionId?: string;
10
+ }
11
+
12
+ export function InstagramFeed({
13
+ username,
14
+ colors,
15
+ enableInlineEditing = false,
16
+ sectionId = ''
17
+ }: InstagramFeedProps) {
18
+ return (
19
+ <div className="container mx-auto px-4 py-8">
20
+ <div className="max-w-4xl mx-auto">
21
+ <p className="text-center text-sm opacity-60 mb-4">
22
+ Instagram Feed: {username || '@handle'}
23
+ </p>
24
+ <div className="grid grid-cols-3 gap-1">
25
+ {[1, 2, 3, 4, 5, 6].map((i) => (
26
+ <div
27
+ key={i}
28
+ className="aspect-square rounded"
29
+ style={{ backgroundColor: colors.primary, opacity: 0.2 }}
30
+ />
31
+ ))}
32
+ </div>
33
+ </div>
34
+ </div>
35
+ );
36
+ }
37
+
38
+ export default InstagramFeed;
@@ -0,0 +1,76 @@
1
+ 'use client';
2
+
3
+ export interface Link {
4
+ id: string;
5
+ label: string;
6
+ url: string;
7
+ }
8
+
9
+ export interface LinkListProps {
10
+ links: Link[];
11
+ style?: 'pill' | 'square' | 'outline';
12
+ colors: {
13
+ primary: string;
14
+ linkBackground?: string;
15
+ linkText?: string;
16
+ };
17
+ enableInlineEditing?: boolean;
18
+ sectionId?: string;
19
+ }
20
+
21
+ export function LinkList({ links, style = 'pill', colors, enableInlineEditing = false, sectionId = '' }: LinkListProps) {
22
+ const getLinkClassName = () => {
23
+ const baseClasses = "block w-full p-4 text-center font-medium transition-all hover:scale-105";
24
+
25
+ switch (style) {
26
+ case 'pill':
27
+ return `${baseClasses} rounded-full`;
28
+ case 'square':
29
+ return `${baseClasses} rounded-md`;
30
+ case 'outline':
31
+ return `${baseClasses} rounded-full border-2`;
32
+ default:
33
+ return `${baseClasses} rounded-lg`;
34
+ }
35
+ };
36
+
37
+ const getLinkStyle = (isOutline: boolean) => {
38
+ if (isOutline) {
39
+ return {
40
+ backgroundColor: 'transparent',
41
+ borderColor: colors.primary,
42
+ color: colors.primary
43
+ };
44
+ }
45
+ return {
46
+ backgroundColor: colors.linkBackground || colors.primary,
47
+ color: colors.linkText || '#FFFFFF'
48
+ };
49
+ };
50
+
51
+ return (
52
+ <div className="container mx-auto px-4">
53
+ <div className="max-w-2xl mx-auto space-y-3">
54
+ {links.map((link, index) => (
55
+ <a
56
+ key={link.id}
57
+ href={link.url}
58
+ target="_blank"
59
+ rel="noopener noreferrer"
60
+ className={getLinkClassName()}
61
+ style={getLinkStyle(style === 'outline')}
62
+ {...(enableInlineEditing && {
63
+ 'data-editable': true,
64
+ 'data-section-id': sectionId,
65
+ 'data-field-path': `settings.links.${index}.label`
66
+ })}
67
+ >
68
+ {link.label}
69
+ </a>
70
+ ))}
71
+ </div>
72
+ </div>
73
+ );
74
+ }
75
+
76
+ export default LinkList;
@@ -0,0 +1,202 @@
1
+ 'use client';
2
+
3
+ export interface LocationMapProps {
4
+ address: string;
5
+ displayType?: 'map-only' | 'address-only' | 'both';
6
+ mapHeight?: 'small' | 'medium' | 'large';
7
+ mapStyle?: 'roadmap' | 'satellite';
8
+ zoom?: number;
9
+ showMarker?: boolean;
10
+ borderRadius?: number;
11
+ directionsButtonText?: string;
12
+ colors: {
13
+ primary: string;
14
+ text: string;
15
+ };
16
+ typography?: {
17
+ headingFont?: string;
18
+ bodyFont?: string;
19
+ };
20
+ enableInlineEditing?: boolean;
21
+ sectionId?: string;
22
+ }
23
+
24
+ export function LocationMap({
25
+ address,
26
+ displayType = 'both',
27
+ mapHeight = 'medium',
28
+ mapStyle = 'roadmap',
29
+ zoom = 15,
30
+ showMarker = true,
31
+ borderRadius = 12,
32
+ directionsButtonText = 'Get Directions',
33
+ colors,
34
+ typography,
35
+ enableInlineEditing = false,
36
+ sectionId = ''
37
+ }: LocationMapProps) {
38
+ // Map height mapping
39
+ const heightMap = {
40
+ small: 300,
41
+ medium: 400,
42
+ large: 500
43
+ };
44
+
45
+ const height = heightMap[mapHeight];
46
+
47
+ // Encode address for Google Maps
48
+ const encodedAddress = encodeURIComponent(address);
49
+ const directionsUrl = `https://www.google.com/maps/dir/?api=1&destination=${encodedAddress}`;
50
+
51
+ // Google Maps embed URL
52
+ // Support both Next.js (process.env.NEXT_PUBLIC_*) and Vite (import.meta.env.VITE_*)
53
+ const apiKey = (import.meta as any).env?.VITE_GOOGLE_MAPS_API_KEY ||
54
+ (typeof globalThis !== 'undefined' && (globalThis as any).process?.env?.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY) ||
55
+ '';
56
+ const mapEmbedUrl = `https://www.google.com/maps/embed/v1/place?key=${apiKey}&q=${encodedAddress}&zoom=${zoom}&maptype=${mapStyle}`;
57
+
58
+ return (
59
+ <div className="container mx-auto px-4">
60
+ <div className="max-w-4xl mx-auto">
61
+ {/* Map */}
62
+ {(displayType === 'map-only' || displayType === 'both') && address && (
63
+ <div
64
+ className="w-full overflow-hidden mb-6"
65
+ style={{
66
+ height: `${height}px`,
67
+ borderRadius: `${borderRadius}px`
68
+ }}
69
+ >
70
+ <iframe
71
+ src={mapEmbedUrl}
72
+ className="w-full h-full"
73
+ style={{ border: 0 }}
74
+ allowFullScreen
75
+ loading="lazy"
76
+ referrerPolicy="no-referrer-when-downgrade"
77
+ title="Location Map"
78
+ />
79
+ </div>
80
+ )}
81
+
82
+ {/* Address & Directions */}
83
+ {(displayType === 'address-only' || displayType === 'both') && (
84
+ <div
85
+ className="p-6 rounded-lg border"
86
+ style={{
87
+ backgroundColor: `${colors.primary}10`,
88
+ borderColor: `${colors.primary}30`,
89
+ borderRadius: `${borderRadius}px`
90
+ }}
91
+ >
92
+ <div className="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
93
+ {/* Address */}
94
+ <div className="flex-1">
95
+ <div className="flex items-start gap-3">
96
+ <svg
97
+ className="w-5 h-5 mt-0.5 flex-shrink-0"
98
+ fill="none"
99
+ stroke={colors.primary}
100
+ viewBox="0 0 24 24"
101
+ >
102
+ <path
103
+ strokeLinecap="round"
104
+ strokeLinejoin="round"
105
+ strokeWidth={2}
106
+ d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"
107
+ />
108
+ <path
109
+ strokeLinecap="round"
110
+ strokeLinejoin="round"
111
+ strokeWidth={2}
112
+ d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"
113
+ />
114
+ </svg>
115
+ <div>
116
+ <p
117
+ className="font-medium mb-1"
118
+ style={{
119
+ color: colors.text,
120
+ fontFamily: typography?.bodyFont || 'inherit'
121
+ }}
122
+ {...(enableInlineEditing && {
123
+ 'data-editable': true,
124
+ 'data-section-id': sectionId,
125
+ 'data-field-path': 'settings.address'
126
+ })}
127
+ >
128
+ {address || 'Add your address'}
129
+ </p>
130
+ {address && (
131
+ <a
132
+ href={directionsUrl}
133
+ target="_blank"
134
+ rel="noopener noreferrer"
135
+ className="text-sm underline hover:no-underline"
136
+ style={{ color: colors.primary }}
137
+ >
138
+ View in Google Maps �
139
+ </a>
140
+ )}
141
+ </div>
142
+ </div>
143
+ </div>
144
+
145
+ {/* Get Directions Button */}
146
+ {address && (
147
+ <a
148
+ href={directionsUrl}
149
+ target="_blank"
150
+ rel="noopener noreferrer"
151
+ className="px-6 py-3 rounded-lg font-semibold transition-all hover:opacity-90 text-center"
152
+ style={{
153
+ backgroundColor: colors.primary,
154
+ color: '#FFFFFF'
155
+ }}
156
+ >
157
+ {directionsButtonText}
158
+ </a>
159
+ )}
160
+ </div>
161
+ </div>
162
+ )}
163
+
164
+ {/* Fallback when no address */}
165
+ {!address && (
166
+ <div
167
+ className="p-12 rounded-lg border-2 border-dashed text-center"
168
+ style={{
169
+ borderColor: `${colors.primary}30`,
170
+ borderRadius: `${borderRadius}px`
171
+ }}
172
+ >
173
+ <svg
174
+ className="w-12 h-12 mx-auto mb-4"
175
+ fill="none"
176
+ stroke={colors.primary}
177
+ viewBox="0 0 24 24"
178
+ >
179
+ <path
180
+ strokeLinecap="round"
181
+ strokeLinejoin="round"
182
+ strokeWidth={2}
183
+ d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"
184
+ />
185
+ <path
186
+ strokeLinecap="round"
187
+ strokeLinejoin="round"
188
+ strokeWidth={2}
189
+ d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"
190
+ />
191
+ </svg>
192
+ <p style={{ color: colors.text, opacity: 0.6 }}>
193
+ Add your business address to display a map
194
+ </p>
195
+ </div>
196
+ )}
197
+ </div>
198
+ </div>
199
+ );
200
+ }
201
+
202
+ export default LocationMap;
@@ -0,0 +1,61 @@
1
+ 'use client';
2
+
3
+ export interface LogoProps {
4
+ logoUrl?: string;
5
+ fallbackText?: string;
6
+ size?: 'small' | 'medium' | 'large';
7
+ alignment?: 'left' | 'center' | 'right';
8
+ colors: {
9
+ primary: string;
10
+ };
11
+ enableInlineEditing?: boolean;
12
+ sectionId?: string;
13
+ }
14
+
15
+ export function Logo({ logoUrl, fallbackText, size = 'medium', alignment = 'center', colors, enableInlineEditing = false, sectionId = '' }: LogoProps) {
16
+ const logoSizeMap = {
17
+ small: 80,
18
+ medium: 120,
19
+ large: 160
20
+ };
21
+ const logoSize = logoSizeMap[size];
22
+ const alignmentClass = alignment === 'center' ? 'justify-center' : alignment === 'right' ? 'justify-end' : 'justify-start';
23
+
24
+ return (
25
+ <div className={`container mx-auto px-4 flex ${alignmentClass}`}>
26
+ {logoUrl ? (
27
+ <img
28
+ src={logoUrl}
29
+ alt={fallbackText || 'Logo'}
30
+ className="object-contain"
31
+ style={{ height: `${logoSize}px`, width: 'auto' }}
32
+ />
33
+ ) : fallbackText ? (
34
+ <div
35
+ className="flex items-center justify-center rounded-full font-bold"
36
+ style={{
37
+ width: `${logoSize}px`,
38
+ height: `${logoSize}px`,
39
+ backgroundColor: colors.primary,
40
+ color: '#FFFFFF',
41
+ fontSize: `${logoSize * 0.4}px`
42
+ }}
43
+ >
44
+ {fallbackText.slice(0, 2).toUpperCase()}
45
+ </div>
46
+ ) : (
47
+ <div
48
+ className="rounded-full"
49
+ style={{
50
+ width: `${logoSize}px`,
51
+ height: `${logoSize}px`,
52
+ backgroundColor: colors.primary,
53
+ opacity: 0.2
54
+ }}
55
+ />
56
+ )}
57
+ </div>
58
+ );
59
+ }
60
+
61
+ export default Logo;
@@ -0,0 +1,78 @@
1
+ 'use client';
2
+
3
+ export interface MinimalFooterProps {
4
+ footerText?: string;
5
+ alignment?: 'left' | 'center' | 'right';
6
+ showSocial?: boolean;
7
+ showContact?: boolean;
8
+ showLinks?: boolean;
9
+ colors: {
10
+ primary: string;
11
+ text: string;
12
+ };
13
+ enableInlineEditing?: boolean;
14
+ sectionId?: string;
15
+ }
16
+
17
+ export function MinimalFooter({
18
+ footerText,
19
+ alignment = 'center',
20
+ showSocial = true,
21
+ showContact = true,
22
+ showLinks = true,
23
+ colors,
24
+ enableInlineEditing = false,
25
+ sectionId = ''
26
+ }: MinimalFooterProps) {
27
+ const alignClass = alignment === 'center' ? 'text-center' : alignment === 'right' ? 'text-right' : 'text-left';
28
+
29
+ return (
30
+ <footer
31
+ className={`border-t py-8`}
32
+ style={{ borderColor: colors.primary }}
33
+ >
34
+ <div className={`container mx-auto px-4 ${alignClass}`}>
35
+ <div className="space-y-4">
36
+ {showSocial && (
37
+ <div className={`flex gap-4 ${alignment === 'center' ? 'justify-center' : alignment === 'right' ? 'justify-end' : 'justify-start'}`}>
38
+ {['instagram', 'facebook', 'twitter'].map((platform) => (
39
+ <a
40
+ key={platform}
41
+ href={`https://${platform}.com/`}
42
+ className="w-8 h-8 rounded-full flex items-center justify-center transition-transform hover:scale-110"
43
+ style={{ backgroundColor: colors.primary, color: '#FFFFFF' }}
44
+ >
45
+ {platform[0].toUpperCase()}
46
+ </a>
47
+ ))}
48
+ </div>
49
+ )}
50
+ {showContact && (
51
+ <div className="text-sm opacity-70" style={{ color: colors.text }}>
52
+ <p>contact@example.com | (555) 123-4567</p>
53
+ </div>
54
+ )}
55
+ {showLinks && (
56
+ <div className={`flex gap-4 text-sm ${alignment === 'center' ? 'justify-center' : alignment === 'right' ? 'justify-end' : 'justify-start'}`}>
57
+ <a href="/privacy" className="hover:opacity-70" style={{ color: colors.text }}>Privacy</a>
58
+ <a href="/terms" className="hover:opacity-70" style={{ color: colors.text }}>Terms</a>
59
+ </div>
60
+ )}
61
+ <p
62
+ className="text-sm opacity-60"
63
+ style={{ color: colors.text }}
64
+ {...(enableInlineEditing && {
65
+ 'data-editable': true,
66
+ 'data-section-id': sectionId,
67
+ 'data-field-path': 'settings.footerText'
68
+ })}
69
+ >
70
+ {footerText || '© 2025 Your Business'}
71
+ </p>
72
+ </div>
73
+ </div>
74
+ </footer>
75
+ );
76
+ }
77
+
78
+ export default MinimalFooter;
@@ -0,0 +1,81 @@
1
+ 'use client';
2
+
3
+ export interface MinimalHeaderProps {
4
+ brandName?: string;
5
+ logoUrl?: string;
6
+ style?: 'logo' | 'text' | 'both';
7
+ alignment?: 'left' | 'center' | 'right';
8
+ sticky?: boolean;
9
+ colors: {
10
+ primary: string;
11
+ text: string;
12
+ };
13
+ typography: {
14
+ headingFont?: string;
15
+ };
16
+ enableInlineEditing?: boolean;
17
+ sectionId?: string;
18
+ }
19
+
20
+ export function MinimalHeader({
21
+ brandName,
22
+ logoUrl,
23
+ style = 'both',
24
+ alignment = 'left',
25
+ sticky = true,
26
+ colors,
27
+ typography,
28
+ enableInlineEditing = false,
29
+ sectionId = ''
30
+ }: MinimalHeaderProps) {
31
+ const alignmentClass = alignment === 'center' ? 'justify-center' : alignment === 'right' ? 'justify-end' : 'justify-start';
32
+
33
+ return (
34
+ <header
35
+ className={`${sticky ? 'sticky top-0 z-50 backdrop-blur-sm' : ''} border-b`}
36
+ style={{
37
+ backgroundColor: sticky ? 'rgba(255, 255, 255, 0.8)' : 'transparent',
38
+ borderColor: colors.primary
39
+ }}
40
+ >
41
+ <div className={`container mx-auto px-4 py-4 flex items-center ${alignmentClass}`}>
42
+ <div className="flex items-center gap-3">
43
+ {(style === 'logo' || style === 'both') && (
44
+ <>
45
+ {logoUrl ? (
46
+ <img
47
+ src={logoUrl}
48
+ alt={brandName || 'Logo'}
49
+ className="h-10 w-auto object-contain"
50
+ />
51
+ ) : (
52
+ <div
53
+ className="w-10 h-10 rounded-full"
54
+ style={{ backgroundColor: colors.primary, opacity: 0.2 }}
55
+ />
56
+ )}
57
+ </>
58
+ )}
59
+ {(style === 'text' || style === 'both' || !style) && (
60
+ <h1
61
+ className="text-2xl font-bold"
62
+ style={{
63
+ fontFamily: typography.headingFont,
64
+ color: colors.text
65
+ }}
66
+ {...(enableInlineEditing && {
67
+ 'data-editable': true,
68
+ 'data-section-id': sectionId,
69
+ 'data-field-path': 'settings.brandName'
70
+ })}
71
+ >
72
+ {brandName || 'Your Brand'}
73
+ </h1>
74
+ )}
75
+ </div>
76
+ </div>
77
+ </header>
78
+ );
79
+ }
80
+
81
+ export default MinimalHeader;