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:
Dwindi Ramadhana
2025-12-30 20:33:15 +07:00
parent f054a78c5d
commit fe98e6233d
5 changed files with 45 additions and 131 deletions

View File

@@ -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>