feat: Cherry-pick Website Maker feature from remote backup
This commit is contained in:
@@ -1,32 +1,6 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import {
|
import { Box, Button, TextField, Typography, Card, CardContent, CircularProgress, Alert, MenuItem, Divider } from '@mui/material';
|
||||||
Box,
|
import { ArrowBack as ArrowBackIcon, Save as SaveIcon, CheckCircle as CheckCircleIcon } from '@mui/icons-material';
|
||||||
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 { businessInfoApi, BusinessInfo } from '../../api/businessInfo';
|
import { businessInfoApi, BusinessInfo } from '../../api/businessInfo';
|
||||||
import { onboardingCache } from '../../services/onboardingCache';
|
import { onboardingCache } from '../../services/onboardingCache';
|
||||||
|
|
||||||
@@ -35,36 +9,72 @@ interface BusinessDescriptionStepProps {
|
|||||||
onContinue: (businessData?: BusinessInfo) => void;
|
onContinue: (businessData?: BusinessInfo) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const BUSINESS_EXAMPLES = [
|
interface WebsiteIntakeForm {
|
||||||
{
|
business_name: string;
|
||||||
title: "SaaS Tech Startup",
|
business_summary: string;
|
||||||
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.",
|
template_type: 'blog' | 'profile' | 'shop' | 'dont_know';
|
||||||
industry: "Technology / Software",
|
geo_scope: 'global' | 'local' | 'hyper_local' | 'dont_know';
|
||||||
target_audience: "Remote-first companies, Project Managers, Product Owners, Startups",
|
primary_offerings: string;
|
||||||
business_goals: "Increase user acquisition by 20% in Q3, improve user retention, and launch a new mobile app."
|
target_audience: string;
|
||||||
},
|
audience_type: 'B2B' | 'B2C' | 'Both' | 'dont_know';
|
||||||
{
|
brand_tone: string;
|
||||||
title: "Artisanal Coffee Shop",
|
brand_adjectives: string;
|
||||||
description: "A cozy local coffee shop specializing in single-origin beans and homemade pastries, serving the downtown community with a focus on sustainability.",
|
avoid_terms: string;
|
||||||
industry: "Food & Beverage / Hospitality",
|
competitor_urls: string;
|
||||||
target_audience: "Local residents, office workers, coffee enthusiasts, students",
|
contact_email: string;
|
||||||
business_goals: "Build a loyal customer base, increase foot traffic during weekdays, and expand catering services for local offices."
|
contact_phone: string;
|
||||||
},
|
contact_location: string;
|
||||||
{
|
product_asset_mode: 'upload' | 'generate' | 'dont_know';
|
||||||
title: "Digital Marketing Agency",
|
product_asset_urls: string;
|
||||||
description: "A full-service digital marketing agency helping small businesses grow their online presence through SEO, PPC, and content marketing strategies.",
|
product_asset_ids: string;
|
||||||
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."
|
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 BusinessDescriptionStep: React.FC<BusinessDescriptionStepProps> = ({ onBack, onContinue }) => {
|
||||||
const [formData, setFormData] = useState<BusinessInfo>({
|
const [intakeForm, setIntakeForm] = useState<WebsiteIntakeForm>({
|
||||||
business_description: '',
|
business_name: '',
|
||||||
industry: '',
|
business_summary: '',
|
||||||
|
template_type: 'dont_know',
|
||||||
|
geo_scope: 'dont_know',
|
||||||
|
primary_offerings: '',
|
||||||
target_audience: '',
|
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 [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
@@ -74,48 +84,46 @@ const BusinessDescriptionStep: React.FC<BusinessDescriptionStepProps> = ({ onBac
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log('🔄 BusinessDescriptionStep mounted. Loading cached data...');
|
console.log('🔄 BusinessDescriptionStep mounted. Loading cached data...');
|
||||||
const cachedData = onboardingCache.getStepData(2)?.businessInfo;
|
const cachedData = onboardingCache.getStepData(2)?.businessInfo;
|
||||||
|
const cachedIntake = onboardingCache.getStepData(2)?.websiteIntake;
|
||||||
if (cachedData) {
|
if (cachedData) {
|
||||||
setFormData(cachedData);
|
|
||||||
console.log('✅ Loaded cached business info:', cachedData);
|
console.log('✅ Loaded cached business info:', cachedData);
|
||||||
} else {
|
} else {
|
||||||
console.log('ℹ️ No cached business info found.');
|
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]) => {
|
const handleIntakeChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
||||||
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 { name, value } = e.target;
|
const { name, value } = e.target;
|
||||||
setFormData(prev => ({ ...prev, [name]: value }));
|
setIntakeForm(prev => ({ ...prev, [name]: value }));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSaveAndContinue = async () => {
|
const handleSaveAndContinue = async () => {
|
||||||
setError(null);
|
setError(null);
|
||||||
setSuccess(null);
|
setSuccess(null);
|
||||||
setLoading(true);
|
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 {
|
try {
|
||||||
// Simulate user_id for now, replace with actual user_id from auth context later
|
// Simulate user_id for now, replace with actual user_id from auth context later
|
||||||
const userId = 1;
|
const userId = 1;
|
||||||
const dataToSave = { ...formData, user_id: userId };
|
const dataToSave = { ...derivedBusinessInfo, user_id: userId };
|
||||||
|
|
||||||
const response = await businessInfoApi.saveBusinessInfo(dataToSave);
|
const response = await businessInfoApi.saveBusinessInfo(dataToSave);
|
||||||
console.log('✅ Business info saved to DB:', response);
|
console.log('✅ Business info saved to DB:', response);
|
||||||
setSuccess('Business information saved successfully!');
|
setSuccess('Business information saved successfully!');
|
||||||
|
|
||||||
// Also save to cache for consistency with other steps
|
// 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.');
|
console.log('✅ Business info saved to cache.');
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -131,25 +139,12 @@ const BusinessDescriptionStep: React.FC<BusinessDescriptionStepProps> = ({ onBac
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ mt: 4 }}>
|
<Box sx={{ mt: 4 }}>
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', mb: 3 }}>
|
<Typography variant="h5" gutterBottom>
|
||||||
<Box>
|
Create your AI-generated website
|
||||||
<Typography variant="h5" gutterBottom sx={{ fontWeight: 600, color: '#111827' }}>
|
</Typography>
|
||||||
Tell us about your business
|
<Typography variant="body1" color="textSecondary" sx={{ mb: 3 }}>
|
||||||
</Typography>
|
Share a few details (even 3-4 lines is enough). If you are unsure, choose “Don't know yet” and we’ll fill the gaps with AI.
|
||||||
<Typography variant="body1" color="text.secondary">
|
</Typography>
|
||||||
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>
|
|
||||||
|
|
||||||
<Card sx={{
|
<Card sx={{
|
||||||
p: 3,
|
p: 3,
|
||||||
@@ -164,156 +159,233 @@ const BusinessDescriptionStep: React.FC<BusinessDescriptionStepProps> = ({ onBac
|
|||||||
{error && <Alert severity="error" sx={{ mb: 2 }}>{error}</Alert>}
|
{error && <Alert severity="error" sx={{ mb: 2 }}>{error}</Alert>}
|
||||||
{success && <Alert severity="success" sx={{ mb: 2 }} icon={<CheckCircleIcon fontSize="inherit" />}>{success}</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
|
||||||
<TextField
|
label="Business Name"
|
||||||
label="Business Description"
|
name="business_name"
|
||||||
name="business_description"
|
value={intakeForm.business_name}
|
||||||
value={formData.business_description}
|
onChange={handleIntakeChange}
|
||||||
onChange={handleChange}
|
fullWidth
|
||||||
fullWidth
|
margin="normal"
|
||||||
multiline
|
placeholder="e.g., Maple Street Homestays"
|
||||||
rows={4}
|
disabled={loading}
|
||||||
margin="normal"
|
/>
|
||||||
required
|
<TextField
|
||||||
placeholder="e.g., We provide AI-powered project management tools for remote teams..."
|
label="Describe what you do (3-4 lines)"
|
||||||
helperText={`${formData.business_description.length}/1000 characters`}
|
name="business_summary"
|
||||||
inputProps={{ maxLength: 1000 }}
|
value={intakeForm.business_summary}
|
||||||
disabled={loading}
|
onChange={handleIntakeChange}
|
||||||
InputProps={{
|
fullWidth
|
||||||
startAdornment: (
|
multiline
|
||||||
<InputAdornment position="start" sx={{ mt: -3 }}>
|
rows={4}
|
||||||
<HelpIcon color="action" fontSize="small" />
|
margin="normal"
|
||||||
</InputAdornment>
|
required
|
||||||
),
|
helperText={`${intakeForm.business_summary.length}/1000 characters`}
|
||||||
}}
|
inputProps={{ maxLength: 1000 }}
|
||||||
sx={{
|
disabled={loading}
|
||||||
'& .MuiOutlinedInput-root': {
|
/>
|
||||||
bgcolor: '#F9FAFB',
|
<TextField
|
||||||
color: '#111827',
|
label="Website template"
|
||||||
borderRadius: '12px',
|
name="template_type"
|
||||||
transition: 'all 0.2s',
|
value={intakeForm.template_type}
|
||||||
'& fieldset': { borderColor: '#E5E7EB' },
|
onChange={handleIntakeChange}
|
||||||
'&:hover fieldset': { borderColor: '#6C5CE7' },
|
select
|
||||||
'&.Mui-focused fieldset': { borderColor: '#6C5CE7', borderWidth: '2px' },
|
fullWidth
|
||||||
'&.Mui-focused': { bgcolor: '#FFFFFF', boxShadow: '0 0 0 4px rgba(108, 92, 231, 0.1)' }
|
margin="normal"
|
||||||
},
|
disabled={loading}
|
||||||
'& .MuiInputLabel-root': { color: '#6B7280' },
|
>
|
||||||
'& .MuiInputLabel-root.Mui-focused': { color: '#6C5CE7' },
|
{templateOptions.map(option => (
|
||||||
}}
|
<MenuItem key={option.value} value={option.value}>
|
||||||
/>
|
{option.label}
|
||||||
</Tooltip>
|
</MenuItem>
|
||||||
|
))}
|
||||||
<Tooltip title="What industry or sector does your business operate in?" arrow placement="top">
|
</TextField>
|
||||||
<TextField
|
<TextField
|
||||||
label="Industry"
|
label="Audience scope"
|
||||||
name="industry"
|
name="geo_scope"
|
||||||
value={formData.industry}
|
value={intakeForm.geo_scope}
|
||||||
onChange={handleChange}
|
onChange={handleIntakeChange}
|
||||||
fullWidth
|
select
|
||||||
margin="normal"
|
fullWidth
|
||||||
placeholder="e.g., Technology, Retail, Healthcare..."
|
margin="normal"
|
||||||
helperText={`${(formData.industry || '').length}/100 characters`}
|
disabled={loading}
|
||||||
inputProps={{ maxLength: 100 }}
|
>
|
||||||
disabled={loading}
|
{geoScopeOptions.map(option => (
|
||||||
InputProps={{
|
<MenuItem key={option.value} value={option.value}>
|
||||||
startAdornment: (
|
{option.label}
|
||||||
<InputAdornment position="start">
|
</MenuItem>
|
||||||
<HelpIcon color="action" fontSize="small" />
|
))}
|
||||||
</InputAdornment>
|
</TextField>
|
||||||
),
|
<TextField
|
||||||
}}
|
label="Primary offerings (comma separated)"
|
||||||
sx={{
|
name="primary_offerings"
|
||||||
'& .MuiOutlinedInput-root': {
|
value={intakeForm.primary_offerings}
|
||||||
bgcolor: '#F9FAFB',
|
onChange={handleIntakeChange}
|
||||||
color: '#111827',
|
fullWidth
|
||||||
borderRadius: '12px',
|
margin="normal"
|
||||||
transition: 'all 0.2s',
|
placeholder="e.g., Short stays, airport pickup, local tours"
|
||||||
'& fieldset': { borderColor: '#E5E7EB' },
|
disabled={loading}
|
||||||
'&:hover fieldset': { borderColor: '#6C5CE7' },
|
/>
|
||||||
'&.Mui-focused fieldset': { borderColor: '#6C5CE7', borderWidth: '2px' },
|
<TextField
|
||||||
'&.Mui-focused': { bgcolor: '#FFFFFF', boxShadow: '0 0 0 4px rgba(108, 92, 231, 0.1)' }
|
label="Target audience"
|
||||||
},
|
name="target_audience"
|
||||||
'& .MuiInputLabel-root': { color: '#6B7280' },
|
value={intakeForm.target_audience}
|
||||||
'& .MuiInputLabel-root.Mui-focused': { color: '#6C5CE7' },
|
onChange={handleIntakeChange}
|
||||||
}}
|
fullWidth
|
||||||
/>
|
multiline
|
||||||
</Tooltip>
|
rows={2}
|
||||||
|
margin="normal"
|
||||||
<Tooltip title="Who are your ideal customers? Be specific about demographics, interests, or roles." arrow placement="top">
|
placeholder="e.g., Families visiting Pune for weddings"
|
||||||
<TextField
|
disabled={loading}
|
||||||
label="Target Audience"
|
/>
|
||||||
name="target_audience"
|
<TextField
|
||||||
value={formData.target_audience}
|
label="Audience type"
|
||||||
onChange={handleChange}
|
name="audience_type"
|
||||||
fullWidth
|
value={intakeForm.audience_type}
|
||||||
multiline
|
onChange={handleIntakeChange}
|
||||||
rows={2}
|
select
|
||||||
margin="normal"
|
fullWidth
|
||||||
placeholder="e.g., Small business owners, marketing managers, eco-conscious consumers..."
|
margin="normal"
|
||||||
helperText={`${(formData.target_audience || '').length}/500 characters`}
|
disabled={loading}
|
||||||
inputProps={{ maxLength: 500 }}
|
>
|
||||||
disabled={loading}
|
{audienceTypeOptions.map(option => (
|
||||||
InputProps={{
|
<MenuItem key={option.value} value={option.value}>
|
||||||
startAdornment: (
|
{option.label}
|
||||||
<InputAdornment position="start" sx={{ mt: -1 }}>
|
</MenuItem>
|
||||||
<HelpIcon color="action" fontSize="small" />
|
))}
|
||||||
</InputAdornment>
|
</TextField>
|
||||||
),
|
<TextField
|
||||||
}}
|
label="Brand tone"
|
||||||
sx={{
|
name="brand_tone"
|
||||||
'& .MuiOutlinedInput-root': {
|
value={intakeForm.brand_tone}
|
||||||
bgcolor: '#F9FAFB',
|
onChange={handleIntakeChange}
|
||||||
color: '#111827',
|
fullWidth
|
||||||
borderRadius: '12px',
|
margin="normal"
|
||||||
transition: 'all 0.2s',
|
placeholder="Friendly, premium, minimal"
|
||||||
'& fieldset': { borderColor: '#E5E7EB' },
|
disabled={loading}
|
||||||
'&:hover fieldset': { borderColor: '#6C5CE7' },
|
/>
|
||||||
'&.Mui-focused fieldset': { borderColor: '#6C5CE7', borderWidth: '2px' },
|
<TextField
|
||||||
'&.Mui-focused': { bgcolor: '#FFFFFF', boxShadow: '0 0 0 4px rgba(108, 92, 231, 0.1)' }
|
label="Brand adjectives (comma separated)"
|
||||||
},
|
name="brand_adjectives"
|
||||||
'& .MuiInputLabel-root': { color: '#6B7280' },
|
value={intakeForm.brand_adjectives}
|
||||||
'& .MuiInputLabel-root.Mui-focused': { color: '#6C5CE7' },
|
onChange={handleIntakeChange}
|
||||||
}}
|
fullWidth
|
||||||
/>
|
margin="normal"
|
||||||
</Tooltip>
|
placeholder="e.g., cozy, reliable, modern"
|
||||||
|
disabled={loading}
|
||||||
<Tooltip title="What are your main objectives for the next 6-12 months?" arrow placement="top">
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
label="Business Goals"
|
label="Avoid words or styles"
|
||||||
name="business_goals"
|
name="avoid_terms"
|
||||||
value={formData.business_goals}
|
value={intakeForm.avoid_terms}
|
||||||
onChange={handleChange}
|
onChange={handleIntakeChange}
|
||||||
fullWidth
|
fullWidth
|
||||||
multiline
|
margin="normal"
|
||||||
rows={3}
|
placeholder="e.g., pushy sales language"
|
||||||
margin="normal"
|
disabled={loading}
|
||||||
placeholder="e.g., Increase brand awareness, generate more leads, launch a new product..."
|
/>
|
||||||
helperText={`${(formData.business_goals || '').length}/1000 characters`}
|
<Divider sx={{ my: 3 }} />
|
||||||
inputProps={{ maxLength: 1000 }}
|
<Typography variant="subtitle1" sx={{ mb: 1 }}>
|
||||||
disabled={loading}
|
Contact details (we’ll use your account email if left blank)
|
||||||
InputProps={{
|
</Typography>
|
||||||
startAdornment: (
|
<TextField
|
||||||
<InputAdornment position="start" sx={{ mt: -2 }}>
|
label="Contact email"
|
||||||
<HelpIcon color="action" fontSize="small" />
|
name="contact_email"
|
||||||
</InputAdornment>
|
value={intakeForm.contact_email}
|
||||||
),
|
onChange={handleIntakeChange}
|
||||||
}}
|
fullWidth
|
||||||
sx={{
|
margin="normal"
|
||||||
'& .MuiOutlinedInput-root': {
|
placeholder="name@business.com"
|
||||||
bgcolor: '#F9FAFB',
|
disabled={loading}
|
||||||
color: '#111827',
|
/>
|
||||||
borderRadius: '12px',
|
<TextField
|
||||||
transition: 'all 0.2s',
|
label="Contact phone"
|
||||||
'& fieldset': { borderColor: '#E5E7EB' },
|
name="contact_phone"
|
||||||
'&:hover fieldset': { borderColor: '#6C5CE7' },
|
value={intakeForm.contact_phone}
|
||||||
'&.Mui-focused fieldset': { borderColor: '#6C5CE7', borderWidth: '2px' },
|
onChange={handleIntakeChange}
|
||||||
'&.Mui-focused': { bgcolor: '#FFFFFF', boxShadow: '0 0 0 4px rgba(108, 92, 231, 0.1)' }
|
fullWidth
|
||||||
},
|
margin="normal"
|
||||||
'& .MuiInputLabel-root': { color: '#6B7280' },
|
placeholder="+91 90000 00000"
|
||||||
'& .MuiInputLabel-root.Mui-focused': { color: '#6C5CE7' },
|
disabled={loading}
|
||||||
}}
|
/>
|
||||||
/>
|
<TextField
|
||||||
</Tooltip>
|
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>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
@@ -333,95 +405,12 @@ const BusinessDescriptionStep: React.FC<BusinessDescriptionStepProps> = ({ onBac
|
|||||||
color="primary"
|
color="primary"
|
||||||
onClick={handleSaveAndContinue}
|
onClick={handleSaveAndContinue}
|
||||||
endIcon={loading ? <CircularProgress size={20} color="inherit" /> : <SaveIcon />}
|
endIcon={loading ? <CircularProgress size={20} color="inherit" /> : <SaveIcon />}
|
||||||
disabled={loading || !formData.business_description}
|
disabled={loading || !intakeForm.business_summary}
|
||||||
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)' }
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{loading ? 'Saving...' : 'Save & Continue'}
|
{loading ? 'Saving...' : 'Save & Continue'}
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</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>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ import {
|
|||||||
performAnalysis,
|
performAnalysis,
|
||||||
fetchLastAnalysis
|
fetchLastAnalysis
|
||||||
} from './WebsiteStep/utils';
|
} from './WebsiteStep/utils';
|
||||||
|
import { onboardingCache, WebsiteIntakeCache } from '../../services/onboardingCache';
|
||||||
|
|
||||||
interface WebsiteStepProps {
|
interface WebsiteStepProps {
|
||||||
onContinue: (stepData?: any) => void;
|
onContinue: (stepData?: any) => void;
|
||||||
@@ -369,7 +370,15 @@ const WebsiteStep: React.FC<WebsiteStepProps> = ({ onContinue, updateHeaderConte
|
|||||||
analysis: analysis,
|
analysis: analysis,
|
||||||
useAnalysisForGenAI: useAnalysisForGenAI
|
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)
|
// Store in localStorage for Step 3 (Competitor Analysis)
|
||||||
localStorage.setItem('website_url', fixedUrl);
|
localStorage.setItem('website_url', fixedUrl);
|
||||||
localStorage.setItem('website_analysis_data', JSON.stringify(analysis));
|
localStorage.setItem('website_analysis_data', JSON.stringify(analysis));
|
||||||
@@ -619,6 +628,15 @@ const WebsiteStep: React.FC<WebsiteStepProps> = ({ onContinue, updateHeaderConte
|
|||||||
businessData: businessData
|
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)
|
// Store in localStorage for Step 3 (Competitor Analysis)
|
||||||
const fixedUrl = fixUrlFormat(website);
|
const fixedUrl = fixUrlFormat(website);
|
||||||
if (fixedUrl) {
|
if (fixedUrl) {
|
||||||
|
|||||||
@@ -3,6 +3,21 @@
|
|||||||
* Manages client-side caching of onboarding data until final submission
|
* 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 {
|
interface OnboardingCacheData {
|
||||||
step1?: {
|
step1?: {
|
||||||
apiKeys?: Record<string, string>;
|
apiKeys?: Record<string, string>;
|
||||||
@@ -12,6 +27,7 @@ interface OnboardingCacheData {
|
|||||||
website?: string;
|
website?: string;
|
||||||
analysis?: any;
|
analysis?: any;
|
||||||
businessInfo?: any;
|
businessInfo?: any;
|
||||||
|
websiteIntake?: any;
|
||||||
hasWebsite?: boolean;
|
hasWebsite?: boolean;
|
||||||
};
|
};
|
||||||
step3?: {
|
step3?: {
|
||||||
|
|||||||
Reference in New Issue
Block a user