refactor: Simplify to single SPA entry page architecture
User feedback: 'SPA means Single Page, why 4 pages?' Correct architecture: - 1 SPA entry page (e.g., /store) - SPA Mode determines initial route: * Full SPA → starts at shop page * Checkout Only → starts at cart page * Disabled → never loads - React Router handles rest via /#/ routing Changes: - Admin UI: Changed from 4 page selectors to 1 SPA entry page - Backend: spa_pages array → spa_page integer - Template: Initial route based on spa_mode setting - Simplified is_spa_page() checks (single ID comparison) Benefits: - User can set /store as homepage (Settings → Reading) - Landing page → CTA → direct to cart/checkout - Clean single entry point - Mode controls behavior, not multiple pages Example flow: - Visit https://site.com/store - Full SPA: loads shop, navigate via /#/product/123 - Checkout Only: loads cart, navigate via /#/checkout - Homepage: set /store as homepage, SPA loads on site root Next: Add direct-to-cart CTA with product parameter
This commit is contained in:
@@ -21,12 +21,7 @@ interface WordPressPage {
|
||||
export default function AppearanceGeneral() {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [spaMode, setSpaMode] = useState<'disabled' | 'checkout_only' | 'full'>('full');
|
||||
const [spaPages, setSpaPages] = useState({
|
||||
shop: 0,
|
||||
cart: 0,
|
||||
checkout: 0,
|
||||
account: 0,
|
||||
});
|
||||
const [spaPage, setSpaPage] = useState(0);
|
||||
const [availablePages, setAvailablePages] = useState<WordPressPage[]>([]);
|
||||
const [toastPosition, setToastPosition] = useState('top-right');
|
||||
const [typographyMode, setTypographyMode] = useState<'predefined' | 'custom_google'>('predefined');
|
||||
@@ -59,14 +54,7 @@ export default function AppearanceGeneral() {
|
||||
|
||||
if (general) {
|
||||
if (general.spa_mode) setSpaMode(general.spa_mode);
|
||||
if (general.spa_pages) {
|
||||
setSpaPages({
|
||||
shop: general.spa_pages.shop || 0,
|
||||
cart: general.spa_pages.cart || 0,
|
||||
checkout: general.spa_pages.checkout || 0,
|
||||
account: general.spa_pages.account || 0,
|
||||
});
|
||||
}
|
||||
if (general.spa_page) setSpaPage(general.spa_page || 0);
|
||||
if (general.toast_position) setToastPosition(general.toast_position);
|
||||
if (general.typography) {
|
||||
setTypographyMode(general.typography.mode || 'predefined');
|
||||
@@ -105,7 +93,7 @@ export default function AppearanceGeneral() {
|
||||
try {
|
||||
await api.post('/appearance/general', {
|
||||
spaMode,
|
||||
spaPages,
|
||||
spaPage,
|
||||
toastPosition,
|
||||
typography: {
|
||||
mode: typographyMode,
|
||||
@@ -142,7 +130,7 @@ export default function AppearanceGeneral() {
|
||||
Disabled
|
||||
</Label>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Use WordPress default pages (no SPA functionality)
|
||||
SPA never loads (use WordPress default pages)
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -154,7 +142,7 @@ export default function AppearanceGeneral() {
|
||||
Checkout Only
|
||||
</Label>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
SPA for checkout flow only (cart, checkout, thank you)
|
||||
SPA starts at cart page (cart → checkout → thank you → account)
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -166,90 +154,33 @@ export default function AppearanceGeneral() {
|
||||
Full SPA
|
||||
</Label>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Entire customer-facing site uses SPA (recommended)
|
||||
SPA starts at shop page (shop → product → cart → checkout → account)
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</SettingsCard>
|
||||
|
||||
{/* SPA Pages */}
|
||||
{/* SPA Page */}
|
||||
<SettingsCard
|
||||
title="SPA Pages"
|
||||
description="Select which pages should render as full-page SPA (like WooCommerce settings)"
|
||||
title="SPA Page"
|
||||
description="Select the page where the SPA will load (e.g., /store)"
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<Alert>
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
<AlertDescription>
|
||||
These pages will render directly to the body element with no theme interference.
|
||||
This is the recommended approach for a clean SPA experience.
|
||||
This page will render the full SPA to the body element with no theme interference.
|
||||
The SPA Mode above determines the initial route (shop or cart). React Router handles navigation via /#/ routing.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
|
||||
<SettingsSection label="Shop Page" htmlFor="spa-page-shop">
|
||||
<SettingsSection label="SPA Entry Page" htmlFor="spa-page">
|
||||
<Select
|
||||
value={spaPages.shop.toString()}
|
||||
onValueChange={(value) => setSpaPages({ ...spaPages, shop: parseInt(value) })}
|
||||
value={spaPage.toString()}
|
||||
onValueChange={(value) => setSpaPage(parseInt(value))}
|
||||
>
|
||||
<SelectTrigger id="spa-page-shop">
|
||||
<SelectValue placeholder="Select a page..." />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="0">— None —</SelectItem>
|
||||
{availablePages.map((page) => (
|
||||
<SelectItem key={page.id} value={page.id.toString()}>
|
||||
{page.title}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</SettingsSection>
|
||||
|
||||
<SettingsSection label="Cart Page" htmlFor="spa-page-cart">
|
||||
<Select
|
||||
value={spaPages.cart.toString()}
|
||||
onValueChange={(value) => setSpaPages({ ...spaPages, cart: parseInt(value) })}
|
||||
>
|
||||
<SelectTrigger id="spa-page-cart">
|
||||
<SelectValue placeholder="Select a page..." />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="0">— None —</SelectItem>
|
||||
{availablePages.map((page) => (
|
||||
<SelectItem key={page.id} value={page.id.toString()}>
|
||||
{page.title}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</SettingsSection>
|
||||
|
||||
<SettingsSection label="Checkout Page" htmlFor="spa-page-checkout">
|
||||
<Select
|
||||
value={spaPages.checkout.toString()}
|
||||
onValueChange={(value) => setSpaPages({ ...spaPages, checkout: parseInt(value) })}
|
||||
>
|
||||
<SelectTrigger id="spa-page-checkout">
|
||||
<SelectValue placeholder="Select a page..." />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="0">— None —</SelectItem>
|
||||
{availablePages.map((page) => (
|
||||
<SelectItem key={page.id} value={page.id.toString()}>
|
||||
{page.title}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</SettingsSection>
|
||||
|
||||
<SettingsSection label="My Account Page" htmlFor="spa-page-account">
|
||||
<Select
|
||||
value={spaPages.account.toString()}
|
||||
onValueChange={(value) => setSpaPages({ ...spaPages, account: parseInt(value) })}
|
||||
>
|
||||
<SelectTrigger id="spa-page-account">
|
||||
<SelectTrigger id="spa-page">
|
||||
<SelectValue placeholder="Select a page..." />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@@ -261,6 +192,11 @@ export default function AppearanceGeneral() {
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<p className="text-sm text-muted-foreground mt-2">
|
||||
<strong>Full SPA:</strong> Loads shop page initially<br />
|
||||
<strong>Checkout Only:</strong> Loads cart page initially<br />
|
||||
<strong>Tip:</strong> You can set this page as your homepage in Settings → Reading
|
||||
</p>
|
||||
</SettingsSection>
|
||||
</div>
|
||||
</SettingsCard>
|
||||
|
||||
@@ -89,12 +89,7 @@ class AppearanceController {
|
||||
|
||||
$general_data = [
|
||||
'spa_mode' => sanitize_text_field($request->get_param('spaMode')),
|
||||
'spa_pages' => [
|
||||
'shop' => absint($request->get_param('spaPages')['shop'] ?? 0),
|
||||
'cart' => absint($request->get_param('spaPages')['cart'] ?? 0),
|
||||
'checkout' => absint($request->get_param('spaPages')['checkout'] ?? 0),
|
||||
'account' => absint($request->get_param('spaPages')['account'] ?? 0),
|
||||
],
|
||||
'spa_page' => absint($request->get_param('spaPage') ?? 0),
|
||||
'toast_position' => sanitize_text_field($request->get_param('toastPosition') ?? 'top-right'),
|
||||
'typography' => [
|
||||
'mode' => sanitize_text_field($request->get_param('typography')['mode'] ?? 'predefined'),
|
||||
@@ -415,12 +410,7 @@ class AppearanceController {
|
||||
return [
|
||||
'general' => [
|
||||
'spa_mode' => 'full',
|
||||
'spa_pages' => [
|
||||
'shop' => 0,
|
||||
'cart' => 0,
|
||||
'checkout' => 0,
|
||||
'account' => 0,
|
||||
],
|
||||
'spa_page' => 0,
|
||||
'toast_position' => 'top-right',
|
||||
'typography' => [
|
||||
'mode' => 'predefined',
|
||||
|
||||
@@ -363,7 +363,7 @@ class Assets {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if current page is a designated SPA page
|
||||
* Check if current page is the designated SPA page
|
||||
*/
|
||||
private static function is_spa_page() {
|
||||
global $post;
|
||||
@@ -371,17 +371,13 @@ class Assets {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get SPA page IDs from appearance settings
|
||||
// Get SPA page ID from appearance settings
|
||||
$appearance_settings = get_option('woonoow_appearance_settings', []);
|
||||
$spa_pages = isset($appearance_settings['general']['spa_pages']) ? $appearance_settings['general']['spa_pages'] : [];
|
||||
$spa_page_id = isset($appearance_settings['general']['spa_page']) ? $appearance_settings['general']['spa_page'] : 0;
|
||||
|
||||
// Check if current page matches any SPA page
|
||||
$current_page_id = $post->ID;
|
||||
|
||||
foreach ($spa_pages as $page_type => $page_id) {
|
||||
if ($page_id && $current_page_id == $page_id) {
|
||||
return true;
|
||||
}
|
||||
// Check if current page matches the SPA page
|
||||
if ($spa_page_id && $post->ID == $spa_page_id) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
@@ -271,7 +271,7 @@ class TemplateOverride {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if current page is a designated SPA page
|
||||
* Check if current page is the designated SPA page
|
||||
*/
|
||||
private static function is_spa_page() {
|
||||
global $post;
|
||||
@@ -279,17 +279,13 @@ class TemplateOverride {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get SPA page IDs from appearance settings
|
||||
// Get SPA page ID from appearance settings
|
||||
$appearance_settings = get_option('woonoow_appearance_settings', []);
|
||||
$spa_pages = isset($appearance_settings['general']['spa_pages']) ? $appearance_settings['general']['spa_pages'] : [];
|
||||
$spa_page_id = isset($appearance_settings['general']['spa_page']) ? $appearance_settings['general']['spa_page'] : 0;
|
||||
|
||||
// Check if current page matches any SPA page
|
||||
$current_page_id = $post->ID;
|
||||
|
||||
foreach ($spa_pages as $page_type => $page_id) {
|
||||
if ($page_id && $current_page_id == $page_id) {
|
||||
return true;
|
||||
}
|
||||
// Check if current page matches the SPA page
|
||||
if ($spa_page_id && $post->ID == $spa_page_id) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
@@ -8,23 +8,19 @@
|
||||
</head>
|
||||
<body <?php body_class('woonoow-spa-page'); ?>>
|
||||
<?php
|
||||
// Determine page type and data attributes
|
||||
$page_type = 'shop';
|
||||
$data_attrs = 'data-page="shop"';
|
||||
// Determine initial route based on SPA mode
|
||||
$appearance_settings = get_option('woonoow_appearance_settings', []);
|
||||
$spa_mode = isset($appearance_settings['general']['spa_mode']) ? $appearance_settings['general']['spa_mode'] : 'full';
|
||||
|
||||
if (is_product()) {
|
||||
$page_type = 'product';
|
||||
global $post;
|
||||
$data_attrs = 'data-page="product" data-product-id="' . esc_attr($post->ID) . '"';
|
||||
} elseif (is_cart()) {
|
||||
// Set initial page based on mode
|
||||
if ($spa_mode === 'checkout_only') {
|
||||
// Checkout Only mode starts at cart
|
||||
$page_type = 'cart';
|
||||
$data_attrs = 'data-page="cart"';
|
||||
} elseif (is_checkout()) {
|
||||
$page_type = 'checkout';
|
||||
$data_attrs = 'data-page="checkout"';
|
||||
} elseif (is_account_page()) {
|
||||
$page_type = 'account';
|
||||
$data_attrs = 'data-page="account"';
|
||||
$data_attrs = 'data-page="cart" data-initial-route="/cart"';
|
||||
} else {
|
||||
// Full SPA mode starts at shop
|
||||
$page_type = 'shop';
|
||||
$data_attrs = 'data-page="shop" data-initial-route="/shop"';
|
||||
}
|
||||
?>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user