From 62d9c2e8364f33a71fd2715f5de51b1fde5d429a Mon Sep 17 00:00:00 2001 From: ajaysi Date: Sun, 1 Mar 2026 18:42:27 +0530 Subject: [PATCH] feat: Cherry-pick Website Maker feature from remote backup --- .../BusinessDescriptionStep.tsx | 639 +++++++++--------- .../OnboardingWizard/WebsiteStep.tsx | 20 +- frontend/src/services/onboardingCache.ts | 16 + 3 files changed, 349 insertions(+), 326 deletions(-) diff --git a/frontend/src/components/OnboardingWizard/BusinessDescriptionStep.tsx b/frontend/src/components/OnboardingWizard/BusinessDescriptionStep.tsx index 32886af3..98e74ce4 100644 --- a/frontend/src/components/OnboardingWizard/BusinessDescriptionStep.tsx +++ b/frontend/src/components/OnboardingWizard/BusinessDescriptionStep.tsx @@ -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 = ({ onBack, onContinue }) => { - const [formData, setFormData] = useState({ - business_description: '', - industry: '', + const [intakeForm, setIntakeForm] = useState({ + 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(null); @@ -74,48 +84,46 @@ const BusinessDescriptionStep: React.FC = ({ 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) => { + const handleIntakeChange = (e: React.ChangeEvent) => { 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 = ({ onBac return ( - - - - Tell us about your business - - - Provide details about your business to help ALwrity tailor its services. - - - - + + Create your AI-generated website + + + 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. + = ({ onBac {error && {error}} {success && }>{success}} - - - - - ), - }} - 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' }, - }} - /> - - - - - - - ), - }} - 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' }, - }} - /> - - - - - - - ), - }} - 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' }, - }} - /> - - - - - - - ), - }} - 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' }, - }} - /> - + + + + {templateOptions.map(option => ( + + {option.label} + + ))} + + + {geoScopeOptions.map(option => ( + + {option.label} + + ))} + + + + + {audienceTypeOptions.map(option => ( + + {option.label} + + ))} + + + + + + + Contact details (weโ€™ll use your account email if left blank) + + + + + + + Optional: competitor URLs (1-3) + + + {intakeForm.template_type === 'shop' && ( + <> + + + Product images + + + {productAssetOptions.map(option => ( + + {option.label} + + ))} + + + + + + )} @@ -333,95 +405,12 @@ const BusinessDescriptionStep: React.FC = ({ onBac color="primary" onClick={handleSaveAndContinue} endIcon={loading ? : } - 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'} - {/* Examples Modal */} - setShowExamples(false)} - maxWidth="md" - fullWidth - PaperProps={{ - sx: { borderRadius: '16px' } - }} - > - - - - Select an Example - - - Click a card to populate the form - - - - - {BUSINESS_EXAMPLES.map((example, index) => ( - - - handleExampleSelect(example)} - sx={{ height: '100%', p: 2, display: 'flex', flexDirection: 'column', alignItems: 'flex-start', justifyContent: 'flex-start' }} - > - - - - Description: - - - {example.description} - - - - - Industry: - - - {example.industry} - - - - - - ))} - - - - - - ); }; diff --git a/frontend/src/components/OnboardingWizard/WebsiteStep.tsx b/frontend/src/components/OnboardingWizard/WebsiteStep.tsx index b50a512e..c82cf559 100644 --- a/frontend/src/components/OnboardingWizard/WebsiteStep.tsx +++ b/frontend/src/components/OnboardingWizard/WebsiteStep.tsx @@ -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 = ({ 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 = ({ 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) { diff --git a/frontend/src/services/onboardingCache.ts b/frontend/src/services/onboardingCache.ts index a9931381..fe9c4534 100644 --- a/frontend/src/services/onboardingCache.ts +++ b/frontend/src/services/onboardingCache.ts @@ -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; @@ -12,6 +27,7 @@ interface OnboardingCacheData { website?: string; analysis?: any; businessInfo?: any; + websiteIntake?: any; hasWebsite?: boolean; }; step3?: {