diff --git a/.agent/plans/onboarding_strategy.md b/.agent/plans/onboarding_strategy.md new file mode 100644 index 0000000..d560c3c --- /dev/null +++ b/.agent/plans/onboarding_strategy.md @@ -0,0 +1,39 @@ +# User Onboarding & Simplification Strategy + +## The Problem +The current "General Settings" screen presents too many technical decisions (SPA Mode, Entry Page, Container Width, Typography, Colors) at once. This creates analysis paralysis for new users who just want to "get the store running." + +## Recommended Solution: The "Quick Setup" Wizard +Instead of dumping users into the Settings screen, we implement a **Linear Onboarding Flow** that launches automatically on the first visit (or manually via "Setup Wizard" button). + +### Tech Stack +* **No new libraries** needed. We can build this using your existing `@radix-ui` components (Dialog, Cards, Button). +* **State**: Managed via simple React state or Zustand store. + +### The Flow (4 Steps) + +#### 1. Welcome & Mode (The "What"?) +* **Question**: "How do you want to run your store?" +* **Options**: + * **Immersive (Full SPA)**: "Modern, app-like experience. Best for dedicated stores." (Selects 'full') + * **Classic (Checkout Only)**: "Keep your current theme, but use our super-fast checkout." (Selects 'checkout_only') + * **Standard**: "Use standard WordPress pages." (Selects 'disabled') + +#### 2. The Homepage (The "Where"?) +* **Question**: "Where should customers land?" +* **Action**: Dropdown to select a page. +* **Magic Button**: "Auto-create 'Shop' Page" (Creates a page, sets it as SPA Entry, and sets WP Frontpage setting automatically). **<-- This solves the redirect bug confusion.** + +#### 3. Styling (The "Look") +* **Question**: "Choose your vibe." +* **Design**: + * **Layout**: Simple visual toggle between "Boxed" (Focus) vs "Full Width" (Immersive). + * **Theme**: Clickable color swatches (Modern Black, Trusty Blue, Vibrant Purple). + +#### 4. The Finish Line +* **Action**: "Save & Launch Builder". +* **Result**: Redirects the user directly to the Visual Builder for their home page. + +## Ancillary Improvements +1. **Contextual Hints**: Use the already installed `HoverCard` or `Popover` to add "?" icons next to complex settings (like "SPA Entry Page") explaining them in plain English. +2. **Smart Defaults**: Pre-select "Boxed", "Full SPA", and "Modern" font pair so users can just click "Next -> Next -> Next" if they don't care. diff --git a/admin-spa/src/components/DocLink.tsx b/admin-spa/src/components/DocLink.tsx new file mode 100644 index 0000000..1306dc1 --- /dev/null +++ b/admin-spa/src/components/DocLink.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import { useLocation } from 'react-router-dom'; +import { BookOpen } from 'lucide-react'; +import { getDocUrl } from '@/config/docRoutes'; +import { Button } from '@/components/ui/button'; + +export function DocLink() { + const location = useLocation(); + const docUrl = getDocUrl(location.pathname); + + if (!docUrl) return null; + + return ( + + ); +} diff --git a/admin-spa/src/components/PageHeader.tsx b/admin-spa/src/components/PageHeader.tsx index b586325..8925a06 100644 --- a/admin-spa/src/components/PageHeader.tsx +++ b/admin-spa/src/components/PageHeader.tsx @@ -7,6 +7,8 @@ interface PageHeaderProps { hideOnDesktop?: boolean; } +import { DocLink } from '@/components/DocLink'; + export function PageHeader({ fullscreen = false, hideOnDesktop = false }: PageHeaderProps) { const { title, action } = usePageHeader(); const location = useLocation(); @@ -24,8 +26,9 @@ export function PageHeader({ fullscreen = false, hideOnDesktop = false }: PageHe return (
{__('Newsletter module is disabled')}
{__('Manage subscribers and send email campaigns')}
{__('Newsletter, campaigns, and promotions')}
{description}
)} diff --git a/customer-spa/src/components/CouponURLHandler.tsx b/customer-spa/src/components/CouponURLHandler.tsx new file mode 100644 index 0000000..2652cc7 --- /dev/null +++ b/customer-spa/src/components/CouponURLHandler.tsx @@ -0,0 +1,49 @@ +import React, { useEffect, useRef } from 'react'; +import { useSearchParams } from 'react-router-dom'; +import { applyCoupon } from '@/lib/cart/api'; +import { useCartStore } from '@/lib/cart/store'; +import { toast } from 'sonner'; + +/** + * CouponURLHandler + * + * Global component that listens for 'coupon' or 'apply_coupon' query parameters + * and automatically applies them to the cart session. + */ +export function CouponURLHandler() { + const [searchParams, setSearchParams] = useSearchParams(); + const { setCart } = useCartStore(); + const processedRef = useRef(false); + + useEffect(() => { + const couponCode = searchParams.get('coupon') || searchParams.get('apply_coupon'); + + if (couponCode && !processedRef.current) { + processedRef.current = true; // Prevent double firing in StrictMode + + const apply = async () => { + const toastId = toast.loading(`Applying coupon: ${couponCode}...`); + + try { + const updatedCart = await applyCoupon(couponCode); + setCart(updatedCart); + toast.success(`Coupon "${couponCode}" applied successfully!`, { id: toastId }); + } catch (error: any) { + console.error('Failed to apply URL coupon:', error); + toast.error(error.message || `Failed to apply coupon "${couponCode}"`, { id: toastId }); + } finally { + // Remove the coupon param from URL to prevent re-application on refresh + // Use a new URLSearchParams object to avoid direct mutation issues + const newParams = new URLSearchParams(searchParams); + newParams.delete('coupon'); + newParams.delete('apply_coupon'); + setSearchParams(newParams, { replace: true }); + } + }; + + apply(); + } + }, [searchParams, setSearchParams, setCart]); + + return null; // This component renders nothing +} diff --git a/customer-spa/src/layouts/BaseLayout.tsx b/customer-spa/src/layouts/BaseLayout.tsx index ad69580..ea0e13b 100644 --- a/customer-spa/src/layouts/BaseLayout.tsx +++ b/customer-spa/src/layouts/BaseLayout.tsx @@ -9,6 +9,7 @@ import { NewsletterForm } from '../components/NewsletterForm'; import { LayoutWrapper } from './LayoutWrapper'; import { useModules } from '../hooks/useModules'; import { useModuleSettings } from '../hooks/useModuleSettings'; +import { CouponURLHandler } from '../components/CouponURLHandler'; interface BaseLayoutProps { children: ReactNode; @@ -22,22 +23,20 @@ interface BaseLayoutProps { export function BaseLayout({ children }: BaseLayoutProps) { const headerSettings = useHeaderSettings(); - // Map header styles to layouts - // classic -> ClassicLayout, centered -> ModernLayout, minimal -> LaunchLayout, split -> BoutiqueLayout - switch (headerSettings.style) { - case 'classic': - return