fix: Reactive store name in header + sticky header positioning

1. Store Name Updates in Header 

   Problem: Changing store name doesn't update topbar title
   Solution: Custom event system

   Flow:
   - User saves store settings
   - Dispatch 'woonoow:store:updated' event with store_name
   - Header component listens for event
   - Updates title in real-time

   Files:
   - App.tsx: useState + useEffect listener
   - Store.tsx: Dispatch event on save success

2. Sticky Header Positioning 

   Problem 1: Sticky header hidden under submenu
   Solution: top-[49px] instead of top-0

   Problem 2: Sticky header not edge-to-edge
   Solution: Negative margins to break out of container

   Before:
   <div className="sticky top-0 ...">
     <div className="container ...">

   After:
   <div className="sticky top-[49px] -mx-4 px-4 lg:-mx-6 lg:px-6">
     <div className="container ...">

   Responsive:
   - Mobile: -mx-4 px-4 (breaks out of 16px padding)
   - Desktop: -mx-6 px-6 (breaks out of 24px padding)

   Result:
    Sticky header below submenu (49px offset)
    Edge-to-edge background
    Content still centered in container
    Works in fullscreen, standalone, and wp-admin modes

3. Layout Structure

   Parent: space-y-6 lg:p-6 pb-6
   ├─ Sticky Header: -mx to break out, top-[49px]
   └─ Content: container max-w-5xl

   This ensures:
   - Sticky header spans full width
   - Content stays centered
   - Proper spacing maintained

Files Modified:
- App.tsx: Reactive site title
- Store.tsx: Dispatch update event
- SettingsLayout.tsx: Fixed sticky positioning
This commit is contained in:
dwindown
2025-11-06 14:44:37 +07:00
parent 40fb364035
commit 2b3452e9f2
5 changed files with 25 additions and 17 deletions

View File

@@ -257,9 +257,21 @@ function AddonRoute({ config }: { config: any }) {
} }
function Header({ onFullscreen, fullscreen, showToggle = true }: { onFullscreen: () => void; fullscreen: boolean; showToggle?: boolean }) { function Header({ onFullscreen, fullscreen, showToggle = true }: { onFullscreen: () => void; fullscreen: boolean; showToggle?: boolean }) {
const siteTitle = (window as any).wnw?.siteTitle || 'WooNooW'; const [siteTitle, setSiteTitle] = React.useState((window as any).wnw?.siteTitle || 'WooNooW');
const isStandalone = window.WNW_CONFIG?.standaloneMode ?? false; const isStandalone = window.WNW_CONFIG?.standaloneMode ?? false;
// Listen for store settings updates
React.useEffect(() => {
const handleStoreUpdate = (event: CustomEvent) => {
if (event.detail?.store_name) {
setSiteTitle(event.detail.store_name);
}
};
window.addEventListener('woonoow:store:updated' as any, handleStoreUpdate);
return () => window.removeEventListener('woonoow:store:updated' as any, handleStoreUpdate);
}, []);
const handleLogout = async () => { const handleLogout = async () => {
try { try {
await fetch((window.WNW_CONFIG?.restUrl || '') + '/auth/logout', { await fetch((window.WNW_CONFIG?.restUrl || '') + '/auth/logout', {
@@ -406,7 +418,7 @@ function Shell() {
{isDashboardRoute ? ( {isDashboardRoute ? (
<DashboardSubmenuBar items={main.children} fullscreen={true} /> <DashboardSubmenuBar items={main.children} fullscreen={true} />
) : ( ) : (
<SubmenuBar items={main.children} /> <SubmenuBar items={main.children} fullscreen={true} />
)} )}
<div className="p-4"> <div className="p-4">
<AppRoutes /> <AppRoutes />
@@ -419,7 +431,7 @@ function Shell() {
{isDashboardRoute ? ( {isDashboardRoute ? (
<DashboardSubmenuBar items={main.children} fullscreen={true} /> <DashboardSubmenuBar items={main.children} fullscreen={true} />
) : ( ) : (
<SubmenuBar items={main.children} /> <SubmenuBar items={main.children} fullscreen={true} />
)} )}
<main className="flex-1 p-4 overflow-auto"> <main className="flex-1 p-4 overflow-auto">
<AppRoutes /> <AppRoutes />
@@ -432,7 +444,7 @@ function Shell() {
{isDashboardRoute ? ( {isDashboardRoute ? (
<DashboardSubmenuBar items={main.children} fullscreen={false} /> <DashboardSubmenuBar items={main.children} fullscreen={false} />
) : ( ) : (
<SubmenuBar items={main.children} /> <SubmenuBar items={main.children} fullscreen={false} />
)} )}
<main className="flex-1 p-4 overflow-auto"> <main className="flex-1 p-4 overflow-auto">
<AppRoutes /> <AppRoutes />

View File

@@ -281,7 +281,7 @@ export default function Dashboard() {
}; };
return ( return (
<div className="space-y-6 p-6 pb-6"> <div className="space-y-6 lg:p-6 pb-6">
{/* Header */} {/* Header */}
<div className="mb-6"> <div className="mb-6">
<h1 className="text-2xl font-bold">{__('Dashboard')}</h1> <h1 className="text-2xl font-bold">{__('Dashboard')}</h1>

View File

@@ -117,26 +117,19 @@ export default function PaymentsPage() {
// Initialize order from saved order or gateways // Initialize order from saved order or gateways
React.useEffect(() => { React.useEffect(() => {
if (gateways.length > 0 && manualOrder.length === 0 && onlineOrder.length === 0) { if (gateways.length > 0 && manualOrder.length === 0 && onlineOrder.length === 0) {
console.log('Initializing gateway order...');
console.log('All gateways:', gateways.map((g: PaymentGateway) => ({ id: g.id, type: g.type })));
console.log('Saved order:', savedOrder);
// Use saved order if available, otherwise use gateway order // Use saved order if available, otherwise use gateway order
if (savedOrder.manual.length > 0) { if (savedOrder.manual.length > 0) {
console.log('Using saved manual order:', savedOrder.manual);
setManualOrder(savedOrder.manual); setManualOrder(savedOrder.manual);
} else { } else {
const manual = gateways.filter((g: PaymentGateway) => g.type === 'manual').map((g: PaymentGateway) => g.id); const manual = gateways.filter((g: PaymentGateway) => g.type === 'manual').map((g: PaymentGateway) => g.id);
console.log('Using gateway manual order:', manual);
setManualOrder(manual); setManualOrder(manual);
} }
if (savedOrder.online.length > 0) { if (savedOrder.online.length > 0) {
console.log('Using saved online order:', savedOrder.online);
setOnlineOrder(savedOrder.online); setOnlineOrder(savedOrder.online);
} else { } else {
const online = gateways.filter((g: PaymentGateway) => g.type === 'provider' || g.type === 'other').map((g: PaymentGateway) => g.id); const online = gateways.filter((g: PaymentGateway) => g.type === 'provider' || g.type === 'other').map((g: PaymentGateway) => g.id);
console.log('Using gateway online order:', online);
setOnlineOrder(online); setOnlineOrder(online);
} }
} }
@@ -197,7 +190,6 @@ export default function PaymentsPage() {
// Save order to backend // Save order to backend
try { try {
console.log('Saving manual order:', newOrder);
await api.post('/payments/gateways/order', { await api.post('/payments/gateways/order', {
category: 'manual', category: 'manual',
order: newOrder, order: newOrder,
@@ -225,7 +217,6 @@ export default function PaymentsPage() {
// Save order to backend // Save order to backend
try { try {
console.log('Saving online order:', newOrder);
await api.post('/payments/gateways/order', { await api.post('/payments/gateways/order', {
category: 'online', category: 'online',
order: newOrder, order: newOrder,

View File

@@ -137,6 +137,11 @@ export default function StoreDetailsPage() {
onSuccess: () => { onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['store-settings'] }); queryClient.invalidateQueries({ queryKey: ['store-settings'] });
toast.success('Your store details have been updated successfully.'); toast.success('Your store details have been updated successfully.');
// Dispatch event to update site title in header
window.dispatchEvent(new CustomEvent('woonoow:store:updated', {
detail: { store_name: settings.storeName }
}));
}, },
onError: () => { onError: () => {
toast.error('Failed to save store settings'); toast.error('Failed to save store settings');

View File

@@ -34,10 +34,10 @@ export function SettingsLayout({
}; };
return ( return (
<div className="min-h-screen bg-background"> <div className="space-y-6 lg:p-6 pb-6">
{/* Sticky Header with Save Button */} {/* Sticky Header with Save Button - Edge to edge */}
{onSave && ( {onSave && (
<div className="sticky top-0 z-10 border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60"> <div className="sticky top-[49px] z-10 border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60 -mx-4 px-4 lg:-mx-6 lg:px-6">
<div className="container px-0 max-w-5xl mx-auto py-3 flex items-center justify-between"> <div className="container px-0 max-w-5xl mx-auto py-3 flex items-center justify-between">
<div> <div>
<h1 className="text-lg font-semibold">{title}</h1> <h1 className="text-lg font-semibold">{title}</h1>