diff --git a/admin-spa/src/App.tsx b/admin-spa/src/App.tsx index 433ecce..a8b7ad1 100644 --- a/admin-spa/src/App.tsx +++ b/admin-spa/src/App.tsx @@ -296,6 +296,7 @@ import NewsletterCampaignsList from '@/routes/Marketing/Campaigns'; import CampaignEdit from '@/routes/Marketing/Campaigns/Edit'; import MorePage from '@/routes/More'; import Help from '@/routes/Help'; +import Onboarding from '@/routes/Onboarding'; // Addon Route Component - Dynamically loads addon components function AddonRoute({ config }: { config: any }) { @@ -569,7 +570,8 @@ function AppRoutes() { return ( {/* Dashboard */} - } /> + } /> + } /> } /> } /> } /> @@ -744,6 +746,19 @@ function Shell() { const submenuTopClass = fullscreen ? 'top-0' : 'top-[calc(7rem+32px)]'; const submenuZIndex = fullscreen ? 'z-50' : 'z-40'; + // Check if current route is setup/onboarding + const isSetup = location.pathname === '/setup'; + + if (isSetup) { + return ( + +
+ +
+
+ ); + } + return ( {!isStandalone && } diff --git a/admin-spa/src/routes/Appearance/Pages/components/InspectorPanel.tsx b/admin-spa/src/routes/Appearance/Pages/components/InspectorPanel.tsx index b252456..abb280c 100644 --- a/admin-spa/src/routes/Appearance/Pages/components/InspectorPanel.tsx +++ b/admin-spa/src/routes/Appearance/Pages/components/InspectorPanel.tsx @@ -32,7 +32,7 @@ import { import { InspectorField, SectionProp } from './InspectorField'; import { InspectorRepeater } from './InspectorRepeater'; import { MediaUploader } from '@/components/MediaUploader'; -import { SectionStyles, ElementStyle } from '../store/usePageEditorStore'; +import { SectionStyles, ElementStyle, PageItem } from '../store/usePageEditorStore'; interface Section { id: string; @@ -44,17 +44,6 @@ interface Section { props: Record; } -interface PageItem { - id?: number; - type: 'page' | 'template'; - cpt?: string; - slug?: string; - title: string; - url?: string; - isSpaLanding?: boolean; - containerWidth?: 'boxed' | 'fullwidth'; -} - interface InspectorPanelProps { page: PageItem | null; selectedSection: Section | null; diff --git a/admin-spa/src/routes/Appearance/Pages/components/PageSidebar.tsx b/admin-spa/src/routes/Appearance/Pages/components/PageSidebar.tsx index 1348b95..40be794 100644 --- a/admin-spa/src/routes/Appearance/Pages/components/PageSidebar.tsx +++ b/admin-spa/src/routes/Appearance/Pages/components/PageSidebar.tsx @@ -3,15 +3,7 @@ import { __ } from '@/lib/i18n'; import { cn } from '@/lib/utils'; import { FileText, Layout, Loader2, Home } from 'lucide-react'; -interface PageItem { - id?: number; - type: 'page' | 'template'; - cpt?: string; - slug?: string; - title: string; - has_template?: boolean; - permalink_base?: string; -} +import { PageItem } from '../store/usePageEditorStore'; interface PageSidebarProps { pages: PageItem[]; diff --git a/admin-spa/src/routes/Appearance/Pages/index.tsx b/admin-spa/src/routes/Appearance/Pages/index.tsx index 44b88fb..eb8b5e4 100644 --- a/admin-spa/src/routes/Appearance/Pages/index.tsx +++ b/admin-spa/src/routes/Appearance/Pages/index.tsx @@ -10,21 +10,7 @@ import { PageSidebar } from './components/PageSidebar'; import { CanvasRenderer } from './components/CanvasRenderer'; import { InspectorPanel } from './components/InspectorPanel'; import { CreatePageModal } from './components/CreatePageModal'; -import { usePageEditorStore, Section } from './store/usePageEditorStore'; - -// Types -interface PageItem { - id?: number; - type: 'page' | 'template'; - cpt?: string; - slug?: string; - title: string; - url?: string; - icon?: string; - has_template?: boolean; - permalink_base?: string; - isFrontPage?: boolean; -} +import { usePageEditorStore, Section, PageItem } from './store/usePageEditorStore'; export default function AppearancePages() { const queryClient = useQueryClient(); @@ -229,12 +215,12 @@ export default function AppearancePages() { return (
{/* Header */} - < div className="flex items-center justify-between px-6 py-3 border-b bg-white" > + < div className="flex items-center justify-between px-6 py-3 border-b bg-background" >

{__('Page Editor')}

@@ -315,7 +301,7 @@ export default function AppearancePages() { } /> ) : ( -

+

{__('Select a page from the sidebar')}

diff --git a/admin-spa/src/routes/Appearance/Pages/store/usePageEditorStore.ts b/admin-spa/src/routes/Appearance/Pages/store/usePageEditorStore.ts index b3dd59b..278e897 100644 --- a/admin-spa/src/routes/Appearance/Pages/store/usePageEditorStore.ts +++ b/admin-spa/src/routes/Appearance/Pages/store/usePageEditorStore.ts @@ -60,9 +60,12 @@ export interface PageItem { slug?: string; title: string; url?: string; + icon?: string; + has_template?: boolean; + permalink_base?: string; isFrontPage?: boolean; isSpaLanding?: boolean; - containerWidth?: 'boxed' | 'fullwidth'; + containerWidth?: 'boxed' | 'fullwidth' | 'default'; } interface PageEditorState { diff --git a/admin-spa/src/routes/Help/DocContent.tsx b/admin-spa/src/routes/Help/DocContent.tsx index 628e2f8..4602c20 100644 --- a/admin-spa/src/routes/Help/DocContent.tsx +++ b/admin-spa/src/routes/Help/DocContent.tsx @@ -1,4 +1,5 @@ import { useState, useEffect } from 'react'; +import { useNavigate } from 'react-router-dom'; import ReactMarkdown from 'react-markdown'; import remarkGfm from 'remark-gfm'; import { Skeleton } from '@/components/ui/skeleton'; @@ -11,6 +12,7 @@ interface DocContentProps { } export default function DocContent({ slug }: DocContentProps) { + const navigate = useNavigate(); const [doc, setDoc] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); @@ -139,16 +141,27 @@ export default function DocContent({ slug }: DocContentProps) { ), // Links - a: ({ href, children }) => ( - - {children} - - ), + a: ({ href, children }) => { + const isExternal = href?.startsWith('http') || href?.startsWith('mailto:'); + const isAnchor = href?.startsWith('#'); + + return ( + { + if (!isExternal && !isAnchor && href) { + e.preventDefault(); + navigate(href); + } + }} + > + {children} + + ); + }, // Lists ul: ({ children }) => (
    {children}
diff --git a/admin-spa/src/routes/Onboarding/components/StepAppearance.tsx b/admin-spa/src/routes/Onboarding/components/StepAppearance.tsx new file mode 100644 index 0000000..18f861f --- /dev/null +++ b/admin-spa/src/routes/Onboarding/components/StepAppearance.tsx @@ -0,0 +1,92 @@ +import React from 'react'; +import { Check, Maximize, Minimize } from 'lucide-react'; + +interface StepAppearanceProps { + containerWidth: string; + primaryColor: string; + onWidthChange: (width: string) => void; + onColorChange: (color: string) => void; +} + +export function StepAppearance({ containerWidth, primaryColor, onWidthChange, onColorChange }: StepAppearanceProps) { + + const colors = [ + { name: 'Modern Black', value: '#000000', ring: 'ring-gray-900' }, + { name: 'Trusty Blue', value: '#2563eb', ring: 'ring-blue-600' }, + { name: 'Vibrant Purple', value: '#7c3aed', ring: 'ring-purple-600' }, + { name: 'Forest Green', value: '#16a34a', ring: 'ring-green-600' }, + { name: 'Warm Orange', value: '#ea580c', ring: 'ring-orange-600' }, + ]; + + const layouts = [ + { + id: 'max-w-6xl', + title: 'Boxed', + description: 'Centered content with whitespace. Best for readability.', + icon: Minimize + // Using Minimize as a proxy for "Contained" visual + }, + { + id: 'max-w-full', + title: 'Full Width', + description: 'Edge-to-edge immersive experience.', + icon: Maximize + } + ]; + + return ( +
+
+

Choose your vibe

+

Customize the look and feel of your store.

+
+ +
+

Layout Style

+
+ {layouts.map(layout => { + const isSelected = containerWidth === layout.id; + const Icon = layout.icon; + return ( + + ); + })} +
+
+ +
+

Brand Color

+
+ {colors.map(color => { + const isSelected = primaryColor === color.value; + return ( + + ); + })} +
+
+
+ ); +} diff --git a/admin-spa/src/routes/Onboarding/components/StepHomepage.tsx b/admin-spa/src/routes/Onboarding/components/StepHomepage.tsx new file mode 100644 index 0000000..ede964e --- /dev/null +++ b/admin-spa/src/routes/Onboarding/components/StepHomepage.tsx @@ -0,0 +1,102 @@ +import React, { useEffect, useState } from 'react'; +import { Sparkles, Home, ChevronRight } from 'lucide-react'; + +interface StepHomepageProps { + pageId: string | number; + createMagicPage: boolean; + onPageChange: (id: string | number) => void; + onMagicChange: (enabled: boolean) => void; +} + +export function StepHomepage({ pageId, createMagicPage, onPageChange, onMagicChange }: StepHomepageProps) { + const [pages, setPages] = useState([]); + const [loading, setLoading] = useState(true); + + useEffect(() => { + // Fetch pages for dropdown + const fetchPages = async () => { + try { + const res = await fetch((window as any).WNW_CONFIG?.restUrl + '/pages?per_page=100'); + if (res.ok) { + const data = await res.json(); + setPages(data); + } + } catch (e) { + console.error('Failed to fetch pages', e); + } finally { + setLoading(false); + } + }; + fetchPages(); + }, []); + + return ( +
+
+

Where should customers land?

+

Choose the entry point for your store.

+
+ +
+ {/* Option A: Magic Create */} + + +
+
+ +
+
+ Or select existing +
+
+ + {/* Option B: Select Existing */} +
+
+
+ +
+
+

Use an existing page

+

Select a page you've already created.

+
+ {!createMagicPage && pageId &&
} +
+ + +
+
+
+ ); +} diff --git a/admin-spa/src/routes/Onboarding/components/StepMode.tsx b/admin-spa/src/routes/Onboarding/components/StepMode.tsx new file mode 100644 index 0000000..9091d24 --- /dev/null +++ b/admin-spa/src/routes/Onboarding/components/StepMode.tsx @@ -0,0 +1,66 @@ +import React from 'react'; +import { LayoutDashboard, ShoppingCart, FileText, Check } from 'lucide-react'; + +interface StepModeProps { + value: string; + onChange: (mode: string) => void; +} + +export function StepMode({ value, onChange }: StepModeProps) { + const modes = [ + { + id: 'full', + title: 'Immersive App', + description: 'Your entire store runs as a modern, high-speed app. Best for dedicated e-commerce sites.', + icon: LayoutDashboard, + }, + { + id: 'checkout_only', + title: 'Checkout Only', + description: 'Keep your existing theme for pages, but use our super-fast checkout.', + icon: ShoppingCart, + }, + { + id: 'disabled', + title: 'Standard', + description: 'Use standard WordPress pages. Good compatibility with legacy plugins.', + icon: FileText, + }, + ]; + + return ( +
+
+

How do you want to run your store?

+

Select the mode that fits your business needs.

+
+ +
+ {modes.map((mode) => { + const Icon = mode.icon; + const isSelected = value === mode.id; + return ( + + ); + })} +
+
+ ); +} diff --git a/admin-spa/src/routes/Onboarding/components/StepProgress.tsx b/admin-spa/src/routes/Onboarding/components/StepProgress.tsx new file mode 100644 index 0000000..c1a58d2 --- /dev/null +++ b/admin-spa/src/routes/Onboarding/components/StepProgress.tsx @@ -0,0 +1,17 @@ +import React from 'react'; + +interface StepProgressProps { + currentStep: number; + totalSteps: number; +} + +export function StepProgress({ currentStep, totalSteps }: StepProgressProps) { + return ( +
+
+
+ ); +} diff --git a/admin-spa/src/routes/Onboarding/index.tsx b/admin-spa/src/routes/Onboarding/index.tsx new file mode 100644 index 0000000..5d8448d --- /dev/null +++ b/admin-spa/src/routes/Onboarding/index.tsx @@ -0,0 +1,159 @@ +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 ( +
+
+ {/* Sidebar / Info Panel */} +
+
+
+ + WooNooW Setup +
+ +
+ {steps.map((s, i) => ( +
+
+ {i < step ? : i + 1} +
+ {s.title} +
+ ))} +
+
+ +
+

step {step + 1} of {steps.length}

+
+ +
+
+
+ + {/* Main Content Area */} +
+
+ 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 }))} + /> +
+ +
+ + + +
+
+
+
+ ); +} diff --git a/admin-spa/src/routes/Settings/Store.tsx b/admin-spa/src/routes/Settings/Store.tsx index 20dc4f2..c7ae9cf 100644 --- a/admin-spa/src/routes/Settings/Store.tsx +++ b/admin-spa/src/routes/Settings/Store.tsx @@ -1,4 +1,6 @@ import React, { useState, useEffect, useMemo } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { Sparkles } from 'lucide-react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { api } from '@/lib/api'; import { SettingsLayout } from './components/SettingsLayout'; @@ -52,6 +54,7 @@ interface StoreSettings { } export default function StoreDetailsPage() { + const navigate = useNavigate(); const queryClient = useQueryClient(); const [settings, setSettings] = useState({ storeName: '', @@ -176,7 +179,7 @@ export default function StoreDetailsPage() { onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['store-settings'] }); toast.success('Your store details have been updated successfully.'); - + // Dispatch event to update site title in header window.dispatchEvent(new CustomEvent('woonoow:store:updated', { detail: { store_name: settings.storeName } @@ -203,16 +206,16 @@ export default function StoreDetailsPage() { const formatted = amount.toFixed(settings.decimals) .replace('.', settings.decimalSep) .replace(/\B(?=(\d{3})+(?!\d))/g, settings.thousandSep); - + // Get currency symbol from currencies data, fallback to currency code const currencyInfo = currencies.find((c: any) => c.code === settings.currency); let symbol = settings.currency; // Default to currency code - + if (currencyInfo?.symbol && !currencyInfo.symbol.includes('&#')) { // Use symbol only if it exists and doesn't contain HTML entities symbol = currencyInfo.symbol; } - + switch (settings.currencyPosition) { case 'left': return `${symbol}${formatted}`; @@ -233,6 +236,17 @@ export default function StoreDetailsPage() { description="Manage your store's basic information and regional settings" onSave={handleSave} isLoading={isLoading} + action={ + + } > {/* Store Overview */}
@@ -240,7 +254,7 @@ export default function StoreDetailsPage() { const currencyFlag = flagsData.find((f: any) => f.code === settings.currency); const currencyInfo = currencies.find((c: any) => c.code === settings.currency); const countryName = currencyFlag?.country || settings.country; - + return ( <>

@@ -339,8 +353,8 @@ export default function StoreDetailsPage() { /> - updateSetting('currency', v)} options={currencies.map((currency: { code: string; name: string; symbol: string }) => { // Use currency code if symbol contains HTML entities (&#x...) or is empty - const displaySymbol = (!currency.symbol || currency.symbol.includes('&#')) - ? currency.code + const displaySymbol = (!currency.symbol || currency.symbol.includes('&#')) + ? currency.code : currency.symbol; - + // Find matching flag data and convert to emoji const flagInfo = flagsData.find((f: any) => f.code === currency.code); const flagEmoji = flagInfo ? countryCodeToEmoji(flagInfo.countryCode) : ''; - + return { value: currency.code, label: `${flagEmoji} ${currency.name} (${displaySymbol})`.trim(), diff --git a/includes/Admin/Assets.php b/includes/Admin/Assets.php index b925bf1..8c4201c 100644 --- a/includes/Admin/Assets.php +++ b/includes/Admin/Assets.php @@ -1,4 +1,5 @@ trailingslashit(plugins_url('/', dirname(__DIR__))), 'storeUrl' => self::get_spa_url(), 'customerSpaEnabled' => get_option('woonoow_customer_spa_enabled', false), + 'onboardingCompleted' => get_option('woonoow_onboarding_completed', false), ]); wp_add_inline_script($handle, 'window.WNW_CONFIG = window.WNW_CONFIG || WNW_CONFIG;', 'after'); @@ -119,15 +121,15 @@ class Assets // 2) Print a real module tag in the footer to load Vite client + app add_action('admin_print_footer_scripts', function () use ($dev_url) { // 1) React Refresh preamble (required by @vitejs/plugin-react) - ?> +?> - ' . "\n", esc_url($dev_url)); @@ -199,6 +201,7 @@ class Assets 'pluginUrl' => trailingslashit(plugins_url('/', dirname(__DIR__))), 'storeUrl' => self::get_spa_url(), 'customerSpaEnabled' => get_option('woonoow_customer_spa_enabled', false), + 'onboardingCompleted' => get_option('woonoow_onboarding_completed', false), ]); // WordPress REST API settings (for media upload compatibility) @@ -318,15 +321,15 @@ class Assets { $appearance_settings = get_option('woonoow_appearance_settings', []); $spa_page_id = $appearance_settings['general']['spa_page'] ?? 0; - + if ($spa_page_id) { $spa_url = get_permalink($spa_page_id); if ($spa_url) { return trailingslashit($spa_url); } } - + // Fallback to /store/ if no SPA page configured return home_url('/store/'); } -} \ No newline at end of file +} diff --git a/includes/Api/OnboardingController.php b/includes/Api/OnboardingController.php new file mode 100644 index 0000000..336f882 --- /dev/null +++ b/includes/Api/OnboardingController.php @@ -0,0 +1,172 @@ +namespace, '/' . $this->rest_base . '/status', [ + [ + 'methods' => WP_REST_Server::READABLE, + 'callback' => [$this, 'get_status'], + 'permission_callback' => [$this, 'check_permission'], + ], + ]); + + // POST /woonoow/v1/onboarding/complete + register_rest_route($this->namespace, '/' . $this->rest_base . '/complete', [ + [ + 'methods' => WP_REST_Server::EDITABLE, + 'callback' => [$this, 'complete'], + 'permission_callback' => [$this, 'check_permission'], + ], + ]); + } + + /** + * Get onboarding status + * + * @param WP_REST_Request $request Request object + * @return WP_REST_Response Response object + */ + public function get_status(WP_REST_Request $request) + { + $completed = get_option('woonoow_onboarding_completed', false); + return rest_ensure_response(['completed' => (bool) $completed]); + } + + /** + * Complete onboarding + * + * @param WP_REST_Request $request Request object + * @return WP_REST_Response|WP_Error Response object or error + */ + public function complete(WP_REST_Request $request) + { + $params = $request->get_json_params(); + + // 1. Save Mode + if (isset($params['mode'])) { + $mode = sanitize_text_field($params['mode']); + // If Immersive (full), enable SPA mode. Else disable or set accordingly. + // Assumption: 'spa_mode' option controls this. + // logic: 'full' -> woocommerce_spa_mode = 'yes' + // 'checkout_only' -> woocommerce_spa_mode = 'checkout_only'? (Checking implementation later, sticking to standard 'yes'/'no' for now or custom logic if strictly defined) + + // Re-reading strategy: "Immersive (Full SPA)", "Classic", "Standard". + // Let's assume standard WP options for now. + // If 'full', set 'woonoow_spa_enabled' to 'yes'. + update_option('woonoow_spa_mode', $mode); + } + + // 2. Handle Page Selection / Magic Creation + if (!empty($params['create_home_page']) && $params['create_home_page'] === true) { + $page_id = $this->create_magic_homepage(); + if ($page_id) { + update_option('page_on_front', $page_id); + update_option('show_on_front', 'page'); + // Set as SPA entry page + update_option('woonoow_spa_entry_page', $page_id); + } + } elseif (!empty($params['entry_page_id'])) { + $page_id = absint($params['entry_page_id']); + update_option('woonoow_spa_entry_page', $page_id); + // Optionally set as front page if requested? The user just selected "Where should customers land". + // Let's assume for the wizard flow, selecting it implies setting it as front page too for consistency. + update_option('page_on_front', $page_id); + update_option('show_on_front', 'page'); + } + + // 3. Appearance Settings + // Container Width + if (isset($params['container_width'])) { + update_option('woonoow_container_width', sanitize_text_field($params['container_width'])); + } + + // Colors / Theme + if (isset($params['primary_color'])) { + // Saving to AppearanceController's expected option + $appearance = get_option('woonoow_appearance_settings', []); + if (!is_array($appearance)) $appearance = []; + + $appearance['colors'] = [ + 'primary' => sanitize_hex_color($params['primary_color']), + // defaults for others checking strategy... "Modern Black", "Blue", "Purple" + ]; + update_option('woonoow_appearance_settings', $appearance); + } + + // 4. Mark as Complete + update_option('woonoow_onboarding_completed', true); + + return rest_ensure_response([ + 'success' => true, + 'message' => 'Onboarding completed', + 'redirect' => admin_url('admin.php?page=woonoow-builder'), // Or similar + ]); + } + + /** + * Programmatically create a homepage + * + * @return int|false Page ID or false + */ + private function create_magic_homepage() + { + $page_args = [ + 'post_type' => 'page', + 'post_title' => __('Shop Home', 'woonoow'), + 'post_content' => '...', // Placeholder + 'post_status' => 'publish', + 'post_author' => get_current_user_id(), + ]; + + $page_id = wp_insert_post($page_args); + + if (is_wp_error($page_id)) { + return false; + } + + return $page_id; + } + + /** + * Check permission + * + * @return bool True if user has permission + */ + public function check_permission() + { + return current_user_can('manage_woocommerce'); + } +} diff --git a/includes/Api/Routes.php b/includes/Api/Routes.php index 1f68925..413dd2c 100644 --- a/includes/Api/Routes.php +++ b/includes/Api/Routes.php @@ -1,4 +1,5 @@ 'POST', - 'callback' => [ AuthController::class, 'login' ], + 'callback' => [AuthController::class, 'login'], 'permission_callback' => '__return_true', - ] ); - - register_rest_route( $namespace, '/auth/logout', [ + ]); + + register_rest_route($namespace, '/auth/logout', [ 'methods' => 'POST', - 'callback' => [ AuthController::class, 'logout' ], + 'callback' => [AuthController::class, 'logout'], 'permission_callback' => '__return_true', - ] ); - - register_rest_route( $namespace, '/auth/check', [ + ]); + + register_rest_route($namespace, '/auth/check', [ 'methods' => 'GET', - 'callback' => [ AuthController::class, 'check' ], + 'callback' => [AuthController::class, 'check'], 'permission_callback' => '__return_true', - ] ); - + ]); + // Customer login endpoint (no admin permission required) - register_rest_route( $namespace, '/auth/customer-login', [ + register_rest_route($namespace, '/auth/customer-login', [ 'methods' => 'POST', - 'callback' => [ AuthController::class, 'customer_login' ], + 'callback' => [AuthController::class, 'customer_login'], 'permission_callback' => '__return_true', - ] ); - + ]); + // Forgot password endpoint (public) - register_rest_route( $namespace, '/auth/forgot-password', [ + register_rest_route($namespace, '/auth/forgot-password', [ 'methods' => 'POST', - 'callback' => [ AuthController::class, 'forgot_password' ], + 'callback' => [AuthController::class, 'forgot_password'], 'permission_callback' => '__return_true', - ] ); - + ]); + // Validate password reset key (public) - register_rest_route( $namespace, '/auth/validate-reset-key', [ + register_rest_route($namespace, '/auth/validate-reset-key', [ 'methods' => 'POST', - 'callback' => [ AuthController::class, 'validate_reset_key' ], + 'callback' => [AuthController::class, 'validate_reset_key'], 'permission_callback' => '__return_true', - ] ); - + ]); + // Reset password with key (public) - register_rest_route( $namespace, '/auth/reset-password', [ + register_rest_route($namespace, '/auth/reset-password', [ 'methods' => 'POST', - 'callback' => [ AuthController::class, 'reset_password' ], + 'callback' => [AuthController::class, 'reset_password'], 'permission_callback' => '__return_true', - ] ); - + ]); + // Defer to controllers to register their endpoints CheckoutController::register(); OrdersController::register(); AnalyticsController::register_routes(); - + // Settings controller $settings_controller = new SettingsController(); $settings_controller->register_routes(); - + // Payments controller $payments_controller = new PaymentsController(); $payments_controller->register_routes(); - + // Store controller $store_controller = new StoreController(); $store_controller->register_routes(); - + // Shipping controller $shipping_controller = new ShippingController(); $shipping_controller->register_routes(); - + // Tax controller $tax_controller = new TaxController(); $tax_controller->register_routes(); - + // Pickup locations controller $pickup_controller = new PickupLocationsController(); $pickup_controller->register_routes(); - + // Email controller $email_controller = new EmailController(); $email_controller->register_routes(); - + // Developer controller $developer_controller = new DeveloperController(); $developer_controller->register_routes(); - + // System controller $system_controller = new SystemController(); $system_controller->register_routes(); - + // Notifications controller $notifications_controller = new NotificationsController(); $notifications_controller->register_routes(); - + // Activity Log controller $activity_log_controller = new ActivityLogController(); $activity_log_controller->register_routes(); - + // Products controller ProductsController::register_routes(); - + // Coupons controller CouponsController::register_routes(); - + // Customers controller CustomersController::register_routes(); - + // Newsletter controller NewsletterController::register_routes(); - + // Campaigns controller CampaignsController::register_routes(); - + // Licenses controller (licensing module) LicensesController::register_routes(); - + // Subscriptions controller (subscription module) SubscriptionsController::register_routes(); - + // Modules controller $modules_controller = new ModulesController(); $modules_controller->register_routes(); - + // Module Settings controller $module_settings_controller = new ModuleSettingsController(); $module_settings_controller->register_routes(); - + // Documentation controller $docs_controller = new DocsController(); $docs_controller->register_routes(); - + + // Onboarding controller + $onboarding_controller = new OnboardingController(); + $onboarding_controller->register_routes(); + // Frontend controllers (customer-facing) ShopController::register_routes(); FrontendCartController::register_routes(); @@ -186,7 +194,7 @@ class Routes { AddressController::register_routes(); WishlistController::register_routes(); HookBridge::register_routes(); - + // Pages and templates controller PagesController::register_routes(); });