299 lines
11 KiB
TypeScript
299 lines
11 KiB
TypeScript
/**
|
||
* Step 1: Business Details
|
||
* Site type selection, hosting detection, brand inputs
|
||
*
|
||
* Supports both:
|
||
* - Stage 1 Wizard: data, onChange, metadata, selectedSectors
|
||
* - Stage 2 Workflow: blueprintId
|
||
*/
|
||
import { useState, useEffect } from 'react';
|
||
import { useBuilderWorkflowStore } from '../../../../store/builderWorkflowStore';
|
||
import { fetchSiteBlueprintById, updateSiteBlueprint, SiteBlueprint } from '../../../../services/api';
|
||
import { Card, CardDescription, CardTitle } from '../../../../components/ui/card';
|
||
import Button from '../../../../components/ui/button/Button';
|
||
import Input from '../../../../components/form/input/InputField';
|
||
import Alert from '../../../../components/ui/alert/Alert';
|
||
import { BoltIcon, GridIcon } from '../../../../icons';
|
||
import type { BuilderFormData, SiteBuilderMetadata } from '../../../../types/siteBuilder';
|
||
|
||
// Stage 1 Wizard props
|
||
interface Stage1Props {
|
||
data: BuilderFormData;
|
||
onChange: <K extends keyof BuilderFormData>(key: K, value: BuilderFormData[K]) => void;
|
||
metadata?: SiteBuilderMetadata;
|
||
selectedSectors?: Array<{ id: number; name: string }>;
|
||
blueprintId?: never;
|
||
}
|
||
|
||
// Stage 2 Workflow props
|
||
interface Stage2Props {
|
||
blueprintId: number;
|
||
data?: never;
|
||
onChange?: never;
|
||
metadata?: never;
|
||
selectedSectors?: never;
|
||
}
|
||
|
||
type BusinessDetailsStepProps = Stage1Props | Stage2Props;
|
||
|
||
export function BusinessDetailsStep(props: BusinessDetailsStepProps) {
|
||
// Check if this is Stage 2 (has blueprintId)
|
||
const isStage2 = 'blueprintId' in props && props.blueprintId !== undefined;
|
||
|
||
// Stage 2 implementation
|
||
if (isStage2) {
|
||
return <BusinessDetailsStepStage2 blueprintId={props.blueprintId} />;
|
||
}
|
||
|
||
// Stage 1 implementation
|
||
return <BusinessDetailsStepStage1
|
||
data={props.data}
|
||
onChange={props.onChange}
|
||
metadata={props.metadata}
|
||
selectedSectors={props.selectedSectors}
|
||
/>;
|
||
}
|
||
|
||
// Stage 2 Workflow Component
|
||
function BusinessDetailsStepStage2({ blueprintId }: { blueprintId: number }) {
|
||
const { context, completeStep, loading } = useBuilderWorkflowStore();
|
||
const [blueprint, setBlueprint] = useState<SiteBlueprint | null>(null);
|
||
const [formData, setFormData] = useState({
|
||
name: '',
|
||
description: '',
|
||
hosting_type: 'igny8_sites' as const,
|
||
business_type: '',
|
||
});
|
||
const [saving, setSaving] = useState(false);
|
||
const [error, setError] = useState<string | null>(null);
|
||
|
||
useEffect(() => {
|
||
// Load blueprint data
|
||
fetchSiteBlueprintById(blueprintId)
|
||
.then(setBlueprint)
|
||
.catch(err => setError(err.message));
|
||
}, [blueprintId]);
|
||
|
||
useEffect(() => {
|
||
if (blueprint) {
|
||
setFormData({
|
||
name: blueprint.name || '',
|
||
description: blueprint.description || '',
|
||
hosting_type: (blueprint.hosting_type as any) || 'igny8_sites',
|
||
business_type: blueprint.config_json?.business_type || '',
|
||
});
|
||
}
|
||
}, [blueprint]);
|
||
|
||
const handleSave = async () => {
|
||
setSaving(true);
|
||
setError(null);
|
||
try {
|
||
const updated = await updateSiteBlueprint(blueprintId, {
|
||
name: formData.name,
|
||
description: formData.description,
|
||
hosting_type: formData.hosting_type,
|
||
config_json: {
|
||
...blueprint?.config_json,
|
||
business_type: formData.business_type,
|
||
},
|
||
});
|
||
setBlueprint(updated);
|
||
|
||
// Mark step as complete
|
||
await completeStep('business_details', {
|
||
blueprint_name: formData.name,
|
||
hosting_type: formData.hosting_type,
|
||
});
|
||
} catch (err: any) {
|
||
setError(err.message || 'Failed to save business details');
|
||
} finally {
|
||
setSaving(false);
|
||
}
|
||
};
|
||
|
||
const canProceed = formData.name.trim().length > 0;
|
||
|
||
return (
|
||
<Card variant="surface" padding="lg" className="space-y-6">
|
||
<div>
|
||
<CardTitle>Business details</CardTitle>
|
||
<CardDescription>
|
||
Tell us about your business and hosting preference to keep blueprints organized.
|
||
</CardDescription>
|
||
</div>
|
||
|
||
{error && (
|
||
<Alert variant="error" className="mt-4">
|
||
{error}
|
||
</Alert>
|
||
)}
|
||
|
||
<div className="mt-6 space-y-4">
|
||
<div className="grid gap-6 md:grid-cols-2">
|
||
<div>
|
||
<label className="text-sm font-medium text-gray-700 dark:text-gray-300 mb-2 block">
|
||
Site name *
|
||
</label>
|
||
<Input
|
||
value={formData.name}
|
||
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||
placeholder="Acme Robotics"
|
||
required
|
||
/>
|
||
</div>
|
||
<div>
|
||
<label className="text-sm font-medium text-gray-700 dark:text-gray-300 mb-2 block">
|
||
Hosting type
|
||
</label>
|
||
<select
|
||
value={formData.hosting_type}
|
||
onChange={(e) => setFormData({ ...formData, hosting_type: e.target.value as any })}
|
||
className="w-full rounded-xl border border-gray-200 bg-white px-3 py-2 text-sm text-gray-900 shadow-theme-xs dark:border-white/10 dark:bg-white/[0.03] dark:text-white"
|
||
>
|
||
<option value="igny8_sites">IGNY8 Sites</option>
|
||
<option value="wordpress">WordPress</option>
|
||
<option value="shopify">Shopify</option>
|
||
<option value="multi">Multiple destinations</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
|
||
<div>
|
||
<label className="text-sm font-medium text-gray-700 dark:text-gray-300 mb-2 block">
|
||
Business description
|
||
</label>
|
||
<textarea
|
||
value={formData.description}
|
||
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
|
||
className="w-full rounded-xl border border-gray-200 bg-white px-3 py-2 text-sm text-gray-900 shadow-theme-xs dark:border-white/10 dark:bg-white/[0.03] dark:text-white"
|
||
rows={3}
|
||
placeholder="Brief description of your business and what the site should cover"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="mt-6 flex justify-end">
|
||
<Button
|
||
onClick={handleSave}
|
||
disabled={!canProceed || saving || loading}
|
||
variant="primary"
|
||
startIcon={<GridIcon className="size-4" />}
|
||
>
|
||
{saving ? 'Saving…' : 'Save & continue'}
|
||
</Button>
|
||
</div>
|
||
|
||
{!canProceed && (
|
||
<Alert variant="warning" className="mt-4">
|
||
Please provide a site name to continue.
|
||
</Alert>
|
||
)}
|
||
</Card>
|
||
);
|
||
}
|
||
|
||
// Stage 1 Wizard Component
|
||
function BusinessDetailsStepStage1({
|
||
data,
|
||
onChange,
|
||
metadata,
|
||
selectedSectors
|
||
}: {
|
||
data: BuilderFormData;
|
||
onChange: <K extends keyof BuilderFormData>(key: K, value: BuilderFormData[K]) => void;
|
||
metadata?: SiteBuilderMetadata;
|
||
selectedSectors?: Array<{ id: number; name: string }>;
|
||
}) {
|
||
return (
|
||
<Card variant="surface" padding="lg" className="space-y-6">
|
||
<div>
|
||
<div className="inline-flex items-center gap-2 rounded-xl bg-gradient-to-br from-[var(--color-primary)] to-[var(--color-primary-dark)] px-3 py-1 text-xs font-semibold uppercase tracking-wide text-white">
|
||
<BoltIcon className="size-3.5" />
|
||
Business context
|
||
</div>
|
||
<h3 className="mt-3 text-xl font-semibold text-gray-900 dark:text-white">
|
||
Business details
|
||
</h3>
|
||
<p className="text-sm text-gray-500 dark:text-gray-400 max-w-2xl">
|
||
These inputs help the AI understand what we’re building. You can refine them later in the builder or site settings.
|
||
</p>
|
||
</div>
|
||
|
||
<div className="grid gap-6 lg:grid-cols-2">
|
||
<div className="space-y-4 rounded-2xl border border-gray-100 bg-white p-4 shadow-theme-sm dark:border-white/5 dark:bg-white/[0.02]">
|
||
<label className="text-sm font-semibold text-gray-900 dark:text-white">
|
||
Site name
|
||
</label>
|
||
<Input
|
||
value={data.siteName}
|
||
onChange={(e) => onChange('siteName', e.target.value)}
|
||
placeholder="Acme Robotics"
|
||
/>
|
||
<p className="text-xs text-gray-500 dark:text-gray-400">
|
||
Appears in dashboards, blueprints, and deployment metadata.
|
||
</p>
|
||
</div>
|
||
|
||
<div className="space-y-4 rounded-2xl border border-gray-100 bg-white p-4 shadow-theme-sm dark:border-white/5 dark:bg-white/[0.02]">
|
||
<label className="text-sm font-semibold text-gray-900 dark:text-white">
|
||
Target audience
|
||
</label>
|
||
<Input
|
||
value={data.targetAudience}
|
||
onChange={(e) => onChange('targetAudience', e.target.value)}
|
||
placeholder="Operations leaders at fast-scaling eCommerce brands"
|
||
/>
|
||
<p className="text-xs text-gray-500 dark:text-gray-400">
|
||
Helps the AI craft messaging, examples, and tone.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="grid gap-6 lg:grid-cols-3">
|
||
<div className="space-y-3 rounded-2xl border border-gray-100 bg-white p-4 shadow-theme-sm dark:border-white/5 dark:bg-white/[0.02]">
|
||
<label className="text-sm font-semibold text-gray-900 dark:text-white">
|
||
Business type
|
||
</label>
|
||
<Input
|
||
value={data.businessType}
|
||
onChange={(e) => onChange('businessType', e.target.value)}
|
||
placeholder="B2B SaaS platform"
|
||
/>
|
||
</div>
|
||
<div className="space-y-3 rounded-2xl border border-gray-100 bg-white p-4 shadow-theme-sm dark:border-white/5 dark:bg-white/[0.02]">
|
||
<label className="text-sm font-semibold text-gray-900 dark:text-white">
|
||
Industry
|
||
</label>
|
||
<Input
|
||
value={data.industry}
|
||
onChange={(e) => onChange('industry', e.target.value)}
|
||
placeholder="Supply chain automation"
|
||
/>
|
||
</div>
|
||
<div className="space-y-3 rounded-2xl border border-gray-100 bg-white p-4 shadow-theme-sm dark:border-white/5 dark:bg-white/[0.02]">
|
||
<label className="text-sm font-semibold text-gray-900 dark:text-white">
|
||
Hosting preference
|
||
</label>
|
||
<select
|
||
value={data.hostingType}
|
||
onChange={(e) => onChange('hostingType', e.target.value as BuilderFormData['hostingType'])}
|
||
className="w-full rounded-xl border border-gray-200 bg-white px-3 py-2 text-sm text-gray-900 shadow-theme-xs dark:border-white/10 dark:bg-white/[0.03] dark:text-white"
|
||
>
|
||
<option value="igny8_sites">IGNY8 Sites</option>
|
||
<option value="wordpress">WordPress</option>
|
||
<option value="shopify">Shopify</option>
|
||
<option value="multi">Multiple destinations</option>
|
||
</select>
|
||
<p className="text-xs text-gray-500 dark:text-gray-400">
|
||
Determines deployment targets and integration requirements.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</Card>
|
||
);
|
||
}
|
||
|
||
// Also export as default for WorkflowWizard compatibility
|
||
export default BusinessDetailsStep;
|