Files
igny8/frontend/src/pages/Sites/Builder/steps/BusinessDetailsStep.tsx
2025-11-20 21:29:14 +05:00

299 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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 were 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;