Core Features: - Add Quick Setup Wizard for new users with multi-step flow - Implement distraction-free onboarding layout (no sidebar/header) - Create OnboardingController API endpoint for saving settings - Redirect new users to /setup automatically on first admin access Onboarding Components: - StepMode: Select between full/minimal store modes - StepHomepage: Choose or auto-create homepage - StepAppearance: Configure container width and primary color - StepProgress: Visual progress indicator Navigation & Routing: - Fix Help page links to use react-router navigation (prevent full reload) - Update onboarding completion redirect to /appearance/pages - Add manual onboarding access via Settings > Store Details UI/UX Improvements: - Enable dark mode support for Page Editor - Fix page title rendering in onboarding dropdown - Improve title fallback logic (title.rendered, title, post_title) Type Safety: - Unify PageItem interface across all components - Add 'default' to containerWidth type definition - Add missing properties (permalink_base, has_template, icon) Files Modified: - includes/Api/OnboardingController.php - includes/Api/Routes.php - includes/Admin/Assets.php - admin-spa/src/App.tsx - admin-spa/src/routes/Onboarding/* - admin-spa/src/routes/Help/DocContent.tsx - admin-spa/src/routes/Settings/Store.tsx - admin-spa/src/routes/Appearance/Pages/*
160 lines
7.4 KiB
TypeScript
160 lines
7.4 KiB
TypeScript
import React, { useState } from 'react';
|
|
import { useNavigate } from 'react-router-dom';
|
|
import { toast } from 'sonner';
|
|
import { ArrowRight, ArrowLeft, Check, Loader2, Rocket } from 'lucide-react';
|
|
import { StepMode } from './components/StepMode';
|
|
import { StepHomepage } from './components/StepHomepage';
|
|
import { StepAppearance } from './components/StepAppearance';
|
|
import { StepProgress } from './components/StepProgress';
|
|
|
|
|
|
export default function Onboarding() {
|
|
const navigate = useNavigate();
|
|
const [step, setStep] = useState(0);
|
|
const [loading, setLoading] = useState(false);
|
|
const [data, setData] = useState({
|
|
mode: 'full',
|
|
pageId: '',
|
|
createMagicPage: false,
|
|
containerWidth: 'max-w-6xl',
|
|
primaryColor: '#000000',
|
|
});
|
|
|
|
const steps = [
|
|
{ component: StepMode, title: 'Mode' },
|
|
{ component: StepHomepage, title: 'Homepage' },
|
|
{ component: StepAppearance, title: 'Appearance' },
|
|
];
|
|
|
|
const handleNext = async () => {
|
|
if (step < steps.length - 1) {
|
|
if (step === 1 && !data.createMagicPage && !data.pageId) {
|
|
toast.error('Please select a page or choose auto-create');
|
|
return;
|
|
}
|
|
setStep(s => s + 1);
|
|
} else {
|
|
// Final Submit
|
|
setLoading(true);
|
|
try {
|
|
const payload = {
|
|
mode: data.mode,
|
|
create_home_page: data.createMagicPage,
|
|
entry_page_id: data.pageId,
|
|
container_width: data.containerWidth,
|
|
primary_color: data.primaryColor
|
|
};
|
|
|
|
const res = await fetch((window as any).WNW_CONFIG?.restUrl + '/onboarding/complete', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-WP-Nonce': (window as any).WNW_CONFIG?.nonce
|
|
},
|
|
body: JSON.stringify(payload)
|
|
});
|
|
|
|
const json = await res.json();
|
|
|
|
if (json.success) {
|
|
toast.success('Store setup complete!');
|
|
// Update global config to prevent showing onboarding again
|
|
if ((window as any).WNW_CONFIG) {
|
|
(window as any).WNW_CONFIG.onboardingCompleted = true;
|
|
}
|
|
|
|
navigate('/appearance/pages');
|
|
} else {
|
|
throw new Error(json.message || 'Setup failed');
|
|
}
|
|
|
|
} catch (e: any) {
|
|
toast.error(e.message || 'Something went wrong');
|
|
setLoading(false);
|
|
}
|
|
}
|
|
};
|
|
|
|
const CurrentStepComponent = steps[step].component;
|
|
|
|
return (
|
|
<div className="min-h-screen flex flex-col items-center justify-center p-4 bg-background">
|
|
<div className="w-full max-w-4xl bg-card border border-border shadow-xl rounded-2xl overflow-hidden flex flex-col md:flex-row min-h-[600px]">
|
|
{/* Sidebar / Info Panel */}
|
|
<div className="bg-muted/30 p-8 md:w-1/3 border-b md:border-b-0 md:border-r border-border flex flex-col justify-between">
|
|
<div>
|
|
<div className="flex items-center gap-2 mb-8 text-primary">
|
|
<Rocket className="w-6 h-6" />
|
|
<span className="font-bold text-xl tracking-tight">WooNooW Setup</span>
|
|
</div>
|
|
|
|
<div className="space-y-4">
|
|
{steps.map((s, i) => (
|
|
<div key={i} className={`flex items-center gap-3 ${i === step ? 'text-foreground' : 'text-muted-foreground'}`}>
|
|
<div className={`w-8 h-8 rounded-full flex items-center justify-center text-sm font-medium border ${i < step ? 'bg-primary text-primary-foreground border-primary' :
|
|
i === step ? 'bg-background border-primary text-primary' :
|
|
'bg-muted border-border'
|
|
}`}>
|
|
{i < step ? <Check className="w-4 h-4" /> : i + 1}
|
|
</div>
|
|
<span className={i === step ? 'font-medium' : ''}>{s.title}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="mt-8">
|
|
<p className="text-xs text-muted-foreground">step {step + 1} of {steps.length}</p>
|
|
<div className="mt-2">
|
|
<StepProgress currentStep={step} totalSteps={steps.length} />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Main Content Area */}
|
|
<div className="flex-1 p-8 flex flex-col">
|
|
<div className="flex-1">
|
|
<CurrentStepComponent
|
|
// Props mapping is dynamic but typed loosely here for simplicity
|
|
value={data.mode}
|
|
onChange={(val: string) => setData(d => ({ ...d, mode: val }))}
|
|
|
|
// Homepage props
|
|
pageId={data.pageId}
|
|
createMagicPage={data.createMagicPage}
|
|
onPageChange={(id: string | number) => setData(d => ({ ...d, pageId: String(id) }))}
|
|
onMagicChange={(enabled: boolean) => setData(d => ({ ...d, createMagicPage: enabled }))}
|
|
|
|
// Appearance props
|
|
containerWidth={data.containerWidth}
|
|
primaryColor={data.primaryColor}
|
|
onWidthChange={(w: string) => setData(d => ({ ...d, containerWidth: w }))}
|
|
onColorChange={(c: string) => setData(d => ({ ...d, primaryColor: c }))}
|
|
/>
|
|
</div>
|
|
|
|
<div className="flex justify-between items-center mt-8 pt-4 border-t border-border">
|
|
<button
|
|
onClick={() => setStep(s => Math.max(0, s - 1))}
|
|
disabled={step === 0 || loading}
|
|
className="flex items-center gap-2 px-4 py-2 rounded-md hover:bg-accent disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
|
>
|
|
<ArrowLeft className="w-4 h-4" /> Back
|
|
</button>
|
|
|
|
<button
|
|
onClick={handleNext}
|
|
disabled={loading}
|
|
className="flex items-center gap-2 px-6 py-2 rounded-md bg-primary text-primary-foreground hover:bg-primary/90 disabled:opacity-50 font-medium transition-all shadow-sm"
|
|
>
|
|
{loading ? <Loader2 className="w-4 h-4 animate-spin" /> : null}
|
|
{step === steps.length - 1 ? 'Launch Store' : 'Next'}
|
|
{!loading && step < steps.length - 1 && <ArrowRight className="w-4 h-4" />}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|