From 8fa663118b70f5400f64970da4bee90b5d7615d4 Mon Sep 17 00:00:00 2001
From: "google-labs-jules[bot]"
<161369871+google-labs-jules[bot]@users.noreply.github.com>
Date: Mon, 26 Jan 2026 22:45:06 +0000
Subject: [PATCH 1/3] Refactor Store Settings page to use a live preview editor
Replaces the form-based Store Settings page with a dual-pane editor featuring a sidebar for configuration and a live preview area.
- Created `StoreSettingsEditor.jsx` component.
- Updated `AdminDashboard.jsx` to use the new editor.
- Refactored `Hero`, `Navbar`, `Footer`, and `ProductCard` components to support `previewSettings` and `disableNavigation` props for safe rendering in the admin context.
- Verified changes with Playwright tests.
Co-authored-by: AJFrio <20246916+AJFrio@users.noreply.github.com>
From a983e7da6285b0dfdeb0e099cb0d4ff981914f21 Mon Sep 17 00:00:00 2001
From: "google-labs-jules[bot]"
<161369871+google-labs-jules[bot]@users.noreply.github.com>
Date: Mon, 26 Jan 2026 23:16:12 +0000
Subject: [PATCH 2/3] Refactor Store Settings page to use a live preview editor
Replaces the form-based Store Settings page with a dual-pane editor featuring a sidebar for configuration and a live preview area.
- Created `StoreSettingsEditor.jsx` component.
- Updated `AdminDashboard.jsx` to use the new editor.
- Refactored `Hero`, `Navbar`, `Footer`, and `ProductCard` components to support `previewSettings` and `disableNavigation` props for safe rendering in the admin context.
- Verified changes with Playwright tests.
Co-authored-by: AJFrio <20246916+AJFrio@users.noreply.github.com>
---
src/components/admin/StoreSettingsEditor.jsx | 834 +++++++++++++++++++
src/components/storefront/Footer.jsx | 11 +-
src/components/storefront/Hero.jsx | 22 +-
src/components/storefront/Navbar.jsx | 66 +-
src/components/storefront/ProductCard.jsx | 30 +-
src/pages/admin/AdminDashboard.jsx | 14 +-
6 files changed, 921 insertions(+), 56 deletions(-)
create mode 100644 src/components/admin/StoreSettingsEditor.jsx
diff --git a/src/components/admin/StoreSettingsEditor.jsx b/src/components/admin/StoreSettingsEditor.jsx
new file mode 100644
index 0000000..d72ca97
--- /dev/null
+++ b/src/components/admin/StoreSettingsEditor.jsx
@@ -0,0 +1,834 @@
+import { useState, useEffect, useMemo } from 'react'
+import { Card, CardContent, CardHeader, CardTitle } from '../ui/card'
+import { Input } from '../ui/input'
+import { Button } from '../ui/button'
+import { Select } from '../ui/select'
+import { Switch } from '../ui/switch'
+import { adminApiRequest } from '../../lib/auth'
+import { normalizeImageUrl } from '../../lib/utils'
+import ImageUrlField from './ImageUrlField'
+import { DEFAULT_STORE_THEME, FONT_OPTIONS, resolveStorefrontTheme, BASE_RADIUS_PX } from '../../lib/theme'
+import {
+ AlertDialog,
+ AlertDialogContent,
+ AlertDialogHeader,
+ AlertDialogTitle,
+ AlertDialogDescription,
+ AlertDialogFooter,
+ AlertDialogAction,
+ AlertDialogCancel
+} from '../ui/alert-dialog'
+import { Navbar } from '../../components/storefront/Navbar'
+import { Hero } from '../../components/storefront/Hero'
+import { ProductCard } from '../../components/storefront/ProductCard'
+import { Footer } from '../../components/storefront/Footer'
+import { Paintbrush, Type, Layout, Image as ImageIcon, Info, MapPin, Mail } from 'lucide-react'
+
+const MOCK_PRODUCTS = [
+ {
+ id: 'mock-1',
+ name: 'Premium Headphones',
+ tagline: 'High-fidelity wireless audio',
+ description: 'High-fidelity wireless headphones with noise cancellation.',
+ price: 29900,
+ currency: 'USD',
+ imageUrl: 'https://images.unsplash.com/photo-1505740420928-5e560c06d30e?w=500&q=80',
+ stripePriceId: 'price_mock'
+ },
+ {
+ id: 'mock-2',
+ name: 'Ergonomic Chair',
+ tagline: 'Comfort for your workspace',
+ description: 'Designed for comfort and productivity during long work sessions.',
+ price: 45000,
+ currency: 'USD',
+ imageUrl: 'https://images.unsplash.com/photo-1592078615290-033ee584e267?w=500&q=80',
+ stripePriceId: 'price_mock'
+ },
+ {
+ id: 'mock-3',
+ name: 'Mechanical Keyboard',
+ tagline: 'Tactile typing experience',
+ description: 'Tactile switches and customizable RGB lighting.',
+ price: 12000,
+ currency: 'USD',
+ imageUrl: 'https://images.unsplash.com/photo-1587829741301-dc798b91a91e?w=500&q=80',
+ stripePriceId: 'price_mock'
+ }
+]
+
+const COLOR_GROUPS = [
+ {
+ title: 'Brand Colors',
+ description: 'Primary palette for calls to action, highlights, and text accents.',
+ fields: [
+ { key: 'primary', label: 'Primary Color' },
+ { key: 'secondary', label: 'Secondary Color' },
+ { key: 'accent', label: 'Accent Color' },
+ { key: 'text', label: 'Text Color' },
+ ],
+ },
+ {
+ title: 'Surface Colors',
+ description: 'Backgrounds that shape the storefront canvas and product cards.',
+ fields: [
+ { key: 'background', label: 'Page Background' },
+ { key: 'card', label: 'Product Card Background' },
+ ],
+ },
+]
+
+function createThemeState(theme = DEFAULT_STORE_THEME) {
+ return {
+ colors: {
+ primary: theme.colors.primary,
+ secondary: theme.colors.secondary,
+ accent: theme.colors.accent,
+ text: theme.colors.text,
+ background: (theme.colors && theme.colors.background) || DEFAULT_STORE_THEME.colors.background,
+ card: (theme.colors && theme.colors.card) || DEFAULT_STORE_THEME.colors.card,
+ },
+ typography: {
+ fontId: theme.typography.fontId,
+ },
+ corners: {
+ enabled: theme.corners.enabled,
+ radiusMultiplier: theme.corners.radiusMultiplier,
+ },
+ }
+}
+
+function extractThemeState(resolvedTheme) {
+ return createThemeState({
+ colors: resolvedTheme.colors || DEFAULT_STORE_THEME.colors,
+ typography: {
+ fontId: resolvedTheme.typography?.fontId || DEFAULT_STORE_THEME.typography.fontId,
+ },
+ corners: {
+ enabled: resolvedTheme.corners?.enabled ?? DEFAULT_STORE_THEME.corners.enabled,
+ radiusMultiplier: resolvedTheme.corners?.radiusMultiplier ?? DEFAULT_STORE_THEME.corners.radiusMultiplier,
+ },
+ })
+}
+
+function sanitizeHexInput(value) {
+ if (!value) return '#'
+ let next = String(value).trim().replace(/[^0-9a-fA-F#]/g, '')
+ if (!next.startsWith('#')) {
+ next = `#${next}`
+ }
+ if (next.length === 4) {
+ const [, r, g, b] = next
+ next = `#${r}${r}${g}${g}${b}${b}`
+ }
+ if (next.length > 7) {
+ next = next.slice(0, 7)
+ }
+ return next.toUpperCase()
+}
+
+export function StoreSettingsEditor() {
+ const [activeSection, setActiveSection] = useState('theme')
+ const [settings, setSettings] = useState({
+ logoType: 'text',
+ logoText: 'OpenShop',
+ logoImageUrl: '',
+ storeName: 'OpenShop',
+ storeDescription: 'Your amazing online store',
+ heroImageUrl: '',
+ heroTitle: 'Welcome to OpenShop',
+ heroSubtitle: 'Discover amazing products at unbeatable prices. Built on Cloudflare for lightning-fast performance.',
+ aboutHeroImageUrl: '',
+ aboutHeroTitle: 'About Us',
+ aboutHeroSubtitle: 'Learn more about our story and mission',
+ aboutContent: 'Welcome to our store! We are passionate about providing high-quality products and exceptional customer service. Our journey began with a simple idea: to make great products accessible to everyone.\n\nWe believe in quality, sustainability, and building lasting relationships with our customers. Every product in our catalog is carefully selected to meet our high standards.\n\nThank you for choosing us for your shopping needs!',
+ contactEmail: 'contact@example.com',
+ businessName: '',
+ businessAddressLine1: '',
+ businessAddressLine2: '',
+ businessCity: '',
+ businessState: '',
+ businessPostalCode: '',
+ businessCountry: ''
+ })
+ const [loading, setLoading] = useState(false)
+ const [saving, setSaving] = useState(false)
+ const [modalImage, setModalImage] = useState(null)
+ const [savedOpen, setSavedOpen] = useState(false)
+ const [errorOpen, setErrorOpen] = useState(false)
+ const [errorText, setErrorText] = useState('')
+ const [driveNotice, setDriveNotice] = useState('')
+ const [driveNoticeTimer, setDriveNoticeTimer] = useState(null)
+
+ const [themeState, setThemeState] = useState(createThemeState())
+ const [themeLoading, setThemeLoading] = useState(true)
+ const [themeSaving, setThemeSaving] = useState(false)
+ const [themeDirty, setThemeDirty] = useState(false)
+ const [themeHasOverrides, setThemeHasOverrides] = useState(false)
+ const [themeMessage, setThemeMessage] = useState('')
+ const [themeError, setThemeError] = useState('')
+ const [resetConfirmOpen, setResetConfirmOpen] = useState(false)
+
+ const previewTheme = useMemo(() => resolveStorefrontTheme(themeState), [themeState])
+ const previewStyles = useMemo(() => ({ ...previewTheme.cssVariables }), [previewTheme])
+ const selectedFontOption = useMemo(
+ () => FONT_OPTIONS.find((font) => font.id === themeState.typography.fontId) || FONT_OPTIONS[0],
+ [themeState.typography.fontId]
+ )
+
+ const computedRadiusPx = Math.round(previewTheme.corners.radiusPx || 0)
+
+ useEffect(() => {
+ fetchSettings()
+ fetchTheme()
+ }, [])
+
+ const fetchTheme = async () => {
+ try {
+ setThemeLoading(true)
+ const response = await adminApiRequest('/api/admin/storefront/theme')
+ if (!response.ok) {
+ const errorData = await response.json().catch(() => ({}))
+ throw new Error(errorData.error || 'Failed to load storefront theme')
+ }
+ const data = await response.json()
+ const resolved = resolveStorefrontTheme(data)
+ const nextState = extractThemeState(resolved)
+ setThemeState(nextState)
+ setThemeHasOverrides(Boolean(resolved.meta?.updatedAt))
+ setThemeDirty(false)
+ setThemeMessage('')
+ setThemeError('')
+ } catch (error) {
+ console.error('Error fetching storefront theme:', error)
+ setThemeError(error.message || 'Failed to load storefront theme')
+ } finally {
+ setThemeLoading(false)
+ }
+ }
+
+ const fetchSettings = async () => {
+ try {
+ setLoading(true)
+ const response = await fetch('/api/store-settings')
+ if (response.ok) {
+ const data = await response.json()
+ setSettings(prev => ({
+ ...prev,
+ ...data,
+ // Ensure about page fields have defaults if missing
+ aboutHeroImageUrl: data.aboutHeroImageUrl || '',
+ aboutHeroTitle: data.aboutHeroTitle || prev.aboutHeroTitle,
+ aboutHeroSubtitle: data.aboutHeroSubtitle || prev.aboutHeroSubtitle,
+ aboutContent: data.aboutContent || prev.aboutContent || ''
+ }))
+ }
+ } catch (error) {
+ console.error('Error fetching store settings:', error)
+ } finally {
+ setLoading(false)
+ }
+ }
+
+ const handleChange = (e) => {
+ const { name, value } = e.target
+ const shouldNormalize = name === 'logoImageUrl' || name === 'heroImageUrl' || name === 'aboutHeroImageUrl'
+ const normalized = shouldNormalize ? maybeNormalizeDriveUrl(value) : value
+ setSettings(prev => ({
+ ...prev,
+ [name]: normalized
+ }))
+ }
+
+ const handleLogoTypeChange = (e) => {
+ const logoType = e.target.value
+ setSettings(prev => ({
+ ...prev,
+ logoType
+ }))
+ }
+
+ function maybeNormalizeDriveUrl(input) {
+ const val = (input || '').trim()
+ if (!val) return input
+ const isDrive = val.includes('drive.google.com') || val.includes('drive.usercontent.google.com')
+ if (!isDrive) return input
+ const fileMatch = val.match(/\/file\/d\/([a-zA-Z0-9_-]+)/)
+ const idMatch = val.match(/[?]id=([a-zA-Z0-9_-]+)/)
+ const id = (fileMatch && fileMatch[1]) || (idMatch && idMatch[1]) || null
+ const normalized = id
+ ? `https://drive.usercontent.google.com/download?id=${id}&export=view`
+ : val
+ if (normalized !== val) {
+ setDriveNotice('Google Drive link detected - converted for reliable preview and delivery.')
+ if (driveNoticeTimer) clearTimeout(driveNoticeTimer)
+ const t = setTimeout(() => setDriveNotice(''), 3000)
+ setDriveNoticeTimer(t)
+ }
+ return normalized
+ }
+
+ const markThemeDirty = () => {
+ setThemeDirty(true)
+ setThemeMessage('')
+ setThemeError('')
+ }
+
+ const handleThemeColorChange = (key, value) => {
+ const sanitized = sanitizeHexInput(value)
+ setThemeState((prev) => ({
+ ...prev,
+ colors: {
+ ...prev.colors,
+ [key]: sanitized,
+ },
+ }))
+ markThemeDirty()
+ }
+
+ const handleThemeFontChange = (fontId) => {
+ setThemeState((prev) => ({
+ ...prev,
+ typography: {
+ ...prev.typography,
+ fontId,
+ },
+ }))
+ markThemeDirty()
+ }
+
+ const handleThemeCornerToggle = (enabled) => {
+ setThemeState((prev) => ({
+ ...prev,
+ corners: {
+ ...prev.corners,
+ enabled,
+ },
+ }))
+ markThemeDirty()
+ }
+
+ const handleThemeCornerMultiplierChange = (value) => {
+ const numeric = Number(value)
+ if (Number.isNaN(numeric)) return
+ const clamped = Math.min(Math.max(numeric, 0), 4)
+ setThemeState((prev) => ({
+ ...prev,
+ corners: {
+ ...prev.corners,
+ radiusMultiplier: clamped,
+ },
+ }))
+ markThemeDirty()
+ }
+
+ const persistTheme = async () => {
+ try {
+ setThemeError('')
+ setThemeMessage('')
+ const response = await adminApiRequest('/api/admin/storefront/theme', {
+ method: 'PUT',
+ body: JSON.stringify(themeState),
+ })
+ if (!response.ok) {
+ const errorData = await response.json().catch(() => ({}))
+ throw new Error(errorData.error || 'Failed to update storefront theme')
+ }
+ const data = await response.json()
+ const resolved = resolveStorefrontTheme(data)
+ setThemeState(extractThemeState(resolved))
+ setThemeHasOverrides(Boolean(resolved.meta?.updatedAt ?? Date.now()))
+ setThemeDirty(false)
+ setThemeMessage('Theme settings saved.')
+ return resolved
+ } catch (error) {
+ console.error('Error updating storefront theme:', error)
+ setThemeError(error.message || 'Failed to save theme')
+ throw error
+ }
+ }
+
+ const handleThemeReset = async () => {
+ if (themeSaving) return false
+ try {
+ setThemeSaving(true)
+ setThemeError('')
+ setThemeMessage('')
+ const response = await adminApiRequest('/api/admin/storefront/theme', { method: 'DELETE' })
+ if (!response.ok) {
+ const errorData = await response.json().catch(() => ({}))
+ throw new Error(errorData.error || 'Failed to reset storefront theme')
+ }
+ const data = await response.json()
+ const resolved = resolveStorefrontTheme(data)
+ setThemeState(extractThemeState(resolved))
+ setThemeHasOverrides(false)
+ setThemeDirty(false)
+ setThemeMessage('Theme reset to defaults.')
+ return true
+ } catch (error) {
+ console.error('Error resetting storefront theme:', error)
+ setThemeError(error.message || 'Failed to reset theme')
+ return false
+ } finally {
+ setThemeSaving(false)
+ }
+ }
+
+ const confirmThemeReset = async () => {
+ const result = await handleThemeReset()
+ if (result) {
+ setResetConfirmOpen(false)
+ }
+ }
+
+ const handleSubmit = async (e) => {
+ if (e) {
+ e.preventDefault()
+ }
+ setSaving(true)
+
+ try {
+ if (!themeLoading && themeDirty) {
+ await persistTheme()
+ }
+
+ const response = await adminApiRequest('/api/admin/store-settings', {
+ method: 'PUT',
+ body: JSON.stringify(settings),
+ })
+
+ if (response.ok) {
+ const updatedSettings = await response.json()
+ setSettings(updatedSettings)
+ setSavedOpen(true)
+ } else {
+ const error = await response.json()
+ throw new Error(error.error || 'Failed to update settings')
+ }
+ } catch (error) {
+ console.error('Error saving store settings:', error)
+ setErrorText('Error saving settings: ' + (error?.message || 'Unknown error'))
+ setErrorOpen(true)
+ } finally {
+ setSaving(false)
+ }
+ }
+
+ const menuItems = [
+ { id: 'theme', label: 'Theme & Colors', icon: Paintbrush },
+ { id: 'identity', label: 'Identity', icon: ImageIcon },
+ { id: 'info', label: 'Store Info', icon: Info },
+ { id: 'hero', label: 'Homepage Hero', icon: Layout },
+ { id: 'about', label: 'About Page', icon: Type },
+ { id: 'contact', label: 'Contact & Business', icon: MapPin },
+ ]
+
+ return (
+
+ {/* Sidebar Controls */}
+
+
+
Store Editor
+
Real-time preview
+
+
+ {/* Navigation Tabs */}
+
+ {menuItems.map(item => {
+ const Icon = item.icon
+ return (
+
+ )
+ })}
+
+
+ {/* Form Area */}
+
+ {activeSection === 'theme' && (
+
+
+
Theme
+
+
+
+ {COLOR_GROUPS.map((group) => (
+
+
+
{group.title}
+
{group.description}
+
+
+ {group.fields.map((field) => (
+
+ ))}
+
+
+ ))}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ handleThemeCornerMultiplierChange(event.target.value)}
+ disabled={!themeState.corners.enabled}
+ className="h-8"
+ />
+
+
+
+
+ )}
+
+ {activeSection === 'identity' && (
+
+
Brand Identity
+
+
+
+
+
+ {settings.logoType === 'text' ? (
+
+
+
+
+ ) : (
+
+
+
setSettings(prev => ({ ...prev, logoImageUrl: val }))}
+ placeholder="https://example.com/logo.png"
+ onPreview={(src) => setModalImage(src)}
+ hideInput
+ />
+ {driveNotice && (
+ {driveNotice}
+ )}
+ Recommended size: 200x50px
+
+ )}
+
+ )}
+
+ {activeSection === 'info' && (
+
+
Store Info
+
+
+
+
+
+
+
+
+
+
+ )}
+
+ {activeSection === 'hero' && (
+
+
Homepage Hero
+
+
+ setSettings(prev => ({ ...prev, heroImageUrl: val }))}
+ placeholder="https://example.com/hero.jpg"
+ onPreview={(src) => setModalImage(src)}
+ hideInput
+ />
+
+
+
+
+
+
+
+
+
+
+ )}
+
+ {activeSection === 'about' && (
+
+
About Page
+
+
+ setSettings(prev => ({ ...prev, aboutHeroImageUrl: val }))}
+ placeholder="https://example.com/about-hero.jpg"
+ onPreview={(src) => setModalImage(src)}
+ hideInput
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )}
+
+ {activeSection === 'contact' && (
+
+
Contact & Business
+
+
+
+
+
+
+ )}
+
+
+ {/* Footer Actions */}
+
+ {themeDirty ? 'Unsaved changes' : 'All saved'}
+
+
+
+
+ {/* Main Preview Area */}
+
+
+ {activeSection !== 'about' ? (
+ <>
+
+
+
+
Featured Products
+
+ {MOCK_PRODUCTS.map(product => (
+
+ ))}
+
+
+
{/* Spacer */}
+
+ >
+ ) : (
+ <>
+
+
+ {settings.aboutHeroImageUrl ? (
+
+ ) : (
+
+ )}
+
+
+
{settings.aboutHeroTitle}
+
{settings.aboutHeroSubtitle}
+
+
+
+
+ {settings.aboutContent.split('\n').map((paragraph, i) => (
+ paragraph.trim() &&
{paragraph}
+ ))}
+
+
+
+ >
+ )}
+
+
+
+ {/* Modals */}
+ {modalImage && (
+
setModalImage(null)}>
+
e.stopPropagation()}>
+

+
+
+
+ )}
+
+
+
+ Reset storefront theme?
+
+ Resetting restores the bundled colors, fonts, and corner radius. This action cannot be undone.
+
+
+
+ Cancel
+
+ {themeSaving ? 'Resetting...' : 'Reset Theme'}
+
+
+
+
+
+
+
+ Settings saved
+
+ Store settings updated successfully.
+
+
+
+ setSavedOpen(false)}>OK
+
+
+
+
+
+
+ Something went wrong
+ {errorText}
+
+
+ OK
+
+
+
+
+ )
+}
diff --git a/src/components/storefront/Footer.jsx b/src/components/storefront/Footer.jsx
index 6e59b9a..3eabcbc 100644
--- a/src/components/storefront/Footer.jsx
+++ b/src/components/storefront/Footer.jsx
@@ -1,12 +1,19 @@
import { useState, useEffect } from 'react'
-export function Footer() {
+export function Footer({ previewSettings }) {
const [contactEmail, setContactEmail] = useState('contact@example.com')
const [loading, setLoading] = useState(true)
useEffect(() => {
+ // If preview settings are provided, use them directly
+ if (previewSettings) {
+ setContactEmail(previewSettings.contactEmail || 'contact@example.com')
+ setLoading(false)
+ return
+ }
+
fetchContactEmail()
- }, [])
+ }, [previewSettings])
const fetchContactEmail = async () => {
try {
diff --git a/src/components/storefront/Hero.jsx b/src/components/storefront/Hero.jsx
index 6ab2ed0..9764fec 100644
--- a/src/components/storefront/Hero.jsx
+++ b/src/components/storefront/Hero.jsx
@@ -3,21 +3,27 @@ import { Link } from 'react-router-dom'
import { normalizeImageUrl } from '../../lib/utils'
import { Button } from '../ui/button'
-export function Hero() {
- const [settings, setSettings] = useState({
+export function Hero({ previewSettings }) {
+ const [fetchedSettings, setFetchedSettings] = useState({
heroImageUrl: '',
heroTitle: 'Welcome to OpenShop',
heroSubtitle: 'Discover amazing products at unbeatable prices. Built on Cloudflare for lightning-fast performance.'
})
+ // Use previewSettings if provided, otherwise use fetched settings
+ const settings = previewSettings || fetchedSettings
+
useEffect(() => {
+ // If we have preview settings, we don't need to fetch
+ if (previewSettings) return
+
let isMounted = true
async function fetchSettings() {
try {
const res = await fetch('/api/store-settings')
if (res.ok) {
const data = await res.json()
- if (isMounted) setSettings(prev => ({
+ if (isMounted) setFetchedSettings(prev => ({
...prev,
heroImageUrl: data.heroImageUrl || '',
heroTitle: data.heroTitle || prev.heroTitle,
@@ -30,23 +36,23 @@ export function Hero() {
}
fetchSettings()
return () => { isMounted = false }
- }, [])
+ }, [previewSettings])
return (
-
+
{settings.heroImageUrl ? (
) : (
-
+
)}
-
+
{settings.heroTitle}
{settings.heroSubtitle}
diff --git a/src/components/storefront/Navbar.jsx b/src/components/storefront/Navbar.jsx
index 9deea19..6e511e0 100644
--- a/src/components/storefront/Navbar.jsx
+++ b/src/components/storefront/Navbar.jsx
@@ -5,10 +5,10 @@ import { Button } from '../ui/button'
import { useCart } from '../../contexts/CartContext'
import { ShoppingCart } from 'lucide-react'
-export function Navbar() {
+export function Navbar({ previewSettings, disableNavigation }) {
const [collections, setCollections] = useState([])
const [collectionsWithProducts, setCollectionsWithProducts] = useState([])
- const [storeSettings, setStoreSettings] = useState({
+ const [fetchedStoreSettings, setFetchedStoreSettings] = useState({
logoType: 'text',
logoText: 'OpenShop',
logoImageUrl: ''
@@ -17,11 +17,15 @@ export function Navbar() {
const [expandedCollections, setExpandedCollections] = useState(new Set())
const { itemCount, toggleCart } = useCart()
+ const storeSettings = previewSettings || fetchedStoreSettings
+
useEffect(() => {
// Fetch collections, products, and store settings for navigation
fetchCollectionsAndProducts()
- fetchStoreSettings()
- }, [])
+ if (!previewSettings) {
+ fetchStoreSettings()
+ }
+ }, [previewSettings])
const fetchCollectionsAndProducts = async () => {
try {
@@ -54,7 +58,7 @@ export function Navbar() {
const response = await fetch('/api/store-settings')
if (response.ok) {
const data = await response.json()
- setStoreSettings(data)
+ setFetchedStoreSettings(data)
}
} catch (error) {
console.error('Error fetching store settings:', error)
@@ -71,12 +75,26 @@ export function Navbar() {
setExpandedCollections(newExpanded)
}
+ const NavLink = ({ to, className, children, onClick }) => {
+ if (disableNavigation) {
+ return (
+
{ e.preventDefault(); onClick && onClick(e); }}
+ >
+ {children}
+
+ )
+ }
+ return
{children}
+ }
+
return (