feat: Add product images support with WP Media Library integration
- 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
This commit is contained in:
434
HASHROUTER_SOLUTION.md
Normal file
434
HASHROUTER_SOLUTION.md
Normal file
@@ -0,0 +1,434 @@
|
||||
# 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! ✅
|
||||
Reference in New Issue
Block a user