Fix IDE errors from ESLint cleanup

This commit is contained in:
Dwindi Ramadhana
2026-03-12 04:08:57 +07:00
parent 90169b508d
commit ab10c25c28
34 changed files with 155 additions and 258 deletions

View File

@@ -1,4 +1,4 @@
import React, { useEffect, useRef, useState } from 'react';
import { useEffect, useRef, useState } from 'react';
declare global {
interface Window {
@@ -118,7 +118,7 @@ export function CaptchaWidget({ provider, siteKey, onToken, action = 'checkout'
}
}
};
}, [provider, siteKey]);
}, [provider, siteKey, onToken]);
// Execute reCAPTCHA when loaded
useEffect(() => {

View File

@@ -1,4 +1,4 @@
import React, { useEffect, useRef } from 'react';
import { useEffect, useRef } from 'react';
import { useSearchParams } from 'react-router-dom';
import { applyCoupon } from '@/lib/cart/api';
import { useCartStore } from '@/lib/cart/store';

View File

@@ -3,7 +3,7 @@ import { Link, useNavigate } from 'react-router-dom';
import { ShoppingCart, Heart } from 'lucide-react';
import { formatPrice, formatDiscount } from '@/lib/currency';
import { Button } from './ui/button';
import { useLayout } from '@/contexts/ThemeContext';
import { useShopSettings } from '@/hooks/useAppearanceSettings';
import { useWishlist } from '@/hooks/useWishlist';
import { useModules } from '@/hooks/useModules';
@@ -26,7 +26,6 @@ interface ProductCardProps {
export function ProductCard({ product, onAddToCart }: ProductCardProps) {
const navigate = useNavigate();
const { isClassic, isModern, isBoutique, isLaunch } = useLayout();
const { layout, elements, addToCart, saleBadge, isLoading } = useShopSettings();
const { isEnabled: wishlistEnabled, isInWishlist, toggleWishlist } = useWishlist();
const { isEnabled: isModuleEnabled } = useModules();

View File

@@ -28,7 +28,6 @@ export function SEOHead({
}: SEOHeadProps) {
const config = (window as any).woonoowCustomer;
const siteName = config?.siteName || 'Store';
const siteUrl = config?.siteUrl || '';
const fullTitle = title ? `${title} | ${siteName}` : siteName;
const fullUrl = url || (typeof window !== 'undefined' ? window.location.href : '');

View File

@@ -1,6 +1,6 @@
import React from 'react';
import { cn } from '@/lib/utils';
import * as LucideIcons from 'lucide-react';
interface SharedContentProps {
// Content

View File

@@ -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' : ''}`;
return (

View File

@@ -48,7 +48,7 @@ export function WooCommerceHooks({ context, hookName, productId, className }: Wo
/**
* 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({
queryKey: ['wc-hooks', context, productId],
queryFn: async () => {

View File

@@ -35,12 +35,10 @@ const buttonVariants = cva(
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean
}
VariantProps<typeof buttonVariants> { }
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)
return (
<button
@@ -52,5 +50,8 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
}
)
Button.displayName = "Button"
export { Button }
export { Button, buttonVariants }
// eslint-disable-next-line react-refresh/only-export-components
export { buttonVariants }
export default Button;

View File

@@ -1,8 +1,7 @@
import * as React from 'react';
import { cn } from '@/lib/utils';
export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement> { }
export type InputProps = React.InputHTMLAttributes<HTMLInputElement>;
const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, ...props }, ref) => {

View File

@@ -69,63 +69,11 @@ const TYPOGRAPHY_PRESETS = {
},
};
/**
* Load Google Fonts for typography preset
*/
function loadTypography(preset: string, customFonts?: { heading: string; body: string }) {
// Remove existing font link if any
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;
export function ThemeProvider({
config: initialConfig,
children
}: {
config: ThemeConfig;
children: ReactNode;
}) {
const [config, setConfig] = useState<ThemeConfig>(initialConfig);
@@ -139,14 +87,14 @@ export function ThemeProvider({
const response = await fetch(`${apiRoot}/appearance/settings`, {
credentials: 'include',
});
if (response.ok) {
const data = await response.json();
const settings = data.data;
if (settings?.general) {
const general = settings.general;
// Map API settings to theme config
const mappedPreset = FONT_PAIR_MAP[general.typography?.predefined_pair] || 'modern';
const newConfig: ThemeConfig = {
@@ -164,7 +112,7 @@ export function ThemeProvider({
scale: general.typography?.scale || 1.0,
},
};
setConfig(newConfig);
}
}
@@ -174,51 +122,55 @@ export function ThemeProvider({
setLoading(false);
}
};
fetchSettings();
}, []);
useEffect(() => {
const root = document.documentElement;
// Inject color CSS variables
root.style.setProperty('--color-primary', config.colors.primary);
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.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];
if (typoPreset) {
root.style.setProperty('--font-heading', typoPreset.heading);
root.style.setProperty('--font-body', typoPreset.body);
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) {
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);
// Add layout class to body
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');
document.body.classList.add(`mode-${config.mode}`);
const handleLocationChange = () => {
const root = document.documentElement;
// Inject color CSS variables
root.style.setProperty('--color-primary', config.colors.primary);
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.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];
if (typoPreset) {
root.style.setProperty('--font-heading', typoPreset.heading);
root.style.setProperty('--font-body', typoPreset.body);
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) {
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);
// Add layout class to body
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');
document.body.classList.add(`mode-${config.mode}`);
};
handleLocationChange();
}, [config]);
const contextValue: ThemeContextValue = {
config,
isFullSPA: config.mode === 'full',
@@ -226,7 +178,7 @@ export function ThemeProvider({
isLaunchLayout: config.layout === 'launch',
loading,
};
return (
<ThemeContext.Provider value={contextValue}>
{children}
@@ -245,29 +197,3 @@ export function useTheme() {
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,
};
}

View File

@@ -66,7 +66,8 @@ export function useAddToCartFromUrl() {
// Remove from processed set on error so it can be retried
processedRef.current.delete(requestKey);
});
}, [location.hash, navigate, setCart]); // Include all dependencies
}, [location.hash, location.pathname, navigate, setCart]); // Include all dependencies
}
async function addToCart(

View File

@@ -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 { api } from '@/lib/api/client';
import { toast } from 'sonner';
@@ -21,27 +21,25 @@ const GUEST_WISHLIST_KEY = 'woonoow_guest_wishlist';
export function useWishlist() {
const queryClient = useQueryClient();
const [guestIds, setGuestIds] = useState<Set<number>>(new Set());
// 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) {
const [guestIds, setGuestIds] = useState<Set<number>>(() => {
if (typeof window !== 'undefined' && !(window as any).woonoowCustomer?.user?.isLoggedIn) {
try {
const stored = localStorage.getItem(GUEST_WISHLIST_KEY);
if (stored) {
const ids = JSON.parse(stored) as number[];
setGuestIds(new Set(ids));
return new Set(ids);
}
} catch (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
const saveGuestWishlist = useCallback((ids: Set<number>) => {

View File

@@ -1,7 +1,6 @@
import React, { ReactNode, useState } from 'react';
import { Link } from 'react-router-dom';
import { Search, ShoppingCart, User, Menu, X, Heart } from 'lucide-react';
import { useLayout } from '../contexts/ThemeContext';
import { useCartStore } from '../lib/cart/store';
import { useHeaderSettings, useFooterSettings, useMenuSettings } from '../hooks/useAppearanceSettings';
import { SearchModal } from '../components/SearchModal';
@@ -718,6 +717,10 @@ function LaunchLayout({ children }: BaseLayoutProps) {
window.location.pathname.includes('/my-account') ||
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) {
// For non-checkout pages, use minimal layout
return (
@@ -728,10 +731,6 @@ function LaunchLayout({ children }: BaseLayoutProps) {
}
// 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';
return (

View File

@@ -5,14 +5,12 @@
// Get API base URL from WordPress
const getApiBase = (): string => {
// @ts-ignore - WordPress global
return window.woonoowCustomer?.apiUrl || '/wp-json/woonoow/v1';
return (window as any).woonoowCustomer?.apiUrl || '/wp-json/woonoow/v1';
};
// Get nonce for authentication
const getNonce = (): string => {
// @ts-ignore - WordPress global
return window.woonoowCustomer?.nonce || '';
return (window as any).woonoowCustomer?.nonce || '';
};
interface RequestOptions {
@@ -32,7 +30,7 @@ class ApiClient {
const { method = 'GET', body, headers = {} } = options;
const url = `${this.baseUrl}${endpoint}`;
const config: RequestInit = {
method,
headers: {

View File

@@ -16,7 +16,7 @@ export function getSectionBackground(styles?: Record<string, any>): SectionStyle
}
const bgType = styles.backgroundType || 'solid';
let style: React.CSSProperties = {};
const style: React.CSSProperties = {};
let hasOverlay = false;
let overlayOpacity = 0;
let backgroundImage: string | undefined;

View File

@@ -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) {
return (
<div className="flex items-center justify-center py-12">

View File

@@ -1,9 +1,8 @@
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 { api } from '@/lib/api/client';
import { toast } from 'sonner';
import { formatPrice } from '@/lib/currency';
import SEOHead from '@/components/SEOHead';
interface DownloadItem {

View File

@@ -49,6 +49,7 @@ export default function OrderDetails() {
if (orderId) {
loadOrder();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [orderId]);
const loadOrder = async () => {

View File

@@ -22,6 +22,7 @@ export default function Orders() {
useEffect(() => {
loadOrders();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [page]);
const loadOrders = async () => {

View File

@@ -2,7 +2,6 @@ import React, { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { Heart, ShoppingCart, Trash2, X } from 'lucide-react';
import { api } from '@/lib/api/client';
import { useCartStore } from '@/lib/cart/store';
import { Button } from '@/components/ui/button';
import { formatPrice } from '@/lib/currency';
import { toast } from 'sonner';
@@ -26,15 +25,20 @@ interface WishlistItem {
export default function Wishlist() {
const navigate = useNavigate();
const { addItem } = useCartStore();
const [items, setItems] = useState<WishlistItem[]>([]);
const [loading, setLoading] = useState(true);
const { isEnabled, isLoading: modulesLoading } = useModules();
const { settings: wishlistSettings } = useModuleSettings('wishlist');
useEffect(() => {
if (isEnabled('wishlist')) {
loadWishlist();
}
}, [isEnabled]);
if (modulesLoading) {
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>
);
@@ -57,10 +61,6 @@ export default function Wishlist() {
);
}
useEffect(() => {
loadWishlist();
}, []);
const loadWishlist = async () => {
try {
const data = await api.get<WishlistItem[]>('/account/wishlist');
@@ -91,7 +91,7 @@ export default function Wishlist() {
}
try {
const response = await api.post('/cart/add', {
await api.post('/cart/add', {
product_id: item.product_id,
quantity: 1,
});

View File

@@ -84,7 +84,7 @@ export function AccountLayout({ children }: AccountLayoutProps) {
// Full page reload to clear cookies and refresh state
const basePath = (window as any).woonoowCustomer?.basePath || '/store';
window.location.href = window.location.origin + basePath + '/';
} catch (error) {
} catch {
// Even on error, try to redirect and let server handle session
const basePath = (window as any).woonoowCustomer?.basePath || '/store';
window.location.href = window.location.origin + basePath + '/';
@@ -99,7 +99,7 @@ export function AccountLayout({ children }: AccountLayoutProps) {
};
// Logout Button with AlertDialog
const LogoutButton = () => (
const renderLogoutButton = () => (
<AlertDialog>
<AlertDialogTrigger asChild>
<button
@@ -131,7 +131,7 @@ export function AccountLayout({ children }: AccountLayoutProps) {
);
// Sidebar Navigation
const SidebarNav = () => (
const renderSidebarNav = () => (
<aside className="bg-white rounded-lg border p-4">
<div className="mb-6">
<div className="flex items-center gap-3 pb-4 border-b">
@@ -171,13 +171,13 @@ export function AccountLayout({ children }: AccountLayoutProps) {
);
})}
<LogoutButton />
{renderLogoutButton()}
</nav>
</aside>
);
// Tab Navigation (Mobile)
const TabNav = () => (
const renderTabNav = () => (
<div className="bg-white rounded-lg border mb-6 lg:hidden">
<nav className="flex overflow-x-auto">
{menuItems.map((item) => {
@@ -201,23 +201,21 @@ export function AccountLayout({ children }: AccountLayoutProps) {
);
// Responsive layout: Tabs on mobile, Sidebar on desktop
return (
<div className="py-8">
{/* Mobile: Tab Navigation */}
<TabNav />
<div className="py-8">
{/* Mobile: Tab Navigation */}
{renderTabNav()}
{/* Desktop: Sidebar + Content */}
<div className="grid grid-cols-1 lg:grid-cols-4 gap-6">
<div className="hidden lg:block lg:col-span-1">
<SidebarNav />
</div>
<div className="lg:col-span-3">
<div className="bg-white rounded-lg border p-6">
{children}
</div>
{/* Desktop: Sidebar + Content */}
<div className="grid grid-cols-1 lg:grid-cols-4 gap-6">
<div className="hidden lg:block lg:col-span-1">
{renderSidebarNav()}
</div>
<div className="lg:col-span-3">
<div className="bg-white rounded-lg border p-6">
{children}
</div>
</div>
</div>
);
</div>
}

View File

@@ -1,5 +1,5 @@
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 { useCartSettings } from '@/hooks/useAppearanceSettings';
import { updateCartItemQuantity, removeCartItem, clearCartAPI, fetchCart, applyCoupon, removeCoupon } from '@/lib/cart/api';

View File

@@ -8,7 +8,7 @@ import { DynamicCheckoutField, type CheckoutField } from '@/components/DynamicCh
import Container from '@/components/Layout/Container';
import SEOHead from '@/components/SEOHead';
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 { apiClient } from '@/lib/api/client';
import { api } from '@/lib/api/client';
@@ -104,7 +104,6 @@ export default function Checkout() {
// Countries and states data
const [countries, setCountries] = useState<{ code: string; name: string }[]>([]);
const [states, setStates] = useState<Record<string, Record<string, string>>>({});
const [defaultCountry, setDefaultCountry] = useState('');
// Load countries and states
useEffect(() => {
@@ -117,7 +116,6 @@ export default function Checkout() {
}>('/countries');
setCountries(data.countries || []);
setStates(data.states || {});
setDefaultCountry(data.default_country || '');
// Set default country if not already set
if (!billingData.country && data.default_country) {
@@ -131,6 +129,7 @@ export default function Checkout() {
}
};
loadCountries();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// Country/state options for SearchableSelect
@@ -146,7 +145,8 @@ export default function Checkout() {
setBillingData(prev => ({ ...prev, state: '' }));
}
}
}, [billingData.country, states]);
}, [billingData.country, billingData.state, states]);
useEffect(() => {
if (shippingData.country && shippingData.state) {
@@ -155,7 +155,8 @@ export default function Checkout() {
setShippingData(prev => ({ ...prev, state: '' }));
}
}
}, [shippingData.country, states]);
}, [shippingData.country, shippingData.state, states]);
// Dynamic checkout fields from API
const [checkoutFields, setCheckoutFields] = useState<CheckoutField[]>([]);
@@ -298,9 +299,6 @@ export default function Checkout() {
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', {
shipping: {
@@ -308,7 +306,7 @@ export default function Checkout() {
state: addressData.state,
city: addressData.city,
postcode: addressData.postcode,
destination_id: destinationId || undefined,
destination_id: undefined,
},
items,
});
@@ -332,9 +330,6 @@ export default function Checkout() {
// Trigger shipping rate fetch when address or destination changes
useEffect(() => {
const addressData = shipToDifferentAddress ? shippingData : billingData;
const destinationId = shipToDifferentAddress
? customFieldData['shipping_destination_id']
: customFieldData['billing_destination_id'];
// Debounce the fetch
const timeoutId = setTimeout(() => {
@@ -344,12 +339,12 @@ export default function Checkout() {
}, 500);
return () => clearTimeout(timeoutId);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
billingData.country, billingData.state, billingData.city, billingData.postcode,
shippingData.country, shippingData.state, shippingData.city, shippingData.postcode,
shipToDifferentAddress,
customFieldData['billing_destination_id'],
customFieldData['shipping_destination_id'],
customFieldData,
cart.items.length,
]);
@@ -393,6 +388,7 @@ export default function Checkout() {
};
loadAddresses();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [user, isVirtualOnly]);
// Helper functions to fill forms from saved addresses
@@ -461,7 +457,8 @@ export default function Checkout() {
country: user.shipping.country || '',
});
}
}, [user]);
}, [user, savedAddresses.length]);
const handleApplyCoupon = async () => {
if (!couponCode.trim()) return;

View File

@@ -3,7 +3,6 @@ import { useParams, useNavigate } from 'react-router-dom';
import { useQuery } from '@tanstack/react-query';
import { api } from '@/lib/api/client';
import { Helmet } from 'react-helmet-async';
import { cn } from '@/lib/utils';
// Section Components
import { HeroSection } from './sections/HeroSection';
@@ -23,6 +22,7 @@ interface SectionProp {
interface SectionStyles {
backgroundColor?: string;
backgroundImage?: string;
backgroundType?: 'color' | 'image' | 'gradient';
backgroundOverlay?: number;
paddingTop?: string;
paddingBottom?: string;
@@ -164,6 +164,7 @@ export function DynamicPageRenderer({ slug: propSlug }: DynamicPageRendererProps
// Handle 404
useEffect(() => {
if (error) {
// eslint-disable-next-line react-hooks/set-state-in-effect
setNotFound(true);
}
}, [error]);

View File

@@ -92,7 +92,7 @@ export function ContactFormSection({
});
window.location.href = finalUrl;
}
} catch (err) {
} catch {
setError('Failed to submit form. Please try again.');
} finally {
setSubmitting(false);

View File

@@ -28,12 +28,6 @@ const COLOR_SCHEMES: Record<string, { bg: string; text: string }> = {
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) => {
switch (className) {
case 'text-xs': return '0.75rem';
@@ -164,10 +158,10 @@ const generateScopedStyles = (sectionId: string, elementStyles: Record<string, a
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'];
// Default to 'default' width if not specified
const layout = section.layoutVariant || 'default';
const _layout = section.layoutVariant || 'default';
const heightPreset = section.styles?.heightPreset || 'default';

View File

@@ -16,7 +16,6 @@ interface HeroSectionProps {
export function HeroSection({
id,
layout = 'default',
colorScheme = 'default',
title,
subtitle,
image,
@@ -38,7 +37,6 @@ export function HeroSection({
const isImageRight = layout === 'hero-right-image' || layout === 'image-right';
const isCentered = layout === 'centered' || layout === 'default';
const hasCustomBackground = !!styles?.backgroundColor || !!styles?.backgroundImage || styles?.backgroundType === 'gradient';
const sectionBg = getSectionBackground(styles);
// Helper to get text styles (including font family)
@@ -73,7 +71,7 @@ export function HeroSection({
// 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 (hasCustomBackground) return sectionBg.style;
if (colorScheme === 'primary') {
@@ -83,9 +81,7 @@ export function HeroSection({
return { backgroundColor: 'var(--wn-secondary, #6b7280)' };
}
return undefined;
};
const isDynamicScheme = ['primary', 'secondary'].includes(colorScheme) && !hasCustomBackground;
}; */
return (
<section

View File

@@ -24,7 +24,6 @@ export function ImageTextSection({
elementStyles,
styles,
}: 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';
// Helper to get text styles (including font family)

View File

@@ -83,7 +83,7 @@ export default function Login() {
credentials: 'include',
body: JSON.stringify({ product_id: productId }),
});
} catch (e) {
} catch {
// Skip if product already in wishlist or other error
console.debug('Wishlist merge skipped for product:', productId);
}

View File

@@ -1,5 +1,5 @@
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 { toast } from 'sonner';
import SubscriptionTimeline from '../../components/SubscriptionTimeline';
@@ -52,7 +52,7 @@ const OrderPay: React.FC = () => {
const { orderId } = useParams<{ orderId: string }>();
const [searchParams] = useSearchParams();
const orderKey = searchParams.get('key');
const navigate = useNavigate();
// const navigate = useNavigate();
const [order, setOrder] = useState<OrderDetailsResponse | null>(null);
const [loading, setLoading] = useState(true);
@@ -63,6 +63,7 @@ const OrderPay: React.FC = () => {
if (orderId) {
fetchOrder();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [orderId]);
const fetchOrder = async () => {
@@ -123,7 +124,7 @@ const OrderPay: React.FC = () => {
minimumFractionDigits: 0,
maximumFractionDigits: 0,
}).format(amount);
} catch (e) {
} catch {
// Fallback
return `${currency} ${amount.toFixed(0)}`;
}

View File

@@ -26,7 +26,7 @@ export default function Product() {
const [selectedAttributes, setSelectedAttributes] = useState<Record<string, string>>({});
const thumbnailsRef = useRef<HTMLDivElement>(null);
const { addItem } = useCartStore();
const { isEnabled: wishlistEnabled, isInWishlist, toggleWishlist, isLoggedIn } = useWishlist();
const { isEnabled: wishlistEnabled, isInWishlist, toggleWishlist } = useWishlist();
const { isEnabled: isModuleEnabled } = useModules();
// 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) {
setSelectedImage(product.image || product.images?.[0]);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [product]);
// AUTO-SELECT FIRST VARIATION (Issue #2 from report)
@@ -103,6 +104,7 @@ export default function Product() {
setSelectedAttributes(initialAttributes);
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [product]);
// Find matching variation when attributes change
@@ -114,7 +116,6 @@ export default function Product() {
(product.variations as any[]).forEach(v => {
if (!v.attributes) return;
let isMatch = true;
let score = 0;
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
// 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) {
// Get keys from the variation's attributes (these are the correct WooCommerce keys)
Object.keys(selectedVariation.attributes).forEach(key => {

View File

@@ -1,5 +1,5 @@
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 Container from '@/components/Layout/Container';
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';
export default function ResetPassword() {
const [searchParams] = useSearchParams();
const navigate = useNavigate();
const searchParams = new URLSearchParams(window.location.search);
const key = searchParams.get('key') || '';
const login = searchParams.get('login') || '';
@@ -53,7 +51,7 @@ export default function ResetPassword() {
} else {
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.');
} finally {
setIsValidating(false);
@@ -61,7 +59,8 @@ export default function ResetPassword() {
};
validateKey();
}, [key, login]);
}, [login]);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();

View File

@@ -8,15 +8,12 @@ import { Button } from '@/components/ui/button';
import Container from '@/components/Layout/Container';
import { ProductCard } from '@/components/ProductCard';
import { toast } from 'sonner';
import { useTheme, useLayout } from '@/contexts/ThemeContext';
import { useShopSettings } from '@/hooks/useAppearanceSettings';
import SEOHead from '@/components/SEOHead';
import type { ProductsResponse, ProductCategory, Product } from '@/types/product';
import type { ProductsResponse, ProductCategory } from '@/types/product';
export default function Shop() {
const navigate = useNavigate();
const { config } = useTheme();
const { layout } = useLayout();
const { layout: shopLayout, elements } = useShopSettings();
const [page, setPage] = useState(1);
const [search, setSearch] = useState('');
@@ -96,7 +93,7 @@ export default function Shop() {
const handleAddToCart = async (product: any) => {
try {
const response = await apiClient.post(apiClient.endpoints.cart.add, {
await apiClient.post(apiClient.endpoints.cart.add, {
product_id: product.id,
quantity: 1,
});

View File

@@ -12,11 +12,11 @@ export default function ThankYou() {
const { orderId } = useParams<{ orderId: string }>();
const [searchParams] = useSearchParams();
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 [relatedProducts, setRelatedProducts] = useState<any[]>([]);
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;
useEffect(() => {