feat: add SEOHead to all SPA pages for dynamic page titles

Added SEOHead component to:
- ThankYou page (both template styles)
- Login page
- Account/Dashboard
- Account/Orders
- Account/Downloads
- Account/Addresses
- Account/Wishlist
- Account/Licenses
- Account/AccountDetails
- Public Wishlist page

Also created usePageTitle hook as alternative for non-Helmet usage.
This commit is contained in:
Dwindi Ramadhana
2026-01-07 22:51:47 +07:00
parent d7b132d9d9
commit a4a055a98e
10 changed files with 147 additions and 126 deletions

View File

@@ -1,6 +1,7 @@
import React, { useState, useEffect, useRef } from 'react'; import React, { useState, useEffect, useRef } from 'react';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { api } from '@/lib/api/client'; import { api } from '@/lib/api/client';
import SEOHead from '@/components/SEOHead';
interface AvatarSettings { interface AvatarSettings {
allow_custom_avatar: boolean; allow_custom_avatar: boolean;
@@ -198,6 +199,7 @@ export default function AccountDetails() {
return ( return (
<div> <div>
<SEOHead title="Account Details" description="Edit your account information" />
<h1 className="text-2xl font-bold mb-6">Account Details</h1> <h1 className="text-2xl font-bold mb-6">Account Details</h1>
{/* Avatar Section */} {/* Avatar Section */}

View File

@@ -2,6 +2,7 @@ import React, { useState, useEffect } from 'react';
import { MapPin, Plus, Edit, Trash2, Star } from 'lucide-react'; import { MapPin, Plus, Edit, Trash2, Star } from 'lucide-react';
import { api } from '@/lib/api/client'; import { api } from '@/lib/api/client';
import { toast } from 'sonner'; import { toast } from 'sonner';
import SEOHead from '@/components/SEOHead';
interface Address { interface Address {
id: number; id: number;
@@ -153,6 +154,7 @@ export default function Addresses() {
return ( return (
<div> <div>
<SEOHead title="Addresses" description="Manage your shipping and billing addresses" />
<div className="flex items-center justify-between mb-6"> <div className="flex items-center justify-between mb-6">
<h1 className="text-2xl font-bold">Addresses</h1> <h1 className="text-2xl font-bold">Addresses</h1>
<button <button

View File

@@ -1,12 +1,14 @@
import React from 'react'; import React from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { ShoppingBag, Package, MapPin, User } from 'lucide-react'; import { ShoppingBag, Package, MapPin, User } from 'lucide-react';
import SEOHead from '@/components/SEOHead';
export default function Dashboard() { export default function Dashboard() {
const user = (window as any).woonoowCustomer?.user; const user = (window as any).woonoowCustomer?.user;
return ( return (
<div> <div>
<SEOHead title="My Account" description="Manage your account" />
<h1 className="text-2xl font-bold mb-6"> <h1 className="text-2xl font-bold mb-6">
Hello {user?.display_name || 'there'}! Hello {user?.display_name || 'there'}!
</h1> </h1>

View File

@@ -4,6 +4,7 @@ import { Button } from '@/components/ui/button';
import { api } from '@/lib/api/client'; import { api } from '@/lib/api/client';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { formatPrice } from '@/lib/currency'; import { formatPrice } from '@/lib/currency';
import SEOHead from '@/components/SEOHead';
interface DownloadItem { interface DownloadItem {
download_id: string; download_id: string;
@@ -97,6 +98,7 @@ export default function Downloads() {
return ( return (
<div> <div>
<SEOHead title="Downloads" description="Your purchased downloads" />
<h1 className="text-2xl font-bold mb-6">Downloads</h1> <h1 className="text-2xl font-bold mb-6">Downloads</h1>
<div className="space-y-4"> <div className="space-y-4">

View File

@@ -15,6 +15,7 @@ import {
} from '@/components/ui/alert-dialog'; } from '@/components/ui/alert-dialog';
import { Key, Copy, Check, ChevronDown, ChevronUp, Monitor, Globe, Power } from 'lucide-react'; import { Key, Copy, Check, ChevronDown, ChevronUp, Monitor, Globe, Power } from 'lucide-react';
import { toast } from 'sonner'; import { toast } from 'sonner';
import SEOHead from '@/components/SEOHead';
interface Activation { interface Activation {
id: number; id: number;
@@ -94,6 +95,7 @@ export default function Licenses() {
return ( return (
<div className="space-y-6"> <div className="space-y-6">
<SEOHead title="Licenses" description="Manage your software licenses" />
<div> <div>
<h1 className="text-2xl font-bold flex items-center gap-2"> <h1 className="text-2xl font-bold flex items-center gap-2">
<Key className="h-6 w-6" /> <Key className="h-6 w-6" />

View File

@@ -3,6 +3,7 @@ import { Link } from 'react-router-dom';
import { Package, Eye } from 'lucide-react'; import { Package, Eye } from 'lucide-react';
import { api } from '@/lib/api/client'; import { api } from '@/lib/api/client';
import { toast } from 'sonner'; import { toast } from 'sonner';
import SEOHead from '@/components/SEOHead';
interface Order { interface Order {
id: number; id: number;
@@ -86,6 +87,7 @@ export default function Orders() {
return ( return (
<div> <div>
<SEOHead title="Orders" description="View your order history" />
<h1 className="text-2xl font-bold mb-6">Orders</h1> <h1 className="text-2xl font-bold mb-6">Orders</h1>
<div className="space-y-4"> <div className="space-y-4">

View File

@@ -8,6 +8,7 @@ import { formatPrice } from '@/lib/currency';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { useModules } from '@/hooks/useModules'; import { useModules } from '@/hooks/useModules';
import { useModuleSettings } from '@/hooks/useModuleSettings'; import { useModuleSettings } from '@/hooks/useModuleSettings';
import SEOHead from '@/components/SEOHead';
interface WishlistItem { interface WishlistItem {
product_id: number; product_id: number;
@@ -126,6 +127,7 @@ export default function Wishlist() {
return ( return (
<div> <div>
<SEOHead title="Wishlist" description="Your saved products" />
{/* Header */} {/* Header */}
<div className="flex items-center justify-between mb-6"> <div className="flex items-center justify-between mb-6">
<div> <div>

View File

@@ -2,6 +2,7 @@ import React, { useState, useEffect } from 'react';
import { useNavigate, useSearchParams, Link } from 'react-router-dom'; import { useNavigate, useSearchParams, Link } from 'react-router-dom';
import { toast } from 'sonner'; import { toast } from 'sonner';
import Container from '@/components/Layout/Container'; import Container from '@/components/Layout/Container';
import SEOHead from '@/components/SEOHead';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input'; import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label'; import { Label } from '@/components/ui/label';
@@ -115,6 +116,7 @@ export default function Login() {
return ( return (
<Container> <Container>
<SEOHead title="Login" description="Sign in to your account" />
<div className="min-h-[60vh] flex items-center justify-center py-12"> <div className="min-h-[60vh] flex items-center justify-center py-12">
<div className="w-full max-w-md"> <div className="w-full max-w-md">
{/* Back link */} {/* Back link */}

View File

@@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react';
import { useParams, Link, useSearchParams } from 'react-router-dom'; import { useParams, Link, useSearchParams } from 'react-router-dom';
import { useThankYouSettings } from '@/hooks/useAppearanceSettings'; import { useThankYouSettings } from '@/hooks/useAppearanceSettings';
import Container from '@/components/Layout/Container'; import Container from '@/components/Layout/Container';
import SEOHead from '@/components/SEOHead';
import { CheckCircle, ShoppingBag, Package, Truck, User, LogIn } from 'lucide-react'; import { CheckCircle, ShoppingBag, Package, Truck, User, LogIn } from 'lucide-react';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { formatPrice } from '@/lib/currency'; import { formatPrice } from '@/lib/currency';
@@ -74,6 +75,7 @@ export default function ThankYou() {
if (template === 'receipt') { if (template === 'receipt') {
return ( return (
<div style={{ backgroundColor }}> <div style={{ backgroundColor }}>
<SEOHead title="Order Confirmed" description={`Order #${order?.number || orderId} confirmed`} />
<Container> <Container>
<div className="py-12 max-w-2xl mx-auto"> <div className="py-12 max-w-2xl mx-auto">
{/* Receipt Container */} {/* Receipt Container */}
@@ -351,6 +353,7 @@ export default function ThankYou() {
// Render basic style template (default) // Render basic style template (default)
return ( return (
<div style={{ backgroundColor }}> <div style={{ backgroundColor }}>
<SEOHead title="Order Confirmed" description={`Order #${order?.number || orderId} confirmed`} />
<Container> <Container>
<div className="py-12 max-w-3xl mx-auto"> <div className="py-12 max-w-3xl mx-auto">
{/* Success Header */} {/* Success Header */}

View File

@@ -7,6 +7,7 @@ import { Button } from '@/components/ui/button';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { apiClient } from '@/lib/api/client'; import { apiClient } from '@/lib/api/client';
import { formatPrice } from '@/lib/currency'; import { formatPrice } from '@/lib/currency';
import SEOHead from '@/components/SEOHead';
interface ProductData { interface ProductData {
id: number; id: number;
@@ -106,6 +107,7 @@ export default function Wishlist() {
return ( return (
<div className="container mx-auto px-4 py-8"> <div className="container mx-auto px-4 py-8">
<SEOHead title="Wishlist" description="Your saved products" />
<div className="max-w-4xl mx-auto"> <div className="max-w-4xl mx-auto">
<div className="flex items-center justify-between mb-6"> <div className="flex items-center justify-between mb-6">
<h1 className="text-3xl font-bold">My Wishlist</h1> <h1 className="text-3xl font-bold">My Wishlist</h1>