- Add WP Media Library integration for product and variation images - Support images array (URLs) conversion to attachment IDs - Add images array to API responses (Admin & Customer SPA) - Implement drag-and-drop sortable images in Admin product form - Add image gallery thumbnails in Customer SPA product page - Initialize WooCommerce session for guest cart operations - Fix product variations and attributes display in Customer SPA - Add variation image field in Admin SPA Changes: - includes/Api/ProductsController.php: Handle images array, add to responses - includes/Frontend/ShopController.php: Add images array for customer SPA - includes/Frontend/CartController.php: Initialize WC session for guests - admin-spa/src/lib/wp-media.ts: Add openWPMediaGallery function - admin-spa/src/routes/Products/partials/tabs/GeneralTab.tsx: WP Media + sortable images - admin-spa/src/routes/Products/partials/tabs/VariationsTab.tsx: Add variation image field - customer-spa/src/pages/Product/index.tsx: Add gallery thumbnails display
435 lines
9.2 KiB
Markdown
435 lines
9.2 KiB
Markdown
# HashRouter Solution - The Right Approach
|
|
|
|
## Problem
|
|
Direct product URLs like `https://woonoow.local/product/edukasi-anak` don't work because WordPress owns the `/product/` route.
|
|
|
|
## Why Admin SPA Works
|
|
|
|
Admin SPA uses HashRouter:
|
|
```
|
|
https://woonoow.local/wp-admin/admin.php?page=woonoow#/dashboard
|
|
↑
|
|
Hash routing
|
|
```
|
|
|
|
**How it works:**
|
|
1. WordPress loads: `/wp-admin/admin.php?page=woonoow`
|
|
2. React takes over: `#/dashboard`
|
|
3. Everything after `#` is client-side only
|
|
4. WordPress never sees or processes it
|
|
5. Works perfectly ✅
|
|
|
|
## Why Customer SPA Should Use HashRouter Too
|
|
|
|
### The Conflict
|
|
|
|
**WordPress owns these routes:**
|
|
- `/product/` - WooCommerce product pages
|
|
- `/cart/` - WooCommerce cart
|
|
- `/checkout/` - WooCommerce checkout
|
|
- `/my-account/` - WooCommerce account
|
|
|
|
**We can't override them reliably** because:
|
|
- WordPress processes the URL first
|
|
- Theme templates load before our SPA
|
|
- Canonical redirects interfere
|
|
- SEO and caching issues
|
|
|
|
### The Solution: HashRouter
|
|
|
|
Use hash-based routing like Admin SPA:
|
|
|
|
```
|
|
https://woonoow.local/shop#/product/edukasi-anak
|
|
↑
|
|
Hash routing
|
|
```
|
|
|
|
**Benefits:**
|
|
- ✅ WordPress loads `/shop` (valid page)
|
|
- ✅ React handles `#/product/edukasi-anak`
|
|
- ✅ No WordPress conflicts
|
|
- ✅ Works for direct access
|
|
- ✅ Works for sharing links
|
|
- ✅ Works for email campaigns
|
|
- ✅ Reliable and predictable
|
|
|
|
---
|
|
|
|
## Implementation
|
|
|
|
### Changed File: App.tsx
|
|
|
|
```tsx
|
|
// Before
|
|
import { BrowserRouter } from 'react-router-dom';
|
|
|
|
<BrowserRouter>
|
|
<Routes>
|
|
<Route path="/product/:slug" element={<Product />} />
|
|
</Routes>
|
|
</BrowserRouter>
|
|
|
|
// After
|
|
import { HashRouter } from 'react-router-dom';
|
|
|
|
<HashRouter>
|
|
<Routes>
|
|
<Route path="/product/:slug" element={<Product />} />
|
|
</Routes>
|
|
</HashRouter>
|
|
```
|
|
|
|
**That's it!** React Router's `Link` components automatically use hash URLs.
|
|
|
|
---
|
|
|
|
## URL Format
|
|
|
|
### Shop Page
|
|
```
|
|
https://woonoow.local/shop
|
|
https://woonoow.local/shop#/
|
|
https://woonoow.local/shop#/shop
|
|
```
|
|
|
|
All work! The SPA loads on `/shop` page.
|
|
|
|
### Product Pages
|
|
```
|
|
https://woonoow.local/shop#/product/edukasi-anak
|
|
https://woonoow.local/shop#/product/test-variable
|
|
```
|
|
|
|
### Cart
|
|
```
|
|
https://woonoow.local/shop#/cart
|
|
```
|
|
|
|
### Checkout
|
|
```
|
|
https://woonoow.local/shop#/checkout
|
|
```
|
|
|
|
### My Account
|
|
```
|
|
https://woonoow.local/shop#/my-account
|
|
```
|
|
|
|
---
|
|
|
|
## How It Works
|
|
|
|
### URL Structure
|
|
```
|
|
https://woonoow.local/shop#/product/edukasi-anak
|
|
↑ ↑
|
|
| └─ Client-side route (React Router)
|
|
└────── Server-side route (WordPress)
|
|
```
|
|
|
|
### Request Flow
|
|
|
|
1. **Browser requests:** `https://woonoow.local/shop#/product/edukasi-anak`
|
|
2. **WordPress receives:** `https://woonoow.local/shop`
|
|
- The `#/product/edukasi-anak` part is NOT sent to server
|
|
3. **WordPress loads:** Shop page template with SPA
|
|
4. **React Router sees:** `#/product/edukasi-anak`
|
|
5. **React Router shows:** Product component
|
|
6. **Result:** Product page displays ✅
|
|
|
|
### Why This Works
|
|
|
|
**Hash fragments are client-side only:**
|
|
- Browsers don't send hash to server
|
|
- WordPress never sees `#/product/edukasi-anak`
|
|
- No conflicts with WordPress routes
|
|
- React Router handles everything after `#`
|
|
|
|
---
|
|
|
|
## Use Cases
|
|
|
|
### 1. Direct Access ✅
|
|
User types URL in browser:
|
|
```
|
|
https://woonoow.local/shop#/product/edukasi-anak
|
|
```
|
|
**Result:** Product page loads directly
|
|
|
|
### 2. Sharing Links ✅
|
|
User shares product link:
|
|
```
|
|
Copy: https://woonoow.local/shop#/product/edukasi-anak
|
|
Paste in chat/email
|
|
Click link
|
|
```
|
|
**Result:** Product page loads for recipient
|
|
|
|
### 3. Email Campaigns ✅
|
|
Admin sends promotional email:
|
|
```html
|
|
<a href="https://woonoow.local/shop#/product/special-offer">
|
|
Check out our special offer!
|
|
</a>
|
|
```
|
|
**Result:** Product page loads when clicked
|
|
|
|
### 4. Social Media ✅
|
|
Share on Facebook, Twitter, etc:
|
|
```
|
|
https://woonoow.local/shop#/product/edukasi-anak
|
|
```
|
|
**Result:** Product page loads when clicked
|
|
|
|
### 5. Bookmarks ✅
|
|
User bookmarks product page:
|
|
```
|
|
Bookmark: https://woonoow.local/shop#/product/edukasi-anak
|
|
```
|
|
**Result:** Product page loads when bookmark opened
|
|
|
|
### 6. QR Codes ✅
|
|
Generate QR code for product:
|
|
```
|
|
QR → https://woonoow.local/shop#/product/edukasi-anak
|
|
```
|
|
**Result:** Product page loads when scanned
|
|
|
|
---
|
|
|
|
## Comparison: BrowserRouter vs HashRouter
|
|
|
|
| Feature | BrowserRouter | HashRouter |
|
|
|---------|---------------|------------|
|
|
| **URL Format** | `/product/slug` | `#/product/slug` |
|
|
| **Clean URLs** | ✅ Yes | ❌ Has `#` |
|
|
| **SEO** | ✅ Better | ⚠️ Acceptable |
|
|
| **Direct Access** | ❌ Conflicts | ✅ Works |
|
|
| **WordPress Conflicts** | ❌ Many | ✅ None |
|
|
| **Sharing** | ❌ Unreliable | ✅ Reliable |
|
|
| **Email Links** | ❌ Breaks | ✅ Works |
|
|
| **Setup Complexity** | ❌ Complex | ✅ Simple |
|
|
| **Reliability** | ❌ Fragile | ✅ Solid |
|
|
|
|
**Winner:** HashRouter for Customer SPA ✅
|
|
|
|
---
|
|
|
|
## SEO Considerations
|
|
|
|
### Hash URLs and SEO
|
|
|
|
**Modern search engines handle hash URLs:**
|
|
- Google can crawl hash URLs
|
|
- Bing supports hash routing
|
|
- Social media platforms parse them
|
|
|
|
**Best practices:**
|
|
1. Use server-side rendering for SEO-critical pages
|
|
2. Add proper meta tags
|
|
3. Use canonical URLs
|
|
4. Submit sitemap with actual product URLs
|
|
|
|
### Our Approach
|
|
|
|
**For SEO:**
|
|
- WooCommerce product pages still exist
|
|
- Search engines index actual product URLs
|
|
- Canonical tags point to real products
|
|
|
|
**For Users:**
|
|
- SPA provides better UX
|
|
- Hash URLs work reliably
|
|
- No broken links
|
|
|
|
**Best of both worlds!** ✅
|
|
|
|
---
|
|
|
|
## Migration Notes
|
|
|
|
### Existing Links
|
|
|
|
If you already shared links with BrowserRouter format:
|
|
|
|
**Old format:**
|
|
```
|
|
https://woonoow.local/product/edukasi-anak
|
|
```
|
|
|
|
**New format:**
|
|
```
|
|
https://woonoow.local/shop#/product/edukasi-anak
|
|
```
|
|
|
|
**Solution:** Add redirect or keep both working:
|
|
```php
|
|
// In TemplateOverride.php
|
|
if (is_product()) {
|
|
// Redirect to hash URL
|
|
$product_slug = get_post_field('post_name', get_the_ID());
|
|
wp_redirect(home_url("/shop#/product/$product_slug"));
|
|
exit;
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Testing
|
|
|
|
### Test 1: Direct Access
|
|
1. Open new browser tab
|
|
2. Type: `https://woonoow.local/shop#/product/edukasi-anak`
|
|
3. Press Enter
|
|
4. **Expected:** Product page loads ✅
|
|
|
|
### Test 2: Navigation
|
|
1. Go to shop page
|
|
2. Click product
|
|
3. **Expected:** URL changes to `#/product/slug` ✅
|
|
4. **Expected:** Product page shows ✅
|
|
|
|
### Test 3: Refresh
|
|
1. On product page
|
|
2. Press F5
|
|
3. **Expected:** Page reloads, product still shows ✅
|
|
|
|
### Test 4: Bookmark
|
|
1. Bookmark product page
|
|
2. Close browser
|
|
3. Open bookmark
|
|
4. **Expected:** Product page loads ✅
|
|
|
|
### Test 5: Share Link
|
|
1. Copy product URL
|
|
2. Open in incognito window
|
|
3. **Expected:** Product page loads ✅
|
|
|
|
### Test 6: Back Button
|
|
1. Navigate: Shop → Product → Cart
|
|
2. Press back button
|
|
3. **Expected:** Goes back to product ✅
|
|
4. Press back again
|
|
5. **Expected:** Goes back to shop ✅
|
|
|
|
---
|
|
|
|
## Advantages Over BrowserRouter
|
|
|
|
### 1. Zero WordPress Conflicts
|
|
- No canonical redirect issues
|
|
- No 404 problems
|
|
- No template override complexity
|
|
- No rewrite rule conflicts
|
|
|
|
### 2. Reliable Direct Access
|
|
- Always works
|
|
- No server configuration needed
|
|
- No .htaccess rules
|
|
- No WordPress query manipulation
|
|
|
|
### 3. Perfect for Sharing
|
|
- Links work everywhere
|
|
- Email campaigns reliable
|
|
- Social media compatible
|
|
- QR codes work
|
|
|
|
### 4. Simple Implementation
|
|
- One line change (BrowserRouter → HashRouter)
|
|
- No PHP changes needed
|
|
- No server configuration
|
|
- No complex debugging
|
|
|
|
### 5. Consistent with Admin SPA
|
|
- Same routing approach
|
|
- Proven to work
|
|
- Easy to understand
|
|
- Maintainable
|
|
|
|
---
|
|
|
|
## Real-World Examples
|
|
|
|
### Example 1: Product Promotion
|
|
```
|
|
Email subject: Special Offer on Edukasi Anak!
|
|
Email body: Click here to view:
|
|
https://woonoow.local/shop#/product/edukasi-anak
|
|
```
|
|
✅ Works perfectly
|
|
|
|
### Example 2: Social Media Post
|
|
```
|
|
Facebook post:
|
|
"Check out our new product! 🎉
|
|
https://woonoow.local/shop#/product/edukasi-anak"
|
|
```
|
|
✅ Link works for all followers
|
|
|
|
### Example 3: Customer Support
|
|
```
|
|
Support: "Please check this product page:"
|
|
https://woonoow.local/shop#/product/edukasi-anak
|
|
|
|
Customer: *clicks link*
|
|
```
|
|
✅ Page loads immediately
|
|
|
|
### Example 4: Affiliate Marketing
|
|
```
|
|
Affiliate link:
|
|
https://woonoow.local/shop#/product/edukasi-anak?ref=affiliate123
|
|
```
|
|
✅ Works with query parameters
|
|
|
|
---
|
|
|
|
## Summary
|
|
|
|
**Problem:** BrowserRouter conflicts with WordPress routes
|
|
|
|
**Solution:** Use HashRouter like Admin SPA
|
|
|
|
**Benefits:**
|
|
- ✅ Direct access works
|
|
- ✅ Sharing works
|
|
- ✅ Email campaigns work
|
|
- ✅ No WordPress conflicts
|
|
- ✅ Simple and reliable
|
|
|
|
**Trade-off:**
|
|
- URLs have `#` in them
|
|
- Acceptable for SPA use case
|
|
|
|
**Result:** Reliable, shareable product links! 🎉
|
|
|
|
---
|
|
|
|
## Files Modified
|
|
|
|
1. **customer-spa/src/App.tsx**
|
|
- Changed: `BrowserRouter` → `HashRouter`
|
|
- That's it!
|
|
|
|
## URL Examples
|
|
|
|
**Shop:**
|
|
- `https://woonoow.local/shop`
|
|
- `https://woonoow.local/shop#/`
|
|
|
|
**Products:**
|
|
- `https://woonoow.local/shop#/product/edukasi-anak`
|
|
- `https://woonoow.local/shop#/product/test-variable`
|
|
|
|
**Cart:**
|
|
- `https://woonoow.local/shop#/cart`
|
|
|
|
**Checkout:**
|
|
- `https://woonoow.local/shop#/checkout`
|
|
|
|
**Account:**
|
|
- `https://woonoow.local/shop#/my-account`
|
|
|
|
All work perfectly! ✅
|