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>
|
||||
|
||||
Reference in New Issue
Block a user