Fix IDE errors from ESLint cleanup
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
@@ -118,7 +118,7 @@ export function CaptchaWidget({ provider, siteKey, onToken, action = 'checkout'
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [provider, siteKey]);
|
}, [provider, siteKey, onToken]);
|
||||||
|
|
||||||
// Execute reCAPTCHA when loaded
|
// Execute reCAPTCHA when loaded
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect, useRef } from 'react';
|
import { useEffect, useRef } from 'react';
|
||||||
import { useSearchParams } from 'react-router-dom';
|
import { useSearchParams } from 'react-router-dom';
|
||||||
import { applyCoupon } from '@/lib/cart/api';
|
import { applyCoupon } from '@/lib/cart/api';
|
||||||
import { useCartStore } from '@/lib/cart/store';
|
import { useCartStore } from '@/lib/cart/store';
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { Link, useNavigate } from 'react-router-dom';
|
|||||||
import { ShoppingCart, Heart } from 'lucide-react';
|
import { ShoppingCart, Heart } from 'lucide-react';
|
||||||
import { formatPrice, formatDiscount } from '@/lib/currency';
|
import { formatPrice, formatDiscount } from '@/lib/currency';
|
||||||
import { Button } from './ui/button';
|
import { Button } from './ui/button';
|
||||||
import { useLayout } from '@/contexts/ThemeContext';
|
|
||||||
import { useShopSettings } from '@/hooks/useAppearanceSettings';
|
import { useShopSettings } from '@/hooks/useAppearanceSettings';
|
||||||
import { useWishlist } from '@/hooks/useWishlist';
|
import { useWishlist } from '@/hooks/useWishlist';
|
||||||
import { useModules } from '@/hooks/useModules';
|
import { useModules } from '@/hooks/useModules';
|
||||||
@@ -26,7 +26,6 @@ interface ProductCardProps {
|
|||||||
|
|
||||||
export function ProductCard({ product, onAddToCart }: ProductCardProps) {
|
export function ProductCard({ product, onAddToCart }: ProductCardProps) {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { isClassic, isModern, isBoutique, isLaunch } = useLayout();
|
|
||||||
const { layout, elements, addToCart, saleBadge, isLoading } = useShopSettings();
|
const { layout, elements, addToCart, saleBadge, isLoading } = useShopSettings();
|
||||||
const { isEnabled: wishlistEnabled, isInWishlist, toggleWishlist } = useWishlist();
|
const { isEnabled: wishlistEnabled, isInWishlist, toggleWishlist } = useWishlist();
|
||||||
const { isEnabled: isModuleEnabled } = useModules();
|
const { isEnabled: isModuleEnabled } = useModules();
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ export function SEOHead({
|
|||||||
}: SEOHeadProps) {
|
}: SEOHeadProps) {
|
||||||
const config = (window as any).woonoowCustomer;
|
const config = (window as any).woonoowCustomer;
|
||||||
const siteName = config?.siteName || 'Store';
|
const siteName = config?.siteName || 'Store';
|
||||||
const siteUrl = config?.siteUrl || '';
|
|
||||||
|
|
||||||
const fullTitle = title ? `${title} | ${siteName}` : siteName;
|
const fullTitle = title ? `${title} | ${siteName}` : siteName;
|
||||||
const fullUrl = url || (typeof window !== 'undefined' ? window.location.href : '');
|
const fullUrl = url || (typeof window !== 'undefined' ? window.location.href : '');
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import * as LucideIcons from 'lucide-react';
|
|
||||||
|
|
||||||
interface SharedContentProps {
|
interface SharedContentProps {
|
||||||
// Content
|
// Content
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ const SubscriptionTimeline: React.FC<Props> = ({ subscription }) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const isMonth = subscription.billing_period === 'month';
|
const _isMonth = subscription.billing_period === 'month';
|
||||||
const intervalLabel = `${subscription.billing_interval} ${subscription.billing_period}${subscription.billing_interval > 1 ? 's' : ''}`;
|
const intervalLabel = `${subscription.billing_interval} ${subscription.billing_period}${subscription.billing_interval > 1 ? 's' : ''}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ export function WooCommerceHooks({ context, hookName, productId, className }: Wo
|
|||||||
/**
|
/**
|
||||||
* Hook to get all hooks for a context
|
* Hook to get all hooks for a context
|
||||||
*/
|
*/
|
||||||
export function useWooCommerceHooks(context: 'product' | 'shop' | 'cart' | 'checkout', productId?: number) {
|
function useWooCommerceHooks(context: 'product' | 'shop' | 'cart' | 'checkout', productId?: number) {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: ['wc-hooks', context, productId],
|
queryKey: ['wc-hooks', context, productId],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
|
|||||||
@@ -35,12 +35,10 @@ const buttonVariants = cva(
|
|||||||
|
|
||||||
export interface ButtonProps
|
export interface ButtonProps
|
||||||
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||||
VariantProps<typeof buttonVariants> {
|
VariantProps<typeof buttonVariants> { }
|
||||||
asChild?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||||
({ className, variant, size, asChild = false, ...props }, ref) => {
|
({ className, variant, size, ...props }, ref) => {
|
||||||
// Simplified: always render as button (asChild not supported for now)
|
// Simplified: always render as button (asChild not supported for now)
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
@@ -52,5 +50,8 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
Button.displayName = "Button"
|
Button.displayName = "Button"
|
||||||
|
export { Button }
|
||||||
|
|
||||||
export { Button, buttonVariants }
|
// eslint-disable-next-line react-refresh/only-export-components
|
||||||
|
export { buttonVariants }
|
||||||
|
export default Button;
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
export interface InputProps
|
export type InputProps = React.InputHTMLAttributes<HTMLInputElement>;
|
||||||
extends React.InputHTMLAttributes<HTMLInputElement> { }
|
|
||||||
|
|
||||||
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||||
({ className, type, ...props }, ref) => {
|
({ className, type, ...props }, ref) => {
|
||||||
|
|||||||
@@ -69,63 +69,11 @@ const TYPOGRAPHY_PRESETS = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
export function ThemeProvider({
|
||||||
* Load Google Fonts for typography preset
|
config: initialConfig,
|
||||||
*/
|
children
|
||||||
function loadTypography(preset: string, customFonts?: { heading: string; body: string }) {
|
}: {
|
||||||
// Remove existing font link if any
|
config: ThemeConfig;
|
||||||
const existingLink = document.getElementById('woonoow-fonts');
|
|
||||||
if (existingLink) {
|
|
||||||
existingLink.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (preset === 'custom' && customFonts) {
|
|
||||||
// TODO: Handle custom fonts
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const fontMap: Record<string, string[]> = {
|
|
||||||
professional: ['Inter:400,600,700', 'Lora:400,700'],
|
|
||||||
modern: ['Poppins:400,600,700', 'Roboto:400,700'],
|
|
||||||
elegant: ['Playfair+Display:400,700', 'Source+Sans+Pro:400,700'],
|
|
||||||
tech: ['Space+Grotesk:400,700', 'IBM+Plex+Mono:400,700'],
|
|
||||||
};
|
|
||||||
|
|
||||||
const fonts = fontMap[preset];
|
|
||||||
if (!fonts) return;
|
|
||||||
|
|
||||||
const link = document.createElement('link');
|
|
||||||
link.id = 'woonoow-fonts';
|
|
||||||
link.href = `https://fonts.googleapis.com/css2?family=${fonts.join('&family=')}&display=swap`;
|
|
||||||
link.rel = 'stylesheet';
|
|
||||||
document.head.appendChild(link);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate color shades from base color
|
|
||||||
*/
|
|
||||||
function generateColorShades(baseColor: string): Record<number, string> {
|
|
||||||
// For now, just return the base color
|
|
||||||
// TODO: Implement proper color shade generation
|
|
||||||
return {
|
|
||||||
50: baseColor,
|
|
||||||
100: baseColor,
|
|
||||||
200: baseColor,
|
|
||||||
300: baseColor,
|
|
||||||
400: baseColor,
|
|
||||||
500: baseColor,
|
|
||||||
600: baseColor,
|
|
||||||
700: baseColor,
|
|
||||||
800: baseColor,
|
|
||||||
900: baseColor,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ThemeProvider({
|
|
||||||
config: initialConfig,
|
|
||||||
children
|
|
||||||
}: {
|
|
||||||
config: ThemeConfig;
|
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
}) {
|
}) {
|
||||||
const [config, setConfig] = useState<ThemeConfig>(initialConfig);
|
const [config, setConfig] = useState<ThemeConfig>(initialConfig);
|
||||||
@@ -139,14 +87,14 @@ export function ThemeProvider({
|
|||||||
const response = await fetch(`${apiRoot}/appearance/settings`, {
|
const response = await fetch(`${apiRoot}/appearance/settings`, {
|
||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
const settings = data.data;
|
const settings = data.data;
|
||||||
|
|
||||||
if (settings?.general) {
|
if (settings?.general) {
|
||||||
const general = settings.general;
|
const general = settings.general;
|
||||||
|
|
||||||
// Map API settings to theme config
|
// Map API settings to theme config
|
||||||
const mappedPreset = FONT_PAIR_MAP[general.typography?.predefined_pair] || 'modern';
|
const mappedPreset = FONT_PAIR_MAP[general.typography?.predefined_pair] || 'modern';
|
||||||
const newConfig: ThemeConfig = {
|
const newConfig: ThemeConfig = {
|
||||||
@@ -164,7 +112,7 @@ export function ThemeProvider({
|
|||||||
scale: general.typography?.scale || 1.0,
|
scale: general.typography?.scale || 1.0,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
setConfig(newConfig);
|
setConfig(newConfig);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -174,51 +122,55 @@ export function ThemeProvider({
|
|||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
fetchSettings();
|
fetchSettings();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const root = document.documentElement;
|
const handleLocationChange = () => {
|
||||||
|
const root = document.documentElement;
|
||||||
// Inject color CSS variables
|
|
||||||
root.style.setProperty('--color-primary', config.colors.primary);
|
// Inject color CSS variables
|
||||||
root.style.setProperty('--color-secondary', config.colors.secondary);
|
root.style.setProperty('--color-primary', config.colors.primary);
|
||||||
root.style.setProperty('--color-accent', config.colors.accent);
|
root.style.setProperty('--color-secondary', config.colors.secondary);
|
||||||
|
root.style.setProperty('--color-accent', config.colors.accent);
|
||||||
if (config.colors.background) {
|
|
||||||
root.style.setProperty('--color-background', config.colors.background);
|
if (config.colors.background) {
|
||||||
}
|
root.style.setProperty('--color-background', config.colors.background);
|
||||||
if (config.colors.text) {
|
}
|
||||||
root.style.setProperty('--color-text', config.colors.text);
|
if (config.colors.text) {
|
||||||
}
|
root.style.setProperty('--color-text', config.colors.text);
|
||||||
|
}
|
||||||
// Inject typography CSS variables
|
|
||||||
const typoPreset = TYPOGRAPHY_PRESETS[config.typography.preset as keyof typeof TYPOGRAPHY_PRESETS];
|
// Inject typography CSS variables
|
||||||
if (typoPreset) {
|
const typoPreset = TYPOGRAPHY_PRESETS[config.typography.preset as keyof typeof TYPOGRAPHY_PRESETS];
|
||||||
root.style.setProperty('--font-heading', typoPreset.heading);
|
if (typoPreset) {
|
||||||
root.style.setProperty('--font-body', typoPreset.body);
|
root.style.setProperty('--font-heading', typoPreset.heading);
|
||||||
root.style.setProperty('--font-weight-heading', typoPreset.headingWeight.toString());
|
root.style.setProperty('--font-body', typoPreset.body);
|
||||||
root.style.setProperty('--font-weight-body', typoPreset.bodyWeight.toString());
|
root.style.setProperty('--font-weight-heading', typoPreset.headingWeight.toString());
|
||||||
}
|
root.style.setProperty('--font-weight-body', typoPreset.bodyWeight.toString());
|
||||||
|
}
|
||||||
// Apply font scale
|
|
||||||
if (config.typography.scale) {
|
// Apply font scale
|
||||||
root.style.setProperty('--font-scale', config.typography.scale.toString());
|
if (config.typography.scale) {
|
||||||
}
|
root.style.setProperty('--font-scale', config.typography.scale.toString());
|
||||||
|
}
|
||||||
// We're using self-hosted fonts now, no need to load from Google
|
|
||||||
// loadTypography(config.typography.preset, config.typography.customFonts);
|
// We're using self-hosted fonts now, no need to load from Google
|
||||||
|
// loadTypography(config.typography.preset, config.typography.customFonts);
|
||||||
// Add layout class to body
|
|
||||||
document.body.classList.remove('layout-classic', 'layout-modern', 'layout-boutique', 'layout-launch');
|
// Add layout class to body
|
||||||
document.body.classList.add(`layout-${config.layout}`);
|
document.body.classList.remove('layout-classic', 'layout-modern', 'layout-boutique', 'layout-launch');
|
||||||
|
document.body.classList.add(`layout-${config.layout}`);
|
||||||
// Add mode class to body
|
|
||||||
document.body.classList.remove('mode-disabled', 'mode-full', 'mode-checkout-only');
|
// Add mode class to body
|
||||||
document.body.classList.add(`mode-${config.mode}`);
|
document.body.classList.remove('mode-disabled', 'mode-full', 'mode-checkout-only');
|
||||||
|
document.body.classList.add(`mode-${config.mode}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
handleLocationChange();
|
||||||
}, [config]);
|
}, [config]);
|
||||||
|
|
||||||
const contextValue: ThemeContextValue = {
|
const contextValue: ThemeContextValue = {
|
||||||
config,
|
config,
|
||||||
isFullSPA: config.mode === 'full',
|
isFullSPA: config.mode === 'full',
|
||||||
@@ -226,7 +178,7 @@ export function ThemeProvider({
|
|||||||
isLaunchLayout: config.layout === 'launch',
|
isLaunchLayout: config.layout === 'launch',
|
||||||
loading,
|
loading,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ThemeContext.Provider value={contextValue}>
|
<ThemeContext.Provider value={contextValue}>
|
||||||
{children}
|
{children}
|
||||||
@@ -245,29 +197,3 @@ export function useTheme() {
|
|||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Hook to check if we're in a specific layout
|
|
||||||
*/
|
|
||||||
export function useLayout() {
|
|
||||||
const { config } = useTheme();
|
|
||||||
return {
|
|
||||||
isClassic: config.layout === 'classic',
|
|
||||||
isModern: config.layout === 'modern',
|
|
||||||
isBoutique: config.layout === 'boutique',
|
|
||||||
isLaunch: config.layout === 'launch',
|
|
||||||
layout: config.layout,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hook to check current mode
|
|
||||||
*/
|
|
||||||
export function useMode() {
|
|
||||||
const { config, isFullSPA, isCheckoutOnly } = useTheme();
|
|
||||||
return {
|
|
||||||
isFullSPA,
|
|
||||||
isCheckoutOnly,
|
|
||||||
isDisabled: config.mode === 'disabled',
|
|
||||||
mode: config.mode,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -66,7 +66,8 @@ export function useAddToCartFromUrl() {
|
|||||||
// Remove from processed set on error so it can be retried
|
// Remove from processed set on error so it can be retried
|
||||||
processedRef.current.delete(requestKey);
|
processedRef.current.delete(requestKey);
|
||||||
});
|
});
|
||||||
}, [location.hash, navigate, setCart]); // Include all dependencies
|
|
||||||
|
}, [location.hash, location.pathname, navigate, setCart]); // Include all dependencies
|
||||||
}
|
}
|
||||||
|
|
||||||
async function addToCart(
|
async function addToCart(
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useState, useEffect, useCallback, useMemo } from 'react';
|
import { useState, useCallback, useMemo } from 'react';
|
||||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { api } from '@/lib/api/client';
|
import { api } from '@/lib/api/client';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
@@ -21,27 +21,25 @@ const GUEST_WISHLIST_KEY = 'woonoow_guest_wishlist';
|
|||||||
|
|
||||||
export function useWishlist() {
|
export function useWishlist() {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const [guestIds, setGuestIds] = useState<Set<number>>(new Set());
|
const [guestIds, setGuestIds] = useState<Set<number>>(() => {
|
||||||
|
if (typeof window !== 'undefined' && !(window as any).woonoowCustomer?.user?.isLoggedIn) {
|
||||||
// Check if wishlist is enabled (default true if not explicitly set to false)
|
|
||||||
const settings = (window as any).woonoowCustomer?.settings;
|
|
||||||
const isEnabled = settings?.wishlist_enabled !== false;
|
|
||||||
const isLoggedIn = (window as any).woonoowCustomer?.user?.isLoggedIn;
|
|
||||||
|
|
||||||
// Load guest wishlist on mount
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isLoggedIn) {
|
|
||||||
try {
|
try {
|
||||||
const stored = localStorage.getItem(GUEST_WISHLIST_KEY);
|
const stored = localStorage.getItem(GUEST_WISHLIST_KEY);
|
||||||
if (stored) {
|
if (stored) {
|
||||||
const ids = JSON.parse(stored) as number[];
|
const ids = JSON.parse(stored) as number[];
|
||||||
setGuestIds(new Set(ids));
|
return new Set(ids);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to load guest wishlist:', error);
|
console.error('Failed to load guest wishlist:', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [isLoggedIn]);
|
return new Set<number>();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check if wishlist is enabled (default true if not explicitly set to false)
|
||||||
|
const settings = (window as any).woonoowCustomer?.settings;
|
||||||
|
const isEnabled = settings?.wishlist_enabled !== false;
|
||||||
|
const isLoggedIn = (window as any).woonoowCustomer?.user?.isLoggedIn;
|
||||||
|
|
||||||
// Save guest wishlist helper
|
// Save guest wishlist helper
|
||||||
const saveGuestWishlist = useCallback((ids: Set<number>) => {
|
const saveGuestWishlist = useCallback((ids: Set<number>) => {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import React, { ReactNode, useState } from 'react';
|
import React, { ReactNode, useState } from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { Search, ShoppingCart, User, Menu, X, Heart } from 'lucide-react';
|
import { Search, ShoppingCart, User, Menu, X, Heart } from 'lucide-react';
|
||||||
import { useLayout } from '../contexts/ThemeContext';
|
|
||||||
import { useCartStore } from '../lib/cart/store';
|
import { useCartStore } from '../lib/cart/store';
|
||||||
import { useHeaderSettings, useFooterSettings, useMenuSettings } from '../hooks/useAppearanceSettings';
|
import { useHeaderSettings, useFooterSettings, useMenuSettings } from '../hooks/useAppearanceSettings';
|
||||||
import { SearchModal } from '../components/SearchModal';
|
import { SearchModal } from '../components/SearchModal';
|
||||||
@@ -718,6 +717,10 @@ function LaunchLayout({ children }: BaseLayoutProps) {
|
|||||||
window.location.pathname.includes('/my-account') ||
|
window.location.pathname.includes('/my-account') ||
|
||||||
window.location.pathname.includes('/order-received');
|
window.location.pathname.includes('/order-received');
|
||||||
|
|
||||||
|
const storeName = (window as any).woonoowCustomer?.storeName || (window as any).woonoowCustomer?.siteTitle || 'Store Title';
|
||||||
|
const storeLogo = (window as any).woonoowCustomer?.storeLogo;
|
||||||
|
const headerSettings = useHeaderSettings();
|
||||||
|
|
||||||
if (!isCheckoutFlow) {
|
if (!isCheckoutFlow) {
|
||||||
// For non-checkout pages, use minimal layout
|
// For non-checkout pages, use minimal layout
|
||||||
return (
|
return (
|
||||||
@@ -728,10 +731,6 @@ function LaunchLayout({ children }: BaseLayoutProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// For checkout flow: minimal header, no footer
|
// For checkout flow: minimal header, no footer
|
||||||
const storeLogo = (window as any).woonoowCustomer?.storeLogo;
|
|
||||||
const storeName = (window as any).woonoowCustomer?.storeName || (window as any).woonoowCustomer?.siteTitle || 'Store Title';
|
|
||||||
const headerSettings = useHeaderSettings();
|
|
||||||
|
|
||||||
const heightClass = headerSettings.height === 'compact' ? 'h-12' : headerSettings.height === 'tall' ? 'h-20' : 'h-16';
|
const heightClass = headerSettings.height === 'compact' ? 'h-12' : headerSettings.height === 'tall' ? 'h-20' : 'h-16';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -5,14 +5,12 @@
|
|||||||
|
|
||||||
// Get API base URL from WordPress
|
// Get API base URL from WordPress
|
||||||
const getApiBase = (): string => {
|
const getApiBase = (): string => {
|
||||||
// @ts-ignore - WordPress global
|
return (window as any).woonoowCustomer?.apiUrl || '/wp-json/woonoow/v1';
|
||||||
return window.woonoowCustomer?.apiUrl || '/wp-json/woonoow/v1';
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get nonce for authentication
|
// Get nonce for authentication
|
||||||
const getNonce = (): string => {
|
const getNonce = (): string => {
|
||||||
// @ts-ignore - WordPress global
|
return (window as any).woonoowCustomer?.nonce || '';
|
||||||
return window.woonoowCustomer?.nonce || '';
|
|
||||||
};
|
};
|
||||||
|
|
||||||
interface RequestOptions {
|
interface RequestOptions {
|
||||||
@@ -32,7 +30,7 @@ class ApiClient {
|
|||||||
const { method = 'GET', body, headers = {} } = options;
|
const { method = 'GET', body, headers = {} } = options;
|
||||||
|
|
||||||
const url = `${this.baseUrl}${endpoint}`;
|
const url = `${this.baseUrl}${endpoint}`;
|
||||||
|
|
||||||
const config: RequestInit = {
|
const config: RequestInit = {
|
||||||
method,
|
method,
|
||||||
headers: {
|
headers: {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export function getSectionBackground(styles?: Record<string, any>): SectionStyle
|
|||||||
}
|
}
|
||||||
|
|
||||||
const bgType = styles.backgroundType || 'solid';
|
const bgType = styles.backgroundType || 'solid';
|
||||||
let style: React.CSSProperties = {};
|
const style: React.CSSProperties = {};
|
||||||
let hasOverlay = false;
|
let hasOverlay = false;
|
||||||
let overlayOpacity = 0;
|
let overlayOpacity = 0;
|
||||||
let backgroundImage: string | undefined;
|
let backgroundImage: string | undefined;
|
||||||
|
|||||||
@@ -291,13 +291,6 @@ export default function Addresses() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check if a field should be wide (full width)
|
|
||||||
const isFieldWide = (field: CheckoutField): boolean => {
|
|
||||||
const fieldName = field.key.replace(/^billing_/, '');
|
|
||||||
return ['address_1', 'address_2', 'email'].includes(fieldName) ||
|
|
||||||
field.class?.includes('form-row-wide') || false;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center py-12">
|
<div className="flex items-center justify-center py-12">
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { Download, Loader2, FileText, ExternalLink } from 'lucide-react';
|
import { Download, Loader2, FileText } from 'lucide-react';
|
||||||
import { Button } from '@/components/ui/button';
|
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 SEOHead from '@/components/SEOHead';
|
import SEOHead from '@/components/SEOHead';
|
||||||
|
|
||||||
interface DownloadItem {
|
interface DownloadItem {
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ export default function OrderDetails() {
|
|||||||
if (orderId) {
|
if (orderId) {
|
||||||
loadOrder();
|
loadOrder();
|
||||||
}
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [orderId]);
|
}, [orderId]);
|
||||||
|
|
||||||
const loadOrder = async () => {
|
const loadOrder = async () => {
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ export default function Orders() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadOrders();
|
loadOrders();
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [page]);
|
}, [page]);
|
||||||
|
|
||||||
const loadOrders = async () => {
|
const loadOrders = async () => {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import React, { useState, useEffect } from 'react';
|
|||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { Heart, ShoppingCart, Trash2, X } from 'lucide-react';
|
import { Heart, ShoppingCart, Trash2, X } from 'lucide-react';
|
||||||
import { api } from '@/lib/api/client';
|
import { api } from '@/lib/api/client';
|
||||||
import { useCartStore } from '@/lib/cart/store';
|
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { formatPrice } from '@/lib/currency';
|
import { formatPrice } from '@/lib/currency';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
@@ -26,15 +25,20 @@ interface WishlistItem {
|
|||||||
|
|
||||||
export default function Wishlist() {
|
export default function Wishlist() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { addItem } = useCartStore();
|
|
||||||
const [items, setItems] = useState<WishlistItem[]>([]);
|
const [items, setItems] = useState<WishlistItem[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const { isEnabled, isLoading: modulesLoading } = useModules();
|
const { isEnabled, isLoading: modulesLoading } = useModules();
|
||||||
const { settings: wishlistSettings } = useModuleSettings('wishlist');
|
const { settings: wishlistSettings } = useModuleSettings('wishlist');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isEnabled('wishlist')) {
|
||||||
|
loadWishlist();
|
||||||
|
}
|
||||||
|
}, [isEnabled]);
|
||||||
|
|
||||||
if (modulesLoading) {
|
if (modulesLoading) {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen flex items-center justify-center">
|
<div className="flex items-center justify-center min-h-screen">
|
||||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-gray-900"></div>
|
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-gray-900"></div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -57,10 +61,6 @@ export default function Wishlist() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
loadWishlist();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const loadWishlist = async () => {
|
const loadWishlist = async () => {
|
||||||
try {
|
try {
|
||||||
const data = await api.get<WishlistItem[]>('/account/wishlist');
|
const data = await api.get<WishlistItem[]>('/account/wishlist');
|
||||||
@@ -91,7 +91,7 @@ export default function Wishlist() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await api.post('/cart/add', {
|
await api.post('/cart/add', {
|
||||||
product_id: item.product_id,
|
product_id: item.product_id,
|
||||||
quantity: 1,
|
quantity: 1,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ export function AccountLayout({ children }: AccountLayoutProps) {
|
|||||||
// Full page reload to clear cookies and refresh state
|
// Full page reload to clear cookies and refresh state
|
||||||
const basePath = (window as any).woonoowCustomer?.basePath || '/store';
|
const basePath = (window as any).woonoowCustomer?.basePath || '/store';
|
||||||
window.location.href = window.location.origin + basePath + '/';
|
window.location.href = window.location.origin + basePath + '/';
|
||||||
} catch (error) {
|
} catch {
|
||||||
// Even on error, try to redirect and let server handle session
|
// Even on error, try to redirect and let server handle session
|
||||||
const basePath = (window as any).woonoowCustomer?.basePath || '/store';
|
const basePath = (window as any).woonoowCustomer?.basePath || '/store';
|
||||||
window.location.href = window.location.origin + basePath + '/';
|
window.location.href = window.location.origin + basePath + '/';
|
||||||
@@ -99,7 +99,7 @@ export function AccountLayout({ children }: AccountLayoutProps) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Logout Button with AlertDialog
|
// Logout Button with AlertDialog
|
||||||
const LogoutButton = () => (
|
const renderLogoutButton = () => (
|
||||||
<AlertDialog>
|
<AlertDialog>
|
||||||
<AlertDialogTrigger asChild>
|
<AlertDialogTrigger asChild>
|
||||||
<button
|
<button
|
||||||
@@ -131,7 +131,7 @@ export function AccountLayout({ children }: AccountLayoutProps) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Sidebar Navigation
|
// Sidebar Navigation
|
||||||
const SidebarNav = () => (
|
const renderSidebarNav = () => (
|
||||||
<aside className="bg-white rounded-lg border p-4">
|
<aside className="bg-white rounded-lg border p-4">
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
<div className="flex items-center gap-3 pb-4 border-b">
|
<div className="flex items-center gap-3 pb-4 border-b">
|
||||||
@@ -171,13 +171,13 @@ export function AccountLayout({ children }: AccountLayoutProps) {
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
||||||
<LogoutButton />
|
{renderLogoutButton()}
|
||||||
</nav>
|
</nav>
|
||||||
</aside>
|
</aside>
|
||||||
);
|
);
|
||||||
|
|
||||||
// Tab Navigation (Mobile)
|
// Tab Navigation (Mobile)
|
||||||
const TabNav = () => (
|
const renderTabNav = () => (
|
||||||
<div className="bg-white rounded-lg border mb-6 lg:hidden">
|
<div className="bg-white rounded-lg border mb-6 lg:hidden">
|
||||||
<nav className="flex overflow-x-auto">
|
<nav className="flex overflow-x-auto">
|
||||||
{menuItems.map((item) => {
|
{menuItems.map((item) => {
|
||||||
@@ -201,23 +201,21 @@ export function AccountLayout({ children }: AccountLayoutProps) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Responsive layout: Tabs on mobile, Sidebar on desktop
|
// Responsive layout: Tabs on mobile, Sidebar on desktop
|
||||||
return (
|
<div className="py-8">
|
||||||
<div className="py-8">
|
{/* Mobile: Tab Navigation */}
|
||||||
{/* Mobile: Tab Navigation */}
|
{renderTabNav()}
|
||||||
<TabNav />
|
|
||||||
|
|
||||||
{/* Desktop: Sidebar + Content */}
|
{/* Desktop: Sidebar + Content */}
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-4 gap-6">
|
<div className="grid grid-cols-1 lg:grid-cols-4 gap-6">
|
||||||
<div className="hidden lg:block lg:col-span-1">
|
<div className="hidden lg:block lg:col-span-1">
|
||||||
<SidebarNav />
|
{renderSidebarNav()}
|
||||||
</div>
|
</div>
|
||||||
<div className="lg:col-span-3">
|
<div className="lg:col-span-3">
|
||||||
<div className="bg-white rounded-lg border p-6">
|
<div className="bg-white rounded-lg border p-6">
|
||||||
{children}
|
{children}
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { Link, useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { useCartStore, type CartItem } from '@/lib/cart/store';
|
import { useCartStore, type CartItem } from '@/lib/cart/store';
|
||||||
import { useCartSettings } from '@/hooks/useAppearanceSettings';
|
import { useCartSettings } from '@/hooks/useAppearanceSettings';
|
||||||
import { updateCartItemQuantity, removeCartItem, clearCartAPI, fetchCart, applyCoupon, removeCoupon } from '@/lib/cart/api';
|
import { updateCartItemQuantity, removeCartItem, clearCartAPI, fetchCart, applyCoupon, removeCoupon } from '@/lib/cart/api';
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { DynamicCheckoutField, type CheckoutField } from '@/components/DynamicCh
|
|||||||
import Container from '@/components/Layout/Container';
|
import Container from '@/components/Layout/Container';
|
||||||
import SEOHead from '@/components/SEOHead';
|
import SEOHead from '@/components/SEOHead';
|
||||||
import { formatPrice } from '@/lib/currency';
|
import { formatPrice } from '@/lib/currency';
|
||||||
import { ArrowLeft, ShoppingBag, MapPin, Check, Edit2, Loader2, X, Tag } from 'lucide-react';
|
import { ArrowLeft, ShoppingBag, MapPin, Edit2, Loader2, X, Tag } from 'lucide-react';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { apiClient } from '@/lib/api/client';
|
import { apiClient } from '@/lib/api/client';
|
||||||
import { api } from '@/lib/api/client';
|
import { api } from '@/lib/api/client';
|
||||||
@@ -104,7 +104,6 @@ export default function Checkout() {
|
|||||||
// Countries and states data
|
// Countries and states data
|
||||||
const [countries, setCountries] = useState<{ code: string; name: string }[]>([]);
|
const [countries, setCountries] = useState<{ code: string; name: string }[]>([]);
|
||||||
const [states, setStates] = useState<Record<string, Record<string, string>>>({});
|
const [states, setStates] = useState<Record<string, Record<string, string>>>({});
|
||||||
const [defaultCountry, setDefaultCountry] = useState('');
|
|
||||||
|
|
||||||
// Load countries and states
|
// Load countries and states
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -117,7 +116,6 @@ export default function Checkout() {
|
|||||||
}>('/countries');
|
}>('/countries');
|
||||||
setCountries(data.countries || []);
|
setCountries(data.countries || []);
|
||||||
setStates(data.states || {});
|
setStates(data.states || {});
|
||||||
setDefaultCountry(data.default_country || '');
|
|
||||||
|
|
||||||
// Set default country if not already set
|
// Set default country if not already set
|
||||||
if (!billingData.country && data.default_country) {
|
if (!billingData.country && data.default_country) {
|
||||||
@@ -131,6 +129,7 @@ export default function Checkout() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
loadCountries();
|
loadCountries();
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Country/state options for SearchableSelect
|
// Country/state options for SearchableSelect
|
||||||
@@ -146,7 +145,8 @@ export default function Checkout() {
|
|||||||
setBillingData(prev => ({ ...prev, state: '' }));
|
setBillingData(prev => ({ ...prev, state: '' }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [billingData.country, states]);
|
|
||||||
|
}, [billingData.country, billingData.state, states]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (shippingData.country && shippingData.state) {
|
if (shippingData.country && shippingData.state) {
|
||||||
@@ -155,7 +155,8 @@ export default function Checkout() {
|
|||||||
setShippingData(prev => ({ ...prev, state: '' }));
|
setShippingData(prev => ({ ...prev, state: '' }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [shippingData.country, states]);
|
|
||||||
|
}, [shippingData.country, shippingData.state, states]);
|
||||||
|
|
||||||
// Dynamic checkout fields from API
|
// Dynamic checkout fields from API
|
||||||
const [checkoutFields, setCheckoutFields] = useState<CheckoutField[]>([]);
|
const [checkoutFields, setCheckoutFields] = useState<CheckoutField[]>([]);
|
||||||
@@ -298,9 +299,6 @@ export default function Checkout() {
|
|||||||
quantity: item.quantity,
|
quantity: item.quantity,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const destinationId = shipToDifferentAddress
|
|
||||||
? customFieldData['shipping_destination_id']
|
|
||||||
: customFieldData['billing_destination_id'];
|
|
||||||
|
|
||||||
const response = await api.post<{ ok: boolean; rates: ShippingRate[]; zone_name?: string }>('/checkout/shipping-rates', {
|
const response = await api.post<{ ok: boolean; rates: ShippingRate[]; zone_name?: string }>('/checkout/shipping-rates', {
|
||||||
shipping: {
|
shipping: {
|
||||||
@@ -308,7 +306,7 @@ export default function Checkout() {
|
|||||||
state: addressData.state,
|
state: addressData.state,
|
||||||
city: addressData.city,
|
city: addressData.city,
|
||||||
postcode: addressData.postcode,
|
postcode: addressData.postcode,
|
||||||
destination_id: destinationId || undefined,
|
destination_id: undefined,
|
||||||
},
|
},
|
||||||
items,
|
items,
|
||||||
});
|
});
|
||||||
@@ -332,9 +330,6 @@ export default function Checkout() {
|
|||||||
// Trigger shipping rate fetch when address or destination changes
|
// Trigger shipping rate fetch when address or destination changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const addressData = shipToDifferentAddress ? shippingData : billingData;
|
const addressData = shipToDifferentAddress ? shippingData : billingData;
|
||||||
const destinationId = shipToDifferentAddress
|
|
||||||
? customFieldData['shipping_destination_id']
|
|
||||||
: customFieldData['billing_destination_id'];
|
|
||||||
|
|
||||||
// Debounce the fetch
|
// Debounce the fetch
|
||||||
const timeoutId = setTimeout(() => {
|
const timeoutId = setTimeout(() => {
|
||||||
@@ -344,12 +339,12 @@ export default function Checkout() {
|
|||||||
}, 500);
|
}, 500);
|
||||||
|
|
||||||
return () => clearTimeout(timeoutId);
|
return () => clearTimeout(timeoutId);
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [
|
}, [
|
||||||
billingData.country, billingData.state, billingData.city, billingData.postcode,
|
billingData.country, billingData.state, billingData.city, billingData.postcode,
|
||||||
shippingData.country, shippingData.state, shippingData.city, shippingData.postcode,
|
shippingData.country, shippingData.state, shippingData.city, shippingData.postcode,
|
||||||
shipToDifferentAddress,
|
shipToDifferentAddress,
|
||||||
customFieldData['billing_destination_id'],
|
customFieldData,
|
||||||
customFieldData['shipping_destination_id'],
|
|
||||||
cart.items.length,
|
cart.items.length,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -393,6 +388,7 @@ export default function Checkout() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
loadAddresses();
|
loadAddresses();
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [user, isVirtualOnly]);
|
}, [user, isVirtualOnly]);
|
||||||
|
|
||||||
// Helper functions to fill forms from saved addresses
|
// Helper functions to fill forms from saved addresses
|
||||||
@@ -461,7 +457,8 @@ export default function Checkout() {
|
|||||||
country: user.shipping.country || '',
|
country: user.shipping.country || '',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [user]);
|
|
||||||
|
}, [user, savedAddresses.length]);
|
||||||
|
|
||||||
const handleApplyCoupon = async () => {
|
const handleApplyCoupon = async () => {
|
||||||
if (!couponCode.trim()) return;
|
if (!couponCode.trim()) return;
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { useParams, useNavigate } from 'react-router-dom';
|
|||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { api } from '@/lib/api/client';
|
import { api } from '@/lib/api/client';
|
||||||
import { Helmet } from 'react-helmet-async';
|
import { Helmet } from 'react-helmet-async';
|
||||||
import { cn } from '@/lib/utils';
|
|
||||||
|
|
||||||
// Section Components
|
// Section Components
|
||||||
import { HeroSection } from './sections/HeroSection';
|
import { HeroSection } from './sections/HeroSection';
|
||||||
@@ -23,6 +22,7 @@ interface SectionProp {
|
|||||||
interface SectionStyles {
|
interface SectionStyles {
|
||||||
backgroundColor?: string;
|
backgroundColor?: string;
|
||||||
backgroundImage?: string;
|
backgroundImage?: string;
|
||||||
|
backgroundType?: 'color' | 'image' | 'gradient';
|
||||||
backgroundOverlay?: number;
|
backgroundOverlay?: number;
|
||||||
paddingTop?: string;
|
paddingTop?: string;
|
||||||
paddingBottom?: string;
|
paddingBottom?: string;
|
||||||
@@ -164,6 +164,7 @@ export function DynamicPageRenderer({ slug: propSlug }: DynamicPageRendererProps
|
|||||||
// Handle 404
|
// Handle 404
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (error) {
|
if (error) {
|
||||||
|
// eslint-disable-next-line react-hooks/set-state-in-effect
|
||||||
setNotFound(true);
|
setNotFound(true);
|
||||||
}
|
}
|
||||||
}, [error]);
|
}, [error]);
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ export function ContactFormSection({
|
|||||||
});
|
});
|
||||||
window.location.href = finalUrl;
|
window.location.href = finalUrl;
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch {
|
||||||
setError('Failed to submit form. Please try again.');
|
setError('Failed to submit form. Please try again.');
|
||||||
} finally {
|
} finally {
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
|
|||||||
@@ -28,12 +28,6 @@ const COLOR_SCHEMES: Record<string, { bg: string; text: string }> = {
|
|||||||
muted: { bg: 'bg-gray-50', text: 'text-gray-700' },
|
muted: { bg: 'bg-gray-50', text: 'text-gray-700' },
|
||||||
};
|
};
|
||||||
|
|
||||||
const WIDTH_CLASSES: Record<string, string> = {
|
|
||||||
default: 'max-w-screen-xl mx-auto',
|
|
||||||
contained: 'max-w-screen-md mx-auto',
|
|
||||||
full: 'w-full',
|
|
||||||
};
|
|
||||||
|
|
||||||
const fontSizeToCSS = (className?: string) => {
|
const fontSizeToCSS = (className?: string) => {
|
||||||
switch (className) {
|
switch (className) {
|
||||||
case 'text-xs': return '0.75rem';
|
case 'text-xs': return '0.75rem';
|
||||||
@@ -164,10 +158,10 @@ const generateScopedStyles = (sectionId: string, elementStyles: Record<string, a
|
|||||||
return styles.join('\n');
|
return styles.join('\n');
|
||||||
};
|
};
|
||||||
|
|
||||||
export function ContentSection({ section, content: propContent, cta_text: propCtaText, cta_url: propCtaUrl, outerPadding = false }: ContentSectionProps & { outerPadding?: boolean }) {
|
export function ContentSection({ section, content: propContent, cta_text: propCtaText, cta_url: propCtaUrl }: ContentSectionProps & { outerPadding?: boolean }) {
|
||||||
const scheme = COLOR_SCHEMES[section.colorScheme || 'default'] ?? COLOR_SCHEMES['default'];
|
const scheme = COLOR_SCHEMES[section.colorScheme || 'default'] ?? COLOR_SCHEMES['default'];
|
||||||
// Default to 'default' width if not specified
|
// Default to 'default' width if not specified
|
||||||
const layout = section.layoutVariant || 'default';
|
const _layout = section.layoutVariant || 'default';
|
||||||
|
|
||||||
const heightPreset = section.styles?.heightPreset || 'default';
|
const heightPreset = section.styles?.heightPreset || 'default';
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ interface HeroSectionProps {
|
|||||||
export function HeroSection({
|
export function HeroSection({
|
||||||
id,
|
id,
|
||||||
layout = 'default',
|
layout = 'default',
|
||||||
colorScheme = 'default',
|
|
||||||
title,
|
title,
|
||||||
subtitle,
|
subtitle,
|
||||||
image,
|
image,
|
||||||
@@ -38,7 +37,6 @@ export function HeroSection({
|
|||||||
const isImageRight = layout === 'hero-right-image' || layout === 'image-right';
|
const isImageRight = layout === 'hero-right-image' || layout === 'image-right';
|
||||||
const isCentered = layout === 'centered' || layout === 'default';
|
const isCentered = layout === 'centered' || layout === 'default';
|
||||||
|
|
||||||
const hasCustomBackground = !!styles?.backgroundColor || !!styles?.backgroundImage || styles?.backgroundType === 'gradient';
|
|
||||||
const sectionBg = getSectionBackground(styles);
|
const sectionBg = getSectionBackground(styles);
|
||||||
|
|
||||||
// Helper to get text styles (including font family)
|
// Helper to get text styles (including font family)
|
||||||
@@ -73,7 +71,7 @@ export function HeroSection({
|
|||||||
|
|
||||||
|
|
||||||
// Helper to get background style for dynamic schemes
|
// Helper to get background style for dynamic schemes
|
||||||
const getBackgroundStyle = (): React.CSSProperties | undefined => {
|
/* const getBackgroundStyle = (): React.CSSProperties | undefined => {
|
||||||
// If user set custom bg via Design tab, use that
|
// If user set custom bg via Design tab, use that
|
||||||
if (hasCustomBackground) return sectionBg.style;
|
if (hasCustomBackground) return sectionBg.style;
|
||||||
if (colorScheme === 'primary') {
|
if (colorScheme === 'primary') {
|
||||||
@@ -83,9 +81,7 @@ export function HeroSection({
|
|||||||
return { backgroundColor: 'var(--wn-secondary, #6b7280)' };
|
return { backgroundColor: 'var(--wn-secondary, #6b7280)' };
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
};
|
}; */
|
||||||
|
|
||||||
const isDynamicScheme = ['primary', 'secondary'].includes(colorScheme) && !hasCustomBackground;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section
|
<section
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ export function ImageTextSection({
|
|||||||
elementStyles,
|
elementStyles,
|
||||||
styles,
|
styles,
|
||||||
}: ImageTextSectionProps & { styles?: Record<string, any>, cta_text?: string, cta_url?: string }) {
|
}: ImageTextSectionProps & { styles?: Record<string, any>, cta_text?: string, cta_url?: string }) {
|
||||||
const isImageLeft = layout === 'image-left' || layout === 'left';
|
|
||||||
const isImageRight = layout === 'image-right' || layout === 'right';
|
const isImageRight = layout === 'image-right' || layout === 'right';
|
||||||
|
|
||||||
// Helper to get text styles (including font family)
|
// Helper to get text styles (including font family)
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ export default function Login() {
|
|||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
body: JSON.stringify({ product_id: productId }),
|
body: JSON.stringify({ product_id: productId }),
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch {
|
||||||
// Skip if product already in wishlist or other error
|
// Skip if product already in wishlist or other error
|
||||||
console.debug('Wishlist merge skipped for product:', productId);
|
console.debug('Wishlist merge skipped for product:', productId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { useParams, useNavigate, useSearchParams } from 'react-router-dom';
|
import { useParams, useSearchParams } from 'react-router-dom';
|
||||||
import { api } from '@/lib/api/client';
|
import { api } from '@/lib/api/client';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import SubscriptionTimeline from '../../components/SubscriptionTimeline';
|
import SubscriptionTimeline from '../../components/SubscriptionTimeline';
|
||||||
@@ -52,7 +52,7 @@ const OrderPay: React.FC = () => {
|
|||||||
const { orderId } = useParams<{ orderId: string }>();
|
const { orderId } = useParams<{ orderId: string }>();
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const orderKey = searchParams.get('key');
|
const orderKey = searchParams.get('key');
|
||||||
const navigate = useNavigate();
|
// const navigate = useNavigate();
|
||||||
|
|
||||||
const [order, setOrder] = useState<OrderDetailsResponse | null>(null);
|
const [order, setOrder] = useState<OrderDetailsResponse | null>(null);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
@@ -63,6 +63,7 @@ const OrderPay: React.FC = () => {
|
|||||||
if (orderId) {
|
if (orderId) {
|
||||||
fetchOrder();
|
fetchOrder();
|
||||||
}
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [orderId]);
|
}, [orderId]);
|
||||||
|
|
||||||
const fetchOrder = async () => {
|
const fetchOrder = async () => {
|
||||||
@@ -123,7 +124,7 @@ const OrderPay: React.FC = () => {
|
|||||||
minimumFractionDigits: 0,
|
minimumFractionDigits: 0,
|
||||||
maximumFractionDigits: 0,
|
maximumFractionDigits: 0,
|
||||||
}).format(amount);
|
}).format(amount);
|
||||||
} catch (e) {
|
} catch {
|
||||||
// Fallback
|
// Fallback
|
||||||
return `${currency} ${amount.toFixed(0)}`;
|
return `${currency} ${amount.toFixed(0)}`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export default function Product() {
|
|||||||
const [selectedAttributes, setSelectedAttributes] = useState<Record<string, string>>({});
|
const [selectedAttributes, setSelectedAttributes] = useState<Record<string, string>>({});
|
||||||
const thumbnailsRef = useRef<HTMLDivElement>(null);
|
const thumbnailsRef = useRef<HTMLDivElement>(null);
|
||||||
const { addItem } = useCartStore();
|
const { addItem } = useCartStore();
|
||||||
const { isEnabled: wishlistEnabled, isInWishlist, toggleWishlist, isLoggedIn } = useWishlist();
|
const { isEnabled: wishlistEnabled, isInWishlist, toggleWishlist } = useWishlist();
|
||||||
const { isEnabled: isModuleEnabled } = useModules();
|
const { isEnabled: isModuleEnabled } = useModules();
|
||||||
|
|
||||||
// Apply white background to <main> in flat mode so the full viewport width is white
|
// Apply white background to <main> in flat mode so the full viewport width is white
|
||||||
@@ -86,6 +86,7 @@ export default function Product() {
|
|||||||
if (product && !selectedImage) {
|
if (product && !selectedImage) {
|
||||||
setSelectedImage(product.image || product.images?.[0]);
|
setSelectedImage(product.image || product.images?.[0]);
|
||||||
}
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [product]);
|
}, [product]);
|
||||||
|
|
||||||
// AUTO-SELECT FIRST VARIATION (Issue #2 from report)
|
// AUTO-SELECT FIRST VARIATION (Issue #2 from report)
|
||||||
@@ -103,6 +104,7 @@ export default function Product() {
|
|||||||
setSelectedAttributes(initialAttributes);
|
setSelectedAttributes(initialAttributes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [product]);
|
}, [product]);
|
||||||
|
|
||||||
// Find matching variation when attributes change
|
// Find matching variation when attributes change
|
||||||
@@ -114,7 +116,6 @@ export default function Product() {
|
|||||||
(product.variations as any[]).forEach(v => {
|
(product.variations as any[]).forEach(v => {
|
||||||
if (!v.attributes) return;
|
if (!v.attributes) return;
|
||||||
|
|
||||||
let isMatch = true;
|
|
||||||
let score = 0;
|
let score = 0;
|
||||||
|
|
||||||
const attributesMatch = Object.entries(selectedAttributes).every(([attrName, attrValue]) => {
|
const attributesMatch = Object.entries(selectedAttributes).every(([attrName, attrValue]) => {
|
||||||
@@ -230,7 +231,7 @@ export default function Product() {
|
|||||||
|
|
||||||
// Construct variation params using keys from the matched variation
|
// Construct variation params using keys from the matched variation
|
||||||
// but filling in values from user selection (handles "Any" variations with empty values)
|
// but filling in values from user selection (handles "Any" variations with empty values)
|
||||||
let variation_params: Record<string, string> = {};
|
const variation_params: Record<string, string> = {};
|
||||||
if (product.type === 'variable' && selectedVariation?.attributes) {
|
if (product.type === 'variable' && selectedVariation?.attributes) {
|
||||||
// Get keys from the variation's attributes (these are the correct WooCommerce keys)
|
// Get keys from the variation's attributes (these are the correct WooCommerce keys)
|
||||||
Object.keys(selectedVariation.attributes).forEach(key => {
|
Object.keys(selectedVariation.attributes).forEach(key => {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { Link, useSearchParams, useNavigate } from 'react-router-dom';
|
import { 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 { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
@@ -8,9 +8,7 @@ import { Label } from '@/components/ui/label';
|
|||||||
import { KeyRound, ArrowLeft, Eye, EyeOff, CheckCircle, AlertCircle, Loader2 } from 'lucide-react';
|
import { KeyRound, ArrowLeft, Eye, EyeOff, CheckCircle, AlertCircle, Loader2 } from 'lucide-react';
|
||||||
|
|
||||||
export default function ResetPassword() {
|
export default function ResetPassword() {
|
||||||
const [searchParams] = useSearchParams();
|
const searchParams = new URLSearchParams(window.location.search);
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
const key = searchParams.get('key') || '';
|
const key = searchParams.get('key') || '';
|
||||||
const login = searchParams.get('login') || '';
|
const login = searchParams.get('login') || '';
|
||||||
|
|
||||||
@@ -53,7 +51,7 @@ export default function ResetPassword() {
|
|||||||
} else {
|
} else {
|
||||||
setError(data.message || 'This password reset link has expired or is invalid.');
|
setError(data.message || 'This password reset link has expired or is invalid.');
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch {
|
||||||
setError('Unable to validate reset link. Please try again later.');
|
setError('Unable to validate reset link. Please try again later.');
|
||||||
} finally {
|
} finally {
|
||||||
setIsValidating(false);
|
setIsValidating(false);
|
||||||
@@ -61,7 +59,8 @@ export default function ResetPassword() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
validateKey();
|
validateKey();
|
||||||
}, [key, login]);
|
|
||||||
|
}, [login]);
|
||||||
|
|
||||||
const handleSubmit = async (e: React.FormEvent) => {
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|||||||
@@ -8,15 +8,12 @@ import { Button } from '@/components/ui/button';
|
|||||||
import Container from '@/components/Layout/Container';
|
import Container from '@/components/Layout/Container';
|
||||||
import { ProductCard } from '@/components/ProductCard';
|
import { ProductCard } from '@/components/ProductCard';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { useTheme, useLayout } from '@/contexts/ThemeContext';
|
|
||||||
import { useShopSettings } from '@/hooks/useAppearanceSettings';
|
import { useShopSettings } from '@/hooks/useAppearanceSettings';
|
||||||
import SEOHead from '@/components/SEOHead';
|
import SEOHead from '@/components/SEOHead';
|
||||||
import type { ProductsResponse, ProductCategory, Product } from '@/types/product';
|
import type { ProductsResponse, ProductCategory } from '@/types/product';
|
||||||
|
|
||||||
export default function Shop() {
|
export default function Shop() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { config } = useTheme();
|
|
||||||
const { layout } = useLayout();
|
|
||||||
const { layout: shopLayout, elements } = useShopSettings();
|
const { layout: shopLayout, elements } = useShopSettings();
|
||||||
const [page, setPage] = useState(1);
|
const [page, setPage] = useState(1);
|
||||||
const [search, setSearch] = useState('');
|
const [search, setSearch] = useState('');
|
||||||
@@ -96,7 +93,7 @@ export default function Shop() {
|
|||||||
|
|
||||||
const handleAddToCart = async (product: any) => {
|
const handleAddToCart = async (product: any) => {
|
||||||
try {
|
try {
|
||||||
const response = await apiClient.post(apiClient.endpoints.cart.add, {
|
await apiClient.post(apiClient.endpoints.cart.add, {
|
||||||
product_id: product.id,
|
product_id: product.id,
|
||||||
quantity: 1,
|
quantity: 1,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -12,11 +12,11 @@ export default function ThankYou() {
|
|||||||
const { orderId } = useParams<{ orderId: string }>();
|
const { orderId } = useParams<{ orderId: string }>();
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const orderKey = searchParams.get('key');
|
const orderKey = searchParams.get('key');
|
||||||
const { template, headerVisibility, footerVisibility, backgroundColor, customMessage, elements, isLoading: settingsLoading } = useThankYouSettings();
|
const { template, backgroundColor, customMessage, elements, isLoading: settingsLoading } = useThankYouSettings();
|
||||||
const [order, setOrder] = useState<any>(null);
|
const [order, setOrder] = useState<any>(null);
|
||||||
const [relatedProducts, setRelatedProducts] = useState<any[]>([]);
|
const [relatedProducts, setRelatedProducts] = useState<any[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [, setError] = useState<string | null>(null);
|
||||||
const isLoggedIn = (window as any).woonoowCustomer?.user?.isLoggedIn;
|
const isLoggedIn = (window as any).woonoowCustomer?.user?.isLoggedIn;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user