@aws505/sheetsite 1.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 +105 -0
- package/dist/components/index.js +1696 -0
- package/dist/components/index.js.map +1 -0
- package/dist/components/index.mjs +1630 -0
- package/dist/components/index.mjs.map +1 -0
- package/dist/config/index.js +1840 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/index.mjs +1793 -0
- package/dist/config/index.mjs.map +1 -0
- package/dist/data/index.js +1296 -0
- package/dist/data/index.js.map +1 -0
- package/dist/data/index.mjs +1220 -0
- package/dist/data/index.mjs.map +1 -0
- package/dist/index.js +5433 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +5285 -0
- package/dist/index.mjs.map +1 -0
- package/dist/seo/index.js +187 -0
- package/dist/seo/index.js.map +1 -0
- package/dist/seo/index.mjs +155 -0
- package/dist/seo/index.mjs.map +1 -0
- package/dist/theme/index.js +552 -0
- package/dist/theme/index.js.map +1 -0
- package/dist/theme/index.mjs +526 -0
- package/dist/theme/index.mjs.map +1 -0
- package/package.json +96 -0
- package/src/components/index.ts +41 -0
- package/src/components/layout/Footer.tsx +234 -0
- package/src/components/layout/Header.tsx +134 -0
- package/src/components/sections/FAQ.tsx +178 -0
- package/src/components/sections/Gallery.tsx +107 -0
- package/src/components/sections/Hero.tsx +202 -0
- package/src/components/sections/Hours.tsx +225 -0
- package/src/components/sections/Services.tsx +216 -0
- package/src/components/sections/Testimonials.tsx +184 -0
- package/src/components/ui/Button.tsx +158 -0
- package/src/components/ui/Card.tsx +162 -0
- package/src/components/ui/Icons.tsx +508 -0
- package/src/config/index.ts +207 -0
- package/src/config/presets/generic.ts +153 -0
- package/src/config/presets/home-kitchen.ts +154 -0
- package/src/config/presets/index.ts +708 -0
- package/src/config/presets/professional.ts +165 -0
- package/src/config/presets/repair.ts +160 -0
- package/src/config/presets/restaurant.ts +162 -0
- package/src/config/presets/salon.ts +178 -0
- package/src/config/presets/tailor.ts +159 -0
- package/src/config/types.ts +314 -0
- package/src/data/csv-parser.ts +154 -0
- package/src/data/defaults.ts +202 -0
- package/src/data/google-drive.ts +148 -0
- package/src/data/index.ts +535 -0
- package/src/data/sheets.ts +709 -0
- package/src/data/types.ts +379 -0
- package/src/seo/index.ts +272 -0
- package/src/theme/colors.ts +351 -0
- package/src/theme/index.ts +249 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tailor / Alterations Business Preset
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { BusinessPreset } from '../types';
|
|
6
|
+
|
|
7
|
+
export const tailorPreset: BusinessPreset = {
|
|
8
|
+
type: 'tailor',
|
|
9
|
+
name: 'Tailor & Alterations',
|
|
10
|
+
description: 'Configuration for tailoring, alterations, and custom clothing businesses',
|
|
11
|
+
|
|
12
|
+
config: {
|
|
13
|
+
businessType: 'tailor',
|
|
14
|
+
theme: {
|
|
15
|
+
preset: 'warm-brown',
|
|
16
|
+
borderRadius: 'md',
|
|
17
|
+
shadows: 'md',
|
|
18
|
+
},
|
|
19
|
+
pages: {
|
|
20
|
+
home: {
|
|
21
|
+
enabled: true,
|
|
22
|
+
sections: [
|
|
23
|
+
'hero',
|
|
24
|
+
'services',
|
|
25
|
+
'gallery',
|
|
26
|
+
'testimonials',
|
|
27
|
+
'hours',
|
|
28
|
+
'location',
|
|
29
|
+
'faq',
|
|
30
|
+
],
|
|
31
|
+
},
|
|
32
|
+
about: { enabled: true },
|
|
33
|
+
services: { enabled: true, title: 'Our Services' },
|
|
34
|
+
gallery: { enabled: true, title: 'Our Work' },
|
|
35
|
+
faq: { enabled: true },
|
|
36
|
+
contact: { enabled: true },
|
|
37
|
+
privacy: { enabled: true },
|
|
38
|
+
},
|
|
39
|
+
seo: {
|
|
40
|
+
titleTemplate: '%s | Expert Tailoring & Alterations',
|
|
41
|
+
keywords: ['tailor', 'alterations', 'seamstress', 'hemming', 'custom clothing', 'wedding dress alterations'],
|
|
42
|
+
jsonLdType: 'LocalBusiness',
|
|
43
|
+
},
|
|
44
|
+
contactForm: {
|
|
45
|
+
enabled: true,
|
|
46
|
+
fields: {
|
|
47
|
+
name: true,
|
|
48
|
+
email: true,
|
|
49
|
+
phone: true,
|
|
50
|
+
service: true,
|
|
51
|
+
message: true,
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
features: {
|
|
55
|
+
mobileCallBar: true,
|
|
56
|
+
openStatus: true,
|
|
57
|
+
mapEmbed: true,
|
|
58
|
+
socialLinks: true,
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
defaults: {
|
|
63
|
+
business: {
|
|
64
|
+
tagline: 'Expert Alterations & Custom Tailoring',
|
|
65
|
+
description: 'Professional tailoring services with attention to detail and quality craftsmanship.',
|
|
66
|
+
priceRange: '$$',
|
|
67
|
+
},
|
|
68
|
+
services: [
|
|
69
|
+
{ id: 's1', title: 'Alterations', description: 'Expert alterations for all garments including pants, shirts, dresses, and jackets', priceNote: 'Starting at $15', icon: 'scissors', sortOrder: 1 },
|
|
70
|
+
{ id: 's2', title: 'Hemming', description: 'Professional hemming for pants, skirts, dresses, and curtains', priceNote: 'From $12', icon: 'ruler', sortOrder: 2 },
|
|
71
|
+
{ id: 's3', title: 'Wedding & Formal', description: 'Specialized alterations for wedding dresses, bridesmaids gowns, and formal wear', priceNote: 'Custom quote', icon: 'heart', sortOrder: 3 },
|
|
72
|
+
{ id: 's4', title: 'Repairs & Restoration', description: 'Zipper replacement, button repair, seam repair, and garment restoration', priceNote: 'From $10', icon: 'wrench', sortOrder: 4 },
|
|
73
|
+
{ id: 's5', title: 'Custom Tailoring', description: 'Made-to-measure suits, shirts, and garments tailored to your exact specifications', priceNote: 'By consultation', icon: 'sparkles', sortOrder: 5 },
|
|
74
|
+
{ id: 's6', title: 'Leather & Specialty', description: 'Expert work on leather, suede, and delicate fabrics', priceNote: 'Call for pricing', icon: 'star', sortOrder: 6 },
|
|
75
|
+
],
|
|
76
|
+
faq: [
|
|
77
|
+
{ id: 'f1', question: 'How long do alterations typically take?', answer: 'Most alterations are completed within 3-5 business days. Rush service is available for an additional fee. Wedding and formal wear may require more time for multiple fittings.', sortOrder: 1 },
|
|
78
|
+
{ id: 'f2', question: 'Do you work on leather and suede?', answer: 'Yes, we have extensive experience with leather and suede garments. These materials require specialized care and techniques, so pricing and timing may vary.', sortOrder: 2 },
|
|
79
|
+
{ id: 'f3', question: 'What should I bring to my fitting?', answer: 'Please bring the shoes you plan to wear with the garment, and any undergarments that might affect the fit. For formal wear, bring accessories like belts or jewelry.', sortOrder: 3 },
|
|
80
|
+
{ id: 'f4', question: 'Do you offer wedding dress alterations?', answer: 'Absolutely! We specialize in wedding dress alterations including hemming, bustling, taking in or letting out, and adding embellishments. We recommend scheduling your first fitting 2-3 months before the wedding.', sortOrder: 4 },
|
|
81
|
+
{ id: 'f5', question: 'Can you match fabric for repairs?', answer: 'We maintain an extensive fabric library and can often find close matches for repairs and patches. Bring your garment in for a free assessment.', sortOrder: 5 },
|
|
82
|
+
],
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
sheetTemplate: {
|
|
86
|
+
tabs: [
|
|
87
|
+
{
|
|
88
|
+
name: 'business',
|
|
89
|
+
required: true,
|
|
90
|
+
columns: [
|
|
91
|
+
{ name: 'key', description: 'Setting name', example: 'name', required: true },
|
|
92
|
+
{ name: 'value', description: 'Setting value', example: 'Expert Tailoring', required: true },
|
|
93
|
+
],
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
name: 'hours',
|
|
97
|
+
required: true,
|
|
98
|
+
columns: [
|
|
99
|
+
{ name: 'day', description: 'Day of week', example: 'Monday', required: true },
|
|
100
|
+
{ name: 'open', description: 'Opening time', example: '9:00 AM', required: false },
|
|
101
|
+
{ name: 'close', description: 'Closing time', example: '6:00 PM', required: false },
|
|
102
|
+
{ name: 'closed', description: 'Is closed?', example: 'FALSE', required: false },
|
|
103
|
+
],
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
name: 'services',
|
|
107
|
+
required: true,
|
|
108
|
+
columns: [
|
|
109
|
+
{ name: 'title', description: 'Service name', example: 'Alterations', required: true },
|
|
110
|
+
{ name: 'description', description: 'Service description', example: 'Expert alterations for all garments', required: false },
|
|
111
|
+
{ name: 'price_note', description: 'Pricing info', example: 'Starting at $15', required: false },
|
|
112
|
+
{ name: 'icon', description: 'Icon name', example: 'scissors', required: false },
|
|
113
|
+
{ name: 'sort', description: 'Display order', example: '1', required: false },
|
|
114
|
+
],
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
name: 'gallery',
|
|
118
|
+
required: false,
|
|
119
|
+
columns: [
|
|
120
|
+
{ name: 'image_url', description: 'Image URL (Google Drive or direct)', example: 'https://drive.google.com/...', required: true },
|
|
121
|
+
{ name: 'alt', description: 'Image description', example: 'Wedding dress alteration', required: false },
|
|
122
|
+
{ name: 'caption', description: 'Caption text', example: 'Custom bridal alterations', required: false },
|
|
123
|
+
{ name: 'sort', description: 'Display order', example: '1', required: false },
|
|
124
|
+
],
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
name: 'testimonials',
|
|
128
|
+
required: false,
|
|
129
|
+
columns: [
|
|
130
|
+
{ name: 'quote', description: 'Customer review', example: 'Amazing work on my wedding dress!', required: true },
|
|
131
|
+
{ name: 'name', description: 'Customer name', example: 'Sarah M.', required: true },
|
|
132
|
+
{ name: 'context', description: 'Service context', example: 'Wedding alterations', required: false },
|
|
133
|
+
{ name: 'rating', description: 'Star rating (1-5)', example: '5', required: false },
|
|
134
|
+
{ name: 'sort', description: 'Display order', example: '1', required: false },
|
|
135
|
+
],
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
name: 'faq',
|
|
139
|
+
required: false,
|
|
140
|
+
columns: [
|
|
141
|
+
{ name: 'question', description: 'Question text', example: 'How long do alterations take?', required: true },
|
|
142
|
+
{ name: 'answer', description: 'Answer text', example: '3-5 business days typically', required: true },
|
|
143
|
+
{ name: 'sort', description: 'Display order', example: '1', required: false },
|
|
144
|
+
],
|
|
145
|
+
},
|
|
146
|
+
],
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
iconSuggestions: ['scissors', 'ruler', 'heart', 'sparkles', 'star', 'wrench', 'shirt', 'dress'],
|
|
150
|
+
|
|
151
|
+
imageSuggestions: [
|
|
152
|
+
'Professional tailor measuring fabric',
|
|
153
|
+
'Close-up of precise stitching',
|
|
154
|
+
'Wedding dress on mannequin',
|
|
155
|
+
'Organized tailor workshop',
|
|
156
|
+
'Before and after alterations',
|
|
157
|
+
'Happy customer with altered garment',
|
|
158
|
+
],
|
|
159
|
+
};
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SheetSite Configuration Types
|
|
3
|
+
*
|
|
4
|
+
* Defines the configuration structure for creating sites.
|
|
5
|
+
* These types are used by both the library and the CLI generator.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { SiteData } from '../data/types';
|
|
9
|
+
|
|
10
|
+
// =============================================================================
|
|
11
|
+
// BUSINESS TYPES
|
|
12
|
+
// =============================================================================
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Supported business types with specialized configurations.
|
|
16
|
+
*/
|
|
17
|
+
export type BusinessType =
|
|
18
|
+
// Service Businesses
|
|
19
|
+
| 'tailor'
|
|
20
|
+
| 'shoe-repair'
|
|
21
|
+
| 'dry-cleaner'
|
|
22
|
+
| 'salon'
|
|
23
|
+
| 'barbershop'
|
|
24
|
+
| 'spa'
|
|
25
|
+
| 'nail-salon'
|
|
26
|
+
| 'pet-grooming'
|
|
27
|
+
// Food & Beverage
|
|
28
|
+
| 'restaurant'
|
|
29
|
+
| 'cafe'
|
|
30
|
+
| 'bakery'
|
|
31
|
+
| 'food-truck'
|
|
32
|
+
| 'home-kitchen'
|
|
33
|
+
| 'catering'
|
|
34
|
+
// Retail
|
|
35
|
+
| 'craft-seller'
|
|
36
|
+
| 'boutique'
|
|
37
|
+
| 'florist'
|
|
38
|
+
| 'gift-shop'
|
|
39
|
+
// Home Services
|
|
40
|
+
| 'cleaning'
|
|
41
|
+
| 'landscaping'
|
|
42
|
+
| 'handyman'
|
|
43
|
+
| 'plumber'
|
|
44
|
+
| 'electrician'
|
|
45
|
+
| 'hvac'
|
|
46
|
+
| 'moving'
|
|
47
|
+
| 'pest-control'
|
|
48
|
+
// Auto Services
|
|
49
|
+
| 'auto-repair'
|
|
50
|
+
| 'auto-detail'
|
|
51
|
+
| 'tire-shop'
|
|
52
|
+
// Professional Services
|
|
53
|
+
| 'accountant'
|
|
54
|
+
| 'lawyer'
|
|
55
|
+
| 'real-estate'
|
|
56
|
+
| 'insurance'
|
|
57
|
+
| 'consulting'
|
|
58
|
+
// Health & Wellness
|
|
59
|
+
| 'personal-trainer'
|
|
60
|
+
| 'yoga-studio'
|
|
61
|
+
| 'martial-arts'
|
|
62
|
+
| 'chiropractor'
|
|
63
|
+
| 'massage'
|
|
64
|
+
// Education & Creative
|
|
65
|
+
| 'tutoring'
|
|
66
|
+
| 'music-lessons'
|
|
67
|
+
| 'photography'
|
|
68
|
+
| 'videography'
|
|
69
|
+
| 'event-planning'
|
|
70
|
+
// General
|
|
71
|
+
| 'generic';
|
|
72
|
+
|
|
73
|
+
// =============================================================================
|
|
74
|
+
// THEME CONFIGURATION
|
|
75
|
+
// =============================================================================
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Color palette configuration.
|
|
79
|
+
*/
|
|
80
|
+
export interface ColorPalette {
|
|
81
|
+
primary: {
|
|
82
|
+
50: string;
|
|
83
|
+
100: string;
|
|
84
|
+
200: string;
|
|
85
|
+
300: string;
|
|
86
|
+
400: string;
|
|
87
|
+
500: string;
|
|
88
|
+
600: string;
|
|
89
|
+
700: string;
|
|
90
|
+
800: string;
|
|
91
|
+
900: string;
|
|
92
|
+
950: string;
|
|
93
|
+
};
|
|
94
|
+
accent: {
|
|
95
|
+
50: string;
|
|
96
|
+
100: string;
|
|
97
|
+
200: string;
|
|
98
|
+
300: string;
|
|
99
|
+
400: string;
|
|
100
|
+
500: string;
|
|
101
|
+
600: string;
|
|
102
|
+
700: string;
|
|
103
|
+
800: string;
|
|
104
|
+
900: string;
|
|
105
|
+
950: string;
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Font configuration.
|
|
111
|
+
*/
|
|
112
|
+
export interface FontConfig {
|
|
113
|
+
heading: string;
|
|
114
|
+
body: string;
|
|
115
|
+
accent?: string;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Theme preset names.
|
|
120
|
+
*/
|
|
121
|
+
export type ThemePreset =
|
|
122
|
+
| 'warm-brown' // Tailoring, leather, wood
|
|
123
|
+
| 'cool-blue' // Professional, tech, medical
|
|
124
|
+
| 'earth-green' // Natural, organic, outdoor
|
|
125
|
+
| 'warm-amber' // Food, bakery, hospitality
|
|
126
|
+
| 'elegant-gold' // Luxury, formal, upscale
|
|
127
|
+
| 'fresh-teal' // Modern, creative, spa
|
|
128
|
+
| 'bold-red' // Auto, sports, urgent
|
|
129
|
+
| 'soft-pink' // Beauty, feminine, delicate
|
|
130
|
+
| 'slate-gray' // Minimalist, industrial
|
|
131
|
+
| 'forest-green'; // Eco, outdoor, nature
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Complete theme configuration.
|
|
135
|
+
*/
|
|
136
|
+
export interface ThemeConfig {
|
|
137
|
+
preset?: ThemePreset;
|
|
138
|
+
colors?: Partial<ColorPalette>;
|
|
139
|
+
fonts?: Partial<FontConfig>;
|
|
140
|
+
borderRadius?: 'none' | 'sm' | 'md' | 'lg' | 'xl' | 'full';
|
|
141
|
+
shadows?: 'none' | 'sm' | 'md' | 'lg';
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// =============================================================================
|
|
145
|
+
// PAGE CONFIGURATION
|
|
146
|
+
// =============================================================================
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Available page types.
|
|
150
|
+
*/
|
|
151
|
+
export type PageType =
|
|
152
|
+
| 'home'
|
|
153
|
+
| 'about'
|
|
154
|
+
| 'services'
|
|
155
|
+
| 'menu'
|
|
156
|
+
| 'products'
|
|
157
|
+
| 'gallery'
|
|
158
|
+
| 'testimonials'
|
|
159
|
+
| 'team'
|
|
160
|
+
| 'faq'
|
|
161
|
+
| 'contact'
|
|
162
|
+
| 'privacy';
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Home page section configuration.
|
|
166
|
+
*/
|
|
167
|
+
export type HomeSectionType =
|
|
168
|
+
| 'hero'
|
|
169
|
+
| 'services'
|
|
170
|
+
| 'menu'
|
|
171
|
+
| 'products'
|
|
172
|
+
| 'gallery'
|
|
173
|
+
| 'testimonials'
|
|
174
|
+
| 'team'
|
|
175
|
+
| 'faq'
|
|
176
|
+
| 'hours'
|
|
177
|
+
| 'location'
|
|
178
|
+
| 'contact-form'
|
|
179
|
+
| 'cta'
|
|
180
|
+
| 'how-it-works'
|
|
181
|
+
| 'announcements'
|
|
182
|
+
| 'about-preview';
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Page configuration.
|
|
186
|
+
*/
|
|
187
|
+
export interface PageConfig {
|
|
188
|
+
enabled: boolean;
|
|
189
|
+
title?: string;
|
|
190
|
+
sections?: HomeSectionType[];
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// =============================================================================
|
|
194
|
+
// SITE CONFIGURATION
|
|
195
|
+
// =============================================================================
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* SEO configuration.
|
|
199
|
+
*/
|
|
200
|
+
export interface SEOConfig {
|
|
201
|
+
titleTemplate?: string;
|
|
202
|
+
defaultDescription?: string;
|
|
203
|
+
keywords?: string[];
|
|
204
|
+
ogImage?: string;
|
|
205
|
+
twitterHandle?: string;
|
|
206
|
+
jsonLdType?: 'LocalBusiness' | 'Restaurant' | 'Store' | 'ProfessionalService' | 'HealthAndBeautyBusiness';
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Contact form configuration.
|
|
211
|
+
*/
|
|
212
|
+
export interface ContactFormConfig {
|
|
213
|
+
enabled: boolean;
|
|
214
|
+
provider?: 'resend' | 'sendgrid' | 'mailgun' | 'custom';
|
|
215
|
+
fields?: {
|
|
216
|
+
name: boolean;
|
|
217
|
+
email: boolean;
|
|
218
|
+
phone: boolean;
|
|
219
|
+
service: boolean;
|
|
220
|
+
message: boolean;
|
|
221
|
+
};
|
|
222
|
+
successMessage?: string;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Feature flags.
|
|
227
|
+
*/
|
|
228
|
+
export interface FeatureFlags {
|
|
229
|
+
mobileCallBar?: boolean;
|
|
230
|
+
announcementBanner?: boolean;
|
|
231
|
+
openStatus?: boolean;
|
|
232
|
+
mapEmbed?: boolean;
|
|
233
|
+
socialLinks?: boolean;
|
|
234
|
+
bookingButton?: boolean;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Complete site configuration.
|
|
239
|
+
*/
|
|
240
|
+
export interface SiteConfig {
|
|
241
|
+
// Basic info
|
|
242
|
+
businessType: BusinessType;
|
|
243
|
+
siteUrl?: string;
|
|
244
|
+
|
|
245
|
+
// Theme
|
|
246
|
+
theme: ThemeConfig;
|
|
247
|
+
|
|
248
|
+
// Pages
|
|
249
|
+
pages: {
|
|
250
|
+
home: PageConfig & { sections: HomeSectionType[] };
|
|
251
|
+
about?: PageConfig;
|
|
252
|
+
services?: PageConfig;
|
|
253
|
+
menu?: PageConfig;
|
|
254
|
+
products?: PageConfig;
|
|
255
|
+
gallery?: PageConfig;
|
|
256
|
+
testimonials?: PageConfig;
|
|
257
|
+
team?: PageConfig;
|
|
258
|
+
faq?: PageConfig;
|
|
259
|
+
contact?: PageConfig;
|
|
260
|
+
privacy?: PageConfig;
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
// SEO
|
|
264
|
+
seo?: SEOConfig;
|
|
265
|
+
|
|
266
|
+
// Contact
|
|
267
|
+
contactForm?: ContactFormConfig;
|
|
268
|
+
|
|
269
|
+
// Features
|
|
270
|
+
features?: FeatureFlags;
|
|
271
|
+
|
|
272
|
+
// Custom CSS classes
|
|
273
|
+
customClasses?: {
|
|
274
|
+
container?: string;
|
|
275
|
+
section?: string;
|
|
276
|
+
button?: string;
|
|
277
|
+
card?: string;
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// =============================================================================
|
|
282
|
+
// BUSINESS PRESET
|
|
283
|
+
// =============================================================================
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* A complete preset for a business type.
|
|
287
|
+
* Includes configuration, default data, and content suggestions.
|
|
288
|
+
*/
|
|
289
|
+
export interface BusinessPreset {
|
|
290
|
+
type: BusinessType;
|
|
291
|
+
name: string;
|
|
292
|
+
description: string;
|
|
293
|
+
config: SiteConfig;
|
|
294
|
+
defaults: Partial<SiteData>;
|
|
295
|
+
sheetTemplate: SheetTemplate;
|
|
296
|
+
iconSuggestions: string[];
|
|
297
|
+
imageSuggestions: string[];
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Google Sheet template structure.
|
|
302
|
+
*/
|
|
303
|
+
export interface SheetTemplate {
|
|
304
|
+
tabs: {
|
|
305
|
+
name: string;
|
|
306
|
+
required: boolean;
|
|
307
|
+
columns: {
|
|
308
|
+
name: string;
|
|
309
|
+
description: string;
|
|
310
|
+
example: string;
|
|
311
|
+
required: boolean;
|
|
312
|
+
}[];
|
|
313
|
+
}[];
|
|
314
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CSV Parser
|
|
3
|
+
*
|
|
4
|
+
* A robust CSV parser that handles:
|
|
5
|
+
* - Quoted fields with embedded commas
|
|
6
|
+
* - Escaped quotes within quoted fields
|
|
7
|
+
* - Various line endings (CRLF, LF)
|
|
8
|
+
* - Empty values
|
|
9
|
+
* - Header normalization
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export interface ParsedRow {
|
|
13
|
+
[key: string]: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Parse a CSV string into an array of objects.
|
|
18
|
+
* Headers are normalized: lowercased and spaces replaced with underscores.
|
|
19
|
+
*/
|
|
20
|
+
export function parseCSV(csvText: string): ParsedRow[] {
|
|
21
|
+
const lines = csvText.split(/\r?\n/);
|
|
22
|
+
if (lines.length < 2) return [];
|
|
23
|
+
|
|
24
|
+
// Parse header row
|
|
25
|
+
const headers = parseCSVLine(lines[0]).map(normalizeHeader);
|
|
26
|
+
if (headers.length === 0) return [];
|
|
27
|
+
|
|
28
|
+
const rows: ParsedRow[] = [];
|
|
29
|
+
|
|
30
|
+
for (let i = 1; i < lines.length; i++) {
|
|
31
|
+
const line = lines[i].trim();
|
|
32
|
+
if (!line) continue;
|
|
33
|
+
|
|
34
|
+
const values = parseCSVLine(line);
|
|
35
|
+
const row: ParsedRow = {};
|
|
36
|
+
|
|
37
|
+
for (let j = 0; j < headers.length; j++) {
|
|
38
|
+
row[headers[j]] = values[j]?.trim() ?? '';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
rows.push(row);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return rows;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Parse a single CSV line, handling quoted fields correctly.
|
|
49
|
+
*/
|
|
50
|
+
function parseCSVLine(line: string): string[] {
|
|
51
|
+
const values: string[] = [];
|
|
52
|
+
let current = '';
|
|
53
|
+
let inQuotes = false;
|
|
54
|
+
let i = 0;
|
|
55
|
+
|
|
56
|
+
while (i < line.length) {
|
|
57
|
+
const char = line[i];
|
|
58
|
+
const nextChar = line[i + 1];
|
|
59
|
+
|
|
60
|
+
if (inQuotes) {
|
|
61
|
+
if (char === '"') {
|
|
62
|
+
if (nextChar === '"') {
|
|
63
|
+
// Escaped quote
|
|
64
|
+
current += '"';
|
|
65
|
+
i += 2;
|
|
66
|
+
} else {
|
|
67
|
+
// End of quoted field
|
|
68
|
+
inQuotes = false;
|
|
69
|
+
i++;
|
|
70
|
+
}
|
|
71
|
+
} else {
|
|
72
|
+
current += char;
|
|
73
|
+
i++;
|
|
74
|
+
}
|
|
75
|
+
} else {
|
|
76
|
+
if (char === '"') {
|
|
77
|
+
inQuotes = true;
|
|
78
|
+
i++;
|
|
79
|
+
} else if (char === ',') {
|
|
80
|
+
values.push(current);
|
|
81
|
+
current = '';
|
|
82
|
+
i++;
|
|
83
|
+
} else {
|
|
84
|
+
current += char;
|
|
85
|
+
i++;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Push the last value
|
|
91
|
+
values.push(current);
|
|
92
|
+
|
|
93
|
+
return values;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Normalize a header string:
|
|
98
|
+
* - Convert to lowercase
|
|
99
|
+
* - Replace spaces with underscores
|
|
100
|
+
* - Remove special characters
|
|
101
|
+
*/
|
|
102
|
+
export function normalizeHeader(header: string): string {
|
|
103
|
+
return header
|
|
104
|
+
.toLowerCase()
|
|
105
|
+
.trim()
|
|
106
|
+
.replace(/\s+/g, '_')
|
|
107
|
+
.replace(/[^a-z0-9_]/g, '');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Convert CSV rows to typed objects using a mapping function.
|
|
112
|
+
*/
|
|
113
|
+
export function mapRows<T>(
|
|
114
|
+
rows: ParsedRow[],
|
|
115
|
+
mapper: (row: ParsedRow, index: number) => T | null
|
|
116
|
+
): T[] {
|
|
117
|
+
return rows
|
|
118
|
+
.map((row, index) => mapper(row, index))
|
|
119
|
+
.filter((item): item is T => item !== null);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Parse a boolean value from CSV (handles various formats).
|
|
124
|
+
*/
|
|
125
|
+
export function parseBoolean(value: string | undefined): boolean {
|
|
126
|
+
if (!value) return false;
|
|
127
|
+
const lower = value.toLowerCase().trim();
|
|
128
|
+
return lower === 'true' || lower === 'yes' || lower === '1';
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Parse a number value from CSV.
|
|
133
|
+
*/
|
|
134
|
+
export function parseNumber(value: string | undefined): number | undefined {
|
|
135
|
+
if (!value) return undefined;
|
|
136
|
+
const num = parseFloat(value);
|
|
137
|
+
return isNaN(num) ? undefined : num;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Parse an integer value from CSV.
|
|
142
|
+
*/
|
|
143
|
+
export function parseInteger(value: string | undefined): number | undefined {
|
|
144
|
+
if (!value) return undefined;
|
|
145
|
+
const num = parseInt(value, 10);
|
|
146
|
+
return isNaN(num) ? undefined : num;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Parse a sort order, defaulting to 0.
|
|
151
|
+
*/
|
|
152
|
+
export function parseSortOrder(value: string | undefined): number {
|
|
153
|
+
return parseInteger(value) ?? 0;
|
|
154
|
+
}
|