feat: add dynamic meta tags for social sharing (Phase 4-5)

Phase 4: Dynamic Meta Tags
- Added react-helmet-async dependency
- Created SEOHead component with Open Graph and Twitter Card support
- Added HelmetProvider wrapper to App.tsx
- Integrated SEOHead in Product page (title, description, image, product info)
- Integrated SEOHead in Shop page (basic meta tags)

Phase 5: Auto-Flush Permalinks
- Enhanced settings change handler to only flush when spa_mode,
  spa_page, or use_browser_router changes
- Plugin already flushes on activation (Installer.php)

This enables proper link previews when sharing product URLs
on Facebook, Twitter, Slack, etc.
This commit is contained in:
Dwindi Ramadhana
2026-01-04 10:40:10 +07:00
parent 45fcbf9d29
commit 75a82cf16c
7 changed files with 357 additions and 224 deletions

View File

@@ -10,6 +10,7 @@ import { ProductCard } from '@/components/ProductCard';
import { toast } from 'sonner';
import { useTheme, useLayout } from '@/contexts/ThemeContext';
import { useShopSettings } from '@/hooks/useAppearanceSettings';
import SEOHead from '@/components/SEOHead';
import type { ProductsResponse, ProductCategory, Product } from '@/types/product';
export default function Shop() {
@@ -22,25 +23,25 @@ export default function Shop() {
const [category, setCategory] = useState('');
const [sortBy, setSortBy] = useState('');
const { addItem } = useCartStore();
// Map grid columns setting to Tailwind classes (responsive)
const gridCols = typeof shopLayout.grid_columns === 'object'
? shopLayout.grid_columns
const gridCols = typeof shopLayout.grid_columns === 'object'
? shopLayout.grid_columns
: { mobile: '2', tablet: '3', desktop: '4' };
// Map to actual Tailwind classes (can't use template literals due to purging)
const mobileClass = {
'1': 'grid-cols-1',
'2': 'grid-cols-2',
'3': 'grid-cols-3',
}[gridCols.mobile] || 'grid-cols-2';
const tabletClass = {
'2': 'md:grid-cols-2',
'3': 'md:grid-cols-3',
'4': 'md:grid-cols-4',
}[gridCols.tablet] || 'md:grid-cols-3';
const desktopClass = {
'2': 'lg:grid-cols-2',
'3': 'lg:grid-cols-3',
@@ -48,22 +49,22 @@ export default function Shop() {
'5': 'lg:grid-cols-5',
'6': 'lg:grid-cols-6',
}[gridCols.desktop] || 'lg:grid-cols-4';
const gridColsClass = `${mobileClass} ${tabletClass} ${desktopClass}`;
// Masonry column classes
const masonryMobileClass = {
'1': 'columns-1',
'2': 'columns-2',
'3': 'columns-3',
}[gridCols.mobile] || 'columns-2';
const masonryTabletClass = {
'2': 'md:columns-2',
'3': 'md:columns-3',
'4': 'md:columns-4',
}[gridCols.tablet] || 'md:columns-3';
const masonryDesktopClass = {
'2': 'lg:columns-2',
'3': 'lg:columns-3',
@@ -71,9 +72,9 @@ export default function Shop() {
'5': 'lg:columns-5',
'6': 'lg:columns-6',
}[gridCols.desktop] || 'lg:columns-4';
const masonryColsClass = `${masonryMobileClass} ${masonryTabletClass} ${masonryDesktopClass}`;
const isMasonry = shopLayout.grid_style === 'masonry';
// Fetch products
@@ -99,7 +100,7 @@ export default function Shop() {
product_id: product.id,
quantity: 1,
});
// Add to local cart store
addItem({
key: `${product.id}`,
@@ -111,7 +112,7 @@ export default function Shop() {
virtual: product.virtual,
downloadable: product.downloadable,
});
toast.success(`${product.name} added to cart!`, {
action: {
label: 'View Cart',
@@ -126,6 +127,11 @@ export default function Shop() {
return (
<Container>
{/* SEO Meta Tags for Social Sharing */}
<SEOHead
title="Shop"
description="Browse our collection of products"
/>
{/* Header */}
<div className="mb-8">
<h1 className="text-4xl font-bold mb-2">Shop</h1>