feat: Cherry-pick Website Maker feature from remote backup

This commit is contained in:
ajaysi
2026-03-01 18:42:27 +05:30
parent 4828274cbf
commit 62d9c2e836
3 changed files with 349 additions and 326 deletions

View File

@@ -1,32 +1,6 @@
import React, { useState, useEffect } from 'react';
import {
Box,
Button,
TextField,
Typography,
Card,
CardContent,
CircularProgress,
Alert,
Dialog,
DialogTitle,
DialogContent,
DialogActions,
Grid,
CardActionArea,
Tooltip,
InputAdornment,
IconButton,
Chip
} from '@mui/material';
import {
ArrowBack as ArrowBackIcon,
Save as SaveIcon,
CheckCircle as CheckCircleIcon,
HelpOutline as HelpIcon,
Lightbulb as LightbulbIcon,
AutoAwesome as AutoAwesomeIcon
} from '@mui/icons-material';
import { Box, Button, TextField, Typography, Card, CardContent, CircularProgress, Alert, MenuItem, Divider } from '@mui/material';
import { ArrowBack as ArrowBackIcon, Save as SaveIcon, CheckCircle as CheckCircleIcon } from '@mui/icons-material';
import { businessInfoApi, BusinessInfo } from '../../api/businessInfo';
import { onboardingCache } from '../../services/onboardingCache';
@@ -35,36 +9,72 @@ interface BusinessDescriptionStepProps {
onContinue: (businessData?: BusinessInfo) => void;
}
const BUSINESS_EXAMPLES = [
{
title: "SaaS Tech Startup",
description: "We provide AI-powered project management tools for remote teams to boost productivity and collaboration. Our platform integrates with popular tools like Slack and Jira.",
industry: "Technology / Software",
target_audience: "Remote-first companies, Project Managers, Product Owners, Startups",
business_goals: "Increase user acquisition by 20% in Q3, improve user retention, and launch a new mobile app."
},
{
title: "Artisanal Coffee Shop",
description: "A cozy local coffee shop specializing in single-origin beans and homemade pastries, serving the downtown community with a focus on sustainability.",
industry: "Food & Beverage / Hospitality",
target_audience: "Local residents, office workers, coffee enthusiasts, students",
business_goals: "Build a loyal customer base, increase foot traffic during weekdays, and expand catering services for local offices."
},
{
title: "Digital Marketing Agency",
description: "A full-service digital marketing agency helping small businesses grow their online presence through SEO, PPC, and content marketing strategies.",
industry: "Marketing & Advertising",
target_audience: "Small to medium-sized business owners, e-commerce stores, local service providers",
business_goals: "Acquire 10 new monthly retainer clients, expand service offerings to include video marketing, and become a thought leader."
}
interface WebsiteIntakeForm {
business_name: string;
business_summary: string;
template_type: 'blog' | 'profile' | 'shop' | 'dont_know';
geo_scope: 'global' | 'local' | 'hyper_local' | 'dont_know';
primary_offerings: string;
target_audience: string;
audience_type: 'B2B' | 'B2C' | 'Both' | 'dont_know';
brand_tone: string;
brand_adjectives: string;
avoid_terms: string;
competitor_urls: string;
contact_email: string;
contact_phone: string;
contact_location: string;
product_asset_mode: 'upload' | 'generate' | 'dont_know';
product_asset_urls: string;
product_asset_ids: string;
}
const templateOptions = [
{ value: 'dont_know', label: "Don't know yet" },
{ value: 'blog', label: 'Blog / Creator site' },
{ value: 'profile', label: 'Profile / Services' },
{ value: 'shop', label: 'Shop / Products' }
];
const geoScopeOptions = [
{ value: 'dont_know', label: "Don't know yet" },
{ value: 'global', label: 'Global' },
{ value: 'local', label: 'Local' },
{ value: 'hyper_local', label: 'Hyper-local' }
];
const audienceTypeOptions = [
{ value: 'dont_know', label: "Don't know yet" },
{ value: 'B2B', label: 'B2B' },
{ value: 'B2C', label: 'B2C' },
{ value: 'Both', label: 'Both' }
];
const productAssetOptions = [
{ value: 'dont_know', label: "Don't know yet" },
{ value: 'upload', label: 'Upload product images' },
{ value: 'generate', label: 'Generate with AI (Product Marketing Studio)' }
];
const BusinessDescriptionStep: React.FC<BusinessDescriptionStepProps> = ({ onBack, onContinue }) => {
const [formData, setFormData] = useState<BusinessInfo>({
business_description: '',
industry: '',
const [intakeForm, setIntakeForm] = useState<WebsiteIntakeForm>({
business_name: '',
business_summary: '',
template_type: 'dont_know',
geo_scope: 'dont_know',
primary_offerings: '',
target_audience: '',
business_goals: '',
audience_type: 'dont_know',
brand_tone: 'Don\'t know yet',
brand_adjectives: '',
avoid_terms: '',
competitor_urls: '',
contact_email: '',
contact_phone: '',
contact_location: '',
product_asset_mode: 'dont_know',
product_asset_urls: '',
product_asset_ids: '',
});
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
@@ -74,48 +84,46 @@ const BusinessDescriptionStep: React.FC<BusinessDescriptionStepProps> = ({ onBac
useEffect(() => {
console.log('🔄 BusinessDescriptionStep mounted. Loading cached data...');
const cachedData = onboardingCache.getStepData(2)?.businessInfo;
const cachedIntake = onboardingCache.getStepData(2)?.websiteIntake;
if (cachedData) {
setFormData(cachedData);
console.log('✅ Loaded cached business info:', cachedData);
} else {
console.log(' No cached business info found.');
}
if (cachedIntake) {
setIntakeForm(cachedIntake);
console.log('✅ Loaded cached website intake:', cachedIntake);
}
}, []);
const handleExampleSelect = (example: typeof BUSINESS_EXAMPLES[0]) => {
setFormData({
business_description: example.description,
industry: example.industry,
target_audience: example.target_audience,
business_goals: example.business_goals,
});
setShowExamples(false);
setSuccess('Example data populated! You can now edit it to fit your needs.');
setTimeout(() => setSuccess(null), 3000);
};
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
const handleIntakeChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
const { name, value } = e.target;
setFormData(prev => ({ ...prev, [name]: value }));
setIntakeForm(prev => ({ ...prev, [name]: value }));
};
const handleSaveAndContinue = async () => {
setError(null);
setSuccess(null);
setLoading(true);
console.log('🚀 Attempting to save business info:', formData);
const derivedBusinessInfo: BusinessInfo = {
business_description: intakeForm.business_summary || 'No description provided',
industry: intakeForm.template_type === 'dont_know' ? '' : intakeForm.template_type,
target_audience: intakeForm.target_audience,
business_goals: intakeForm.primary_offerings,
};
console.log('🚀 Attempting to save business info:', derivedBusinessInfo);
try {
// Simulate user_id for now, replace with actual user_id from auth context later
const userId = 1;
const dataToSave = { ...formData, user_id: userId };
const dataToSave = { ...derivedBusinessInfo, user_id: userId };
const response = await businessInfoApi.saveBusinessInfo(dataToSave);
console.log('✅ Business info saved to DB:', response);
setSuccess('Business information saved successfully!');
// Also save to cache for consistency with other steps
onboardingCache.saveStepData(2, { businessInfo: response, hasWebsite: false });
onboardingCache.saveStepData(2, { businessInfo: response, websiteIntake: intakeForm, hasWebsite: false });
console.log('✅ Business info saved to cache.');
setTimeout(() => {
@@ -131,25 +139,12 @@ const BusinessDescriptionStep: React.FC<BusinessDescriptionStepProps> = ({ onBac
return (
<Box sx={{ mt: 4 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', mb: 3 }}>
<Box>
<Typography variant="h5" gutterBottom sx={{ fontWeight: 600, color: '#111827' }}>
Tell us about your business
</Typography>
<Typography variant="body1" color="text.secondary">
Provide details about your business to help ALwrity tailor its services.
</Typography>
</Box>
<Button
variant="outlined"
color="primary"
startIcon={<LightbulbIcon />}
onClick={() => setShowExamples(true)}
sx={{ textTransform: 'none', borderRadius: '8px', whiteSpace: 'nowrap', ml: 2 }}
>
See Examples
</Button>
</Box>
<Typography variant="h5" gutterBottom>
Create your AI-generated website
</Typography>
<Typography variant="body1" color="textSecondary" sx={{ mb: 3 }}>
Share a few details (even 3-4 lines is enough). If you are unsure, choose Don't know yet” and well fill the gaps with AI.
</Typography>
<Card sx={{
p: 3,
@@ -164,156 +159,233 @@ const BusinessDescriptionStep: React.FC<BusinessDescriptionStepProps> = ({ onBac
{error && <Alert severity="error" sx={{ mb: 2 }}>{error}</Alert>}
{success && <Alert severity="success" sx={{ mb: 2 }} icon={<CheckCircleIcon fontSize="inherit" />}>{success}</Alert>}
<Tooltip title="Describe what your business does, your unique value proposition, and your key products or services." arrow placement="top">
<TextField
label="Business Description"
name="business_description"
value={formData.business_description}
onChange={handleChange}
fullWidth
multiline
rows={4}
margin="normal"
required
placeholder="e.g., We provide AI-powered project management tools for remote teams..."
helperText={`${formData.business_description.length}/1000 characters`}
inputProps={{ maxLength: 1000 }}
disabled={loading}
InputProps={{
startAdornment: (
<InputAdornment position="start" sx={{ mt: -3 }}>
<HelpIcon color="action" fontSize="small" />
</InputAdornment>
),
}}
sx={{
'& .MuiOutlinedInput-root': {
bgcolor: '#F9FAFB',
color: '#111827',
borderRadius: '12px',
transition: 'all 0.2s',
'& fieldset': { borderColor: '#E5E7EB' },
'&:hover fieldset': { borderColor: '#6C5CE7' },
'&.Mui-focused fieldset': { borderColor: '#6C5CE7', borderWidth: '2px' },
'&.Mui-focused': { bgcolor: '#FFFFFF', boxShadow: '0 0 0 4px rgba(108, 92, 231, 0.1)' }
},
'& .MuiInputLabel-root': { color: '#6B7280' },
'& .MuiInputLabel-root.Mui-focused': { color: '#6C5CE7' },
}}
/>
</Tooltip>
<Tooltip title="What industry or sector does your business operate in?" arrow placement="top">
<TextField
label="Industry"
name="industry"
value={formData.industry}
onChange={handleChange}
fullWidth
margin="normal"
placeholder="e.g., Technology, Retail, Healthcare..."
helperText={`${(formData.industry || '').length}/100 characters`}
inputProps={{ maxLength: 100 }}
disabled={loading}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<HelpIcon color="action" fontSize="small" />
</InputAdornment>
),
}}
sx={{
'& .MuiOutlinedInput-root': {
bgcolor: '#F9FAFB',
color: '#111827',
borderRadius: '12px',
transition: 'all 0.2s',
'& fieldset': { borderColor: '#E5E7EB' },
'&:hover fieldset': { borderColor: '#6C5CE7' },
'&.Mui-focused fieldset': { borderColor: '#6C5CE7', borderWidth: '2px' },
'&.Mui-focused': { bgcolor: '#FFFFFF', boxShadow: '0 0 0 4px rgba(108, 92, 231, 0.1)' }
},
'& .MuiInputLabel-root': { color: '#6B7280' },
'& .MuiInputLabel-root.Mui-focused': { color: '#6C5CE7' },
}}
/>
</Tooltip>
<Tooltip title="Who are your ideal customers? Be specific about demographics, interests, or roles." arrow placement="top">
<TextField
label="Target Audience"
name="target_audience"
value={formData.target_audience}
onChange={handleChange}
fullWidth
multiline
rows={2}
margin="normal"
placeholder="e.g., Small business owners, marketing managers, eco-conscious consumers..."
helperText={`${(formData.target_audience || '').length}/500 characters`}
inputProps={{ maxLength: 500 }}
disabled={loading}
InputProps={{
startAdornment: (
<InputAdornment position="start" sx={{ mt: -1 }}>
<HelpIcon color="action" fontSize="small" />
</InputAdornment>
),
}}
sx={{
'& .MuiOutlinedInput-root': {
bgcolor: '#F9FAFB',
color: '#111827',
borderRadius: '12px',
transition: 'all 0.2s',
'& fieldset': { borderColor: '#E5E7EB' },
'&:hover fieldset': { borderColor: '#6C5CE7' },
'&.Mui-focused fieldset': { borderColor: '#6C5CE7', borderWidth: '2px' },
'&.Mui-focused': { bgcolor: '#FFFFFF', boxShadow: '0 0 0 4px rgba(108, 92, 231, 0.1)' }
},
'& .MuiInputLabel-root': { color: '#6B7280' },
'& .MuiInputLabel-root.Mui-focused': { color: '#6C5CE7' },
}}
/>
</Tooltip>
<Tooltip title="What are your main objectives for the next 6-12 months?" arrow placement="top">
<TextField
label="Business Goals"
name="business_goals"
value={formData.business_goals}
onChange={handleChange}
fullWidth
multiline
rows={3}
margin="normal"
placeholder="e.g., Increase brand awareness, generate more leads, launch a new product..."
helperText={`${(formData.business_goals || '').length}/1000 characters`}
inputProps={{ maxLength: 1000 }}
disabled={loading}
InputProps={{
startAdornment: (
<InputAdornment position="start" sx={{ mt: -2 }}>
<HelpIcon color="action" fontSize="small" />
</InputAdornment>
),
}}
sx={{
'& .MuiOutlinedInput-root': {
bgcolor: '#F9FAFB',
color: '#111827',
borderRadius: '12px',
transition: 'all 0.2s',
'& fieldset': { borderColor: '#E5E7EB' },
'&:hover fieldset': { borderColor: '#6C5CE7' },
'&.Mui-focused fieldset': { borderColor: '#6C5CE7', borderWidth: '2px' },
'&.Mui-focused': { bgcolor: '#FFFFFF', boxShadow: '0 0 0 4px rgba(108, 92, 231, 0.1)' }
},
'& .MuiInputLabel-root': { color: '#6B7280' },
'& .MuiInputLabel-root.Mui-focused': { color: '#6C5CE7' },
}}
/>
</Tooltip>
<TextField
label="Business Name"
name="business_name"
value={intakeForm.business_name}
onChange={handleIntakeChange}
fullWidth
margin="normal"
placeholder="e.g., Maple Street Homestays"
disabled={loading}
/>
<TextField
label="Describe what you do (3-4 lines)"
name="business_summary"
value={intakeForm.business_summary}
onChange={handleIntakeChange}
fullWidth
multiline
rows={4}
margin="normal"
required
helperText={`${intakeForm.business_summary.length}/1000 characters`}
inputProps={{ maxLength: 1000 }}
disabled={loading}
/>
<TextField
label="Website template"
name="template_type"
value={intakeForm.template_type}
onChange={handleIntakeChange}
select
fullWidth
margin="normal"
disabled={loading}
>
{templateOptions.map(option => (
<MenuItem key={option.value} value={option.value}>
{option.label}
</MenuItem>
))}
</TextField>
<TextField
label="Audience scope"
name="geo_scope"
value={intakeForm.geo_scope}
onChange={handleIntakeChange}
select
fullWidth
margin="normal"
disabled={loading}
>
{geoScopeOptions.map(option => (
<MenuItem key={option.value} value={option.value}>
{option.label}
</MenuItem>
))}
</TextField>
<TextField
label="Primary offerings (comma separated)"
name="primary_offerings"
value={intakeForm.primary_offerings}
onChange={handleIntakeChange}
fullWidth
margin="normal"
placeholder="e.g., Short stays, airport pickup, local tours"
disabled={loading}
/>
<TextField
label="Target audience"
name="target_audience"
value={intakeForm.target_audience}
onChange={handleIntakeChange}
fullWidth
multiline
rows={2}
margin="normal"
placeholder="e.g., Families visiting Pune for weddings"
disabled={loading}
/>
<TextField
label="Audience type"
name="audience_type"
value={intakeForm.audience_type}
onChange={handleIntakeChange}
select
fullWidth
margin="normal"
disabled={loading}
>
{audienceTypeOptions.map(option => (
<MenuItem key={option.value} value={option.value}>
{option.label}
</MenuItem>
))}
</TextField>
<TextField
label="Brand tone"
name="brand_tone"
value={intakeForm.brand_tone}
onChange={handleIntakeChange}
fullWidth
margin="normal"
placeholder="Friendly, premium, minimal"
disabled={loading}
/>
<TextField
label="Brand adjectives (comma separated)"
name="brand_adjectives"
value={intakeForm.brand_adjectives}
onChange={handleIntakeChange}
fullWidth
margin="normal"
placeholder="e.g., cozy, reliable, modern"
disabled={loading}
/>
<TextField
label="Avoid words or styles"
name="avoid_terms"
value={intakeForm.avoid_terms}
onChange={handleIntakeChange}
fullWidth
margin="normal"
placeholder="e.g., pushy sales language"
disabled={loading}
/>
<Divider sx={{ my: 3 }} />
<Typography variant="subtitle1" sx={{ mb: 1 }}>
Contact details (well use your account email if left blank)
</Typography>
<TextField
label="Contact email"
name="contact_email"
value={intakeForm.contact_email}
onChange={handleIntakeChange}
fullWidth
margin="normal"
placeholder="name@business.com"
disabled={loading}
/>
<TextField
label="Contact phone"
name="contact_phone"
value={intakeForm.contact_phone}
onChange={handleIntakeChange}
fullWidth
margin="normal"
placeholder="+91 90000 00000"
disabled={loading}
/>
<TextField
label="Location"
name="contact_location"
value={intakeForm.contact_location}
onChange={handleIntakeChange}
fullWidth
margin="normal"
placeholder="City, Region"
disabled={loading}
/>
<Divider sx={{ my: 3 }} />
<Typography variant="subtitle1" sx={{ mb: 1 }}>
Optional: competitor URLs (1-3)
</Typography>
<TextField
label="Competitor URLs (comma separated)"
name="competitor_urls"
value={intakeForm.competitor_urls}
onChange={handleIntakeChange}
fullWidth
margin="normal"
placeholder="https://competitor1.com, https://competitor2.com"
disabled={loading}
/>
{intakeForm.template_type === 'shop' && (
<>
<Divider sx={{ my: 3 }} />
<Typography variant="subtitle1" sx={{ mb: 1 }}>
Product images
</Typography>
<TextField
label="Product images"
name="product_asset_mode"
value={intakeForm.product_asset_mode}
onChange={handleIntakeChange}
select
fullWidth
margin="normal"
disabled={loading}
>
{productAssetOptions.map(option => (
<MenuItem key={option.value} value={option.value}>
{option.label}
</MenuItem>
))}
</TextField>
<Button
variant="outlined"
color="secondary"
href="/campaign-creator"
sx={{ mt: 1 }}
disabled={loading}
>
Open Product Marketing Studio
</Button>
<TextField
label="Product image URLs (comma separated)"
name="product_asset_urls"
value={intakeForm.product_asset_urls}
onChange={handleIntakeChange}
fullWidth
multiline
rows={2}
margin="normal"
placeholder="https://cdn.example.com/product-1.jpg, https://cdn.example.com/product-2.jpg"
disabled={loading}
/>
<TextField
label="Product asset IDs (comma separated)"
name="product_asset_ids"
value={intakeForm.product_asset_ids}
onChange={handleIntakeChange}
fullWidth
margin="normal"
placeholder="asset_123, asset_456"
disabled={loading}
/>
</>
)}
</CardContent>
</Card>
@@ -333,95 +405,12 @@ const BusinessDescriptionStep: React.FC<BusinessDescriptionStepProps> = ({ onBac
color="primary"
onClick={handleSaveAndContinue}
endIcon={loading ? <CircularProgress size={20} color="inherit" /> : <SaveIcon />}
disabled={loading || !formData.business_description}
sx={{
boxShadow: '0 4px 6px -1px rgba(108, 92, 231, 0.4), 0 2px 4px -1px rgba(108, 92, 231, 0.2)',
'&:hover': { boxShadow: '0 10px 15px -3px rgba(108, 92, 231, 0.4), 0 4px 6px -2px rgba(108, 92, 231, 0.2)' }
}}
disabled={loading || !intakeForm.business_summary}
>
{loading ? 'Saving...' : 'Save & Continue'}
</Button>
</Box>
{/* Examples Modal */}
<Dialog
open={showExamples}
onClose={() => setShowExamples(false)}
maxWidth="md"
fullWidth
PaperProps={{
sx: { borderRadius: '16px' }
}}
>
<DialogTitle sx={{ display: 'flex', alignItems: 'center', gap: 1, borderBottom: '1px solid #F3F4F6' }}>
<AutoAwesomeIcon color="primary" />
<Typography variant="h6" component="span" sx={{ fontWeight: 600 }}>
Select an Example
</Typography>
<Typography variant="body2" color="text.secondary" sx={{ ml: 'auto' }}>
Click a card to populate the form
</Typography>
</DialogTitle>
<DialogContent sx={{ bgcolor: '#F9FAFB', p: 3 }}>
<Grid container spacing={2}>
{BUSINESS_EXAMPLES.map((example, index) => (
<Grid item xs={12} md={4} key={index}>
<Card
sx={{
height: '100%',
border: '1px solid #E5E7EB',
transition: 'all 0.2s',
'&:hover': {
borderColor: '#6C5CE7',
boxShadow: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)',
transform: 'translateY(-2px)'
}
}}
>
<CardActionArea
onClick={() => handleExampleSelect(example)}
sx={{ height: '100%', p: 2, display: 'flex', flexDirection: 'column', alignItems: 'flex-start', justifyContent: 'flex-start' }}
>
<Chip
label={example.title}
color="primary"
size="small"
variant="filled"
sx={{ mb: 2, fontWeight: 600, bgcolor: '#EEF2FF', color: '#6C5CE7' }}
/>
<Typography variant="subtitle2" sx={{ fontWeight: 700, mb: 0.5, color: '#374151' }}>
Description:
</Typography>
<Typography variant="body2" color="text.secondary" paragraph sx={{
display: '-webkit-box',
WebkitLineClamp: 4,
WebkitBoxOrient: 'vertical',
overflow: 'hidden',
mb: 2,
fontSize: '0.875rem'
}}>
{example.description}
</Typography>
<Box sx={{ mt: 'auto', width: '100%' }}>
<Typography variant="subtitle2" sx={{ fontWeight: 700, mb: 0.5, color: '#374151' }}>
Industry:
</Typography>
<Typography variant="body2" color="text.secondary">
{example.industry}
</Typography>
</Box>
</CardActionArea>
</Card>
</Grid>
))}
</Grid>
</DialogContent>
<DialogActions sx={{ borderTop: '1px solid #F3F4F6', p: 2 }}>
<Button onClick={() => setShowExamples(false)} color="inherit">Close</Button>
</DialogActions>
</Dialog>
</Box>
);
};

View File

@@ -42,6 +42,7 @@ import {
performAnalysis,
fetchLastAnalysis
} from './WebsiteStep/utils';
import { onboardingCache, WebsiteIntakeCache } from '../../services/onboardingCache';
interface WebsiteStepProps {
onContinue: (stepData?: any) => void;
@@ -369,7 +370,15 @@ const WebsiteStep: React.FC<WebsiteStepProps> = ({ onContinue, updateHeaderConte
analysis: analysis,
useAnalysisForGenAI: useAnalysisForGenAI
};
const cachedIntake = onboardingCache.getStepData(2) as WebsiteIntakeCache | undefined;
onboardingCache.saveStepData(2, {
...cachedIntake,
website: fixedUrl,
analysis: analysis,
hasWebsite: true
});
// Store in localStorage for Step 3 (Competitor Analysis)
localStorage.setItem('website_url', fixedUrl);
localStorage.setItem('website_analysis_data', JSON.stringify(analysis));
@@ -619,6 +628,15 @@ const WebsiteStep: React.FC<WebsiteStepProps> = ({ onContinue, updateHeaderConte
businessData: businessData
};
const cachedIntake = onboardingCache.getStepData(2) as WebsiteIntakeCache | undefined;
onboardingCache.saveStepData(2, {
...cachedIntake,
website: fixUrlFormat(website),
analysis: analysis,
businessInfo: businessData,
hasWebsite: false
});
// Store in localStorage for Step 3 (Competitor Analysis)
const fixedUrl = fixUrlFormat(website);
if (fixedUrl) {

View File

@@ -3,6 +3,21 @@
* Manages client-side caching of onboarding data until final submission
*/
export type PageImages = {
home?: string;
about?: string;
contact?: string;
products?: string;
};
export interface WebsiteIntakeCache {
website?: string;
analysis?: any;
businessInfo?: any;
hasWebsite?: boolean;
page_images?: PageImages;
}
interface OnboardingCacheData {
step1?: {
apiKeys?: Record<string, string>;
@@ -12,6 +27,7 @@ interface OnboardingCacheData {
website?: string;
analysis?: any;
businessInfo?: any;
websiteIntake?: any;
hasWebsite?: boolean;
};
step3?: {