diff --git a/packages/ui/src/App.tsx b/packages/ui/src/App.tsx index 65d77414..3d7e9839 100644 --- a/packages/ui/src/App.tsx +++ b/packages/ui/src/App.tsx @@ -20,7 +20,6 @@ import GlobalDragDrop from "@/components/GlobalDragDrop"; // Register all widgets on app boot registerAllWidgets(); -import "./debug_env"; import Index from "./pages/Index"; import Auth from "./pages/Auth"; @@ -64,76 +63,7 @@ const VersionMap = React.lazy(() => import("./pages/VersionMap")); // - -// Ecommerce route wrappers (need useNavigate which requires component context) -const EcommerceBundle = React.lazy(() => import("@polymech/ecommerce").then(m => ({ default: m.EcommerceBundle }))); - -const EcommerceBundleWrapper = () => { - const { user } = useAuth(); - const navigate = useNavigate(); - - // Memoize dependencies to prevent re-renders - const dependencies = React.useMemo(() => { - return { - user: user ? { - id: user.id, - email: user.email, - user_metadata: { - display_name: user.user_metadata?.display_name - } - } : null, - toast: { - success: (msg: string) => import('sonner').then(s => s.toast.success(msg)), - error: (msg: string) => import('sonner').then(s => s.toast.error(msg)) - }, - onFetchAddresses: async (userId: string) => { - const { getShippingAddresses } = await import('@/modules/user/client-user'); - return getShippingAddresses(userId) as Promise; - }, - onSaveAddress: async (userId: string, addresses: any[]) => { - const { saveShippingAddresses } = await import('@/modules/user/client-user'); - await saveShippingAddresses(userId, addresses); - }, - onPlaceOrder: async (data: any) => { - const { useCartStore } = await import('@polymech/ecommerce'); - const { createTransaction } = await import('@/modules/ecommerce/client-ecommerce'); - const items = useCartStore.getState().items; - const subtotal = useCartStore.getState().subtotal; - - await createTransaction({ - shipping_info: data.shipping, - vendor_info: {}, - product_info: items.map((i: any) => ({ - id: i.id, - title: i.title, - image: i.image, - price: i.price, - quantity: i.quantity, - variant: i.variant, - vendorSlug: i.vendorSlug, - pageSlug: i.pageSlug, - })), - total_amount: subtotal, - payment_provider: data.paymentMethod, - }); - useCartStore.getState().clearCart(); - }, - onFetchTransactions: async () => { - const { listTransactions } = await import('@/modules/ecommerce/client-ecommerce'); - return listTransactions(); - }, - onNavigate: (path: string) => navigate(path), - siteName: "PolyMech", - contactEmail: "legal@polymech.org" - }; - }, [user, navigate]); - - return ( - Loading...}> - - - ); -}; +import { EcommerceBundleWrapper } from "./bundles/ecommerce"; const AppWrapper = () => { @@ -146,7 +76,6 @@ const AppWrapper = () => { : "mx-auto 2xl:max-w-7xl flex flex-col min-h-svh transition-colors duration-200 h-full"; const ecommerce = import.meta.env.VITE_ENABLE_ECOMMERCE === 'true'; - console.log('DEBUG: ecommerce:', ecommerce); return (
{!isFullScreenPage && } @@ -285,13 +214,4 @@ const App = () => { ); }; -// Update Routes in AppWrapper to include /test-cache/:id -// ... -// We need to inject the route inside AppWrapper component defined above -// Since ReplaceFileContent works on blocks, I'll target the Routes block in a separate call or try to merge. -// Merging is safer if I can target the App component specifically. -// But the Routes are in AppWrapper. -// Let's split this into two edits. - - export default App; diff --git a/packages/ui/src/bundles/ecommerce.tsx b/packages/ui/src/bundles/ecommerce.tsx new file mode 100644 index 00000000..9773f4d0 --- /dev/null +++ b/packages/ui/src/bundles/ecommerce.tsx @@ -0,0 +1,72 @@ +import React from "react"; +import { useNavigate } from "react-router-dom"; +import { useAuth } from "@/hooks/useAuth"; + +const EcommerceBundle = React.lazy(() => import("@polymech/ecommerce").then(m => ({ default: m.EcommerceBundle }))); + +export const EcommerceBundleWrapper = () => { + const { user } = useAuth(); + const navigate = useNavigate(); + + // Memoize dependencies to prevent re-renders + const dependencies = React.useMemo(() => { + return { + user: user ? { + id: user.id, + email: user.email, + user_metadata: { + display_name: user.user_metadata?.display_name + } + } : null, + toast: { + success: (msg: string) => import('sonner').then(s => s.toast.success(msg)), + error: (msg: string) => import('sonner').then(s => s.toast.error(msg)) + }, + onFetchAddresses: async (userId: string) => { + const { getShippingAddresses } = await import('@/modules/user/client-user'); + return getShippingAddresses(userId) as Promise; + }, + onSaveAddress: async (userId: string, addresses: any[]) => { + const { saveShippingAddresses } = await import('@/modules/user/client-user'); + await saveShippingAddresses(userId, addresses); + }, + onPlaceOrder: async (data: any) => { + const { useCartStore } = await import('@polymech/ecommerce'); + const { createTransaction } = await import('@/modules/ecommerce/client-ecommerce'); + const items = useCartStore.getState().items; + const subtotal = useCartStore.getState().subtotal; + + await createTransaction({ + shipping_info: data.shipping, + vendor_info: {}, + product_info: items.map((i: any) => ({ + id: i.id, + title: i.title, + image: i.image, + price: i.price, + quantity: i.quantity, + variant: i.variant, + vendorSlug: i.vendorSlug, + pageSlug: i.pageSlug, + })), + total_amount: subtotal, + payment_provider: data.paymentMethod, + }); + useCartStore.getState().clearCart(); + }, + onFetchTransactions: async () => { + const { listTransactions } = await import('@/modules/ecommerce/client-ecommerce'); + return listTransactions(); + }, + onNavigate: (path: string) => navigate(path), + siteName: "PolyMech", + contactEmail: "legal@polymech.org" + }; + }, [user, navigate]); + + return ( + Loading...
}> + + + ); +}; diff --git a/packages/ui/src/components/widgets/ImagePickerDialog.tsx b/packages/ui/src/components/widgets/ImagePickerDialog.tsx index 406302b9..a63dd095 100644 --- a/packages/ui/src/components/widgets/ImagePickerDialog.tsx +++ b/packages/ui/src/components/widgets/ImagePickerDialog.tsx @@ -185,7 +185,6 @@ export const ImagePickerDialog: React.FC = ({ } } }; - const toggleSelection = (id: string) => { if (multiple) { setSelectedIds(prev => @@ -197,11 +196,6 @@ export const ImagePickerDialog: React.FC = ({ setSelectedId(id); } }; - - console.log('selectedIds', selectedIds); - console.log('selectedId', selectedId); - - return ( diff --git a/packages/ui/src/debug_env.ts b/packages/ui/src/debug_env.ts deleted file mode 100644 index bfe6f25e..00000000 --- a/packages/ui/src/debug_env.ts +++ /dev/null @@ -1 +0,0 @@ -console.log('DEBUG: process.env.ENABLE_ECOMMERCE:', import.meta.env.VITE_ENABLE_ECOMMERCE); diff --git a/packages/ui/src/hooks/useResponsiveImage.ts b/packages/ui/src/hooks/useResponsiveImage.ts index e3861cb1..82c4f6cc 100644 --- a/packages/ui/src/hooks/useResponsiveImage.ts +++ b/packages/ui/src/hooks/useResponsiveImage.ts @@ -71,17 +71,15 @@ export const useResponsiveImage = ({ if (!requestCache.has(cacheKey)) { const requestPromise = (async () => { - const formData = new FormData(); - formData.append('url', src); - formData.append('sizes', JSON.stringify(responsiveSizes)); - formData.append('formats', JSON.stringify(formats)); - const serverUrl = apiUrl || import.meta.env.VITE_SERVER_IMAGE_API_URL || 'http://192.168.1.11:3333'; - const response = await fetch(`${serverUrl}/api/images/responsive`, { - method: 'POST', - body: formData, + const params = new URLSearchParams({ + url: src, + sizes: JSON.stringify(responsiveSizes), + formats: JSON.stringify(formats), }); + const response = await fetch(`${serverUrl}/api/images/responsive?${params.toString()}`); + if (!response.ok) { const txt = await response.text(); // Remove from cache on error so it can be retried diff --git a/packages/ui/src/modules/user/client-user.ts b/packages/ui/src/modules/user/client-user.ts index 01f6a17e..3721676e 100644 --- a/packages/ui/src/modules/user/client-user.ts +++ b/packages/ui/src/modules/user/client-user.ts @@ -30,26 +30,27 @@ export const fetchAuthorProfile = async (userId: string, client?: SupabaseClient }; export const getUserSettings = async (userId: string, client?: SupabaseClient) => { - const supabase = client || defaultSupabase; return fetchWithDeduplication(`settings-${userId}`, async () => { - const { data, error } = await supabase - .from('profiles') - .select('settings') - .eq('user_id', userId) - .single(); - if (error) throw error; - return (data?.settings as any) || {}; + const token = await getAuthToken(); + const res = await fetch('/api/me/settings', { + headers: { 'Authorization': `Bearer ${token}` } + }); + if (!res.ok) throw new Error(`Failed to fetch settings: ${res.statusText}`); + return await res.json(); }, 100000); }; export const updateUserSettings = async (userId: string, settings: any, client?: SupabaseClient) => { - const supabase = client || defaultSupabase; - await supabase - .from('profiles') - .update({ settings }) - .eq('user_id', userId); - - // Cache invalidation handled by React Query or not needed for now + const token = await getAuthToken(); + const res = await fetch('/api/me/settings', { + method: 'PATCH', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}` + }, + body: JSON.stringify(settings) + }); + if (!res.ok) throw new Error(`Failed to update settings: ${res.statusText}`); }; export const getUserOpenAIKey = async (userId: string, client?: SupabaseClient) => { @@ -118,48 +119,30 @@ export const getUserSecrets = async (userId: string, client?: SupabaseClient) => }; export const getProviderConfig = async (userId: string, provider: string, client?: SupabaseClient) => { - const supabase = client || defaultSupabase; return fetchWithDeduplication(`provider-${userId}-${provider}`, async () => { - console.log('Fetching provider config for user:', userId, 'provider:', provider); - const { data, error } = await supabase - .from('provider_configs') - .select('settings') - .eq('user_id', userId) - .eq('name', provider) - .eq('is_active', true) - .single(); - - if (error) { - // It's common to not have configs for all providers, so we might want to suppress some errors or handle them gracefully - // However, checking error code might be robust. For now let's just throw if it's not a "no rows" error, - // or just return null if not found. - // The original code used .single() which errors if 0 rows. - // Let's use maybeSingle() to be safe? The original code caught the error and returned null. - // But the original query strictly used .single(). - // Let's stick to .single() but catch it here if we want to mimic exact behavior, OR use maybeSingle and return null. - // The calling code expects null if not found. - if (error.code === 'PGRST116') return null; // No rows found - throw error; - } - return data as { settings: any }; + const token = await getAuthToken(); + const res = await fetch(`/api/me/provider-config/${encodeURIComponent(provider)}`, { + headers: { 'Authorization': `Bearer ${token}` } + }); + if (res.status === 404) return null; + if (!res.ok) throw new Error(`Failed to fetch provider config: ${res.statusText}`); + return await res.json(); }); } export const fetchUserRoles = async (userId: string, client?: SupabaseClient) => { - const supabase = client || defaultSupabase; return fetchWithDeduplication(`roles-${userId}`, async () => { - const { data, error } = await supabase - .from('user_roles') - .select('role') - .eq('user_id', userId); - - if (error) { - console.error('Error fetching user roles:', error); + const token = await getAuthToken(); + const res = await fetch('/api/me/roles', { + headers: { 'Authorization': `Bearer ${token}` } + }); + if (!res.ok) { + console.error('Error fetching user roles:', res.statusText); return []; } - return data.map(r => r.role); + return await res.json(); }); }; diff --git a/packages/ui/src/sw.ts b/packages/ui/src/sw.ts index 663f9754..61b24c4d 100644 --- a/packages/ui/src/sw.ts +++ b/packages/ui/src/sw.ts @@ -9,7 +9,7 @@ const SW_VERSION = '1.0.5-debug'; console.log(`[SW] Initializing Version: ${SW_VERSION}`); -self.addEventListener('fetch', (event) => { }); + declare let self: ServiceWorkerGlobalScope @@ -25,8 +25,7 @@ precacheAndRoute(self.__WB_MANIFEST) // clean old assets cleanupOutdatedCaches() -self.skipWaiting() -clientsClaim() + // allow only fallback in dev: we don't want to cache everything let allowlist: undefined | RegExp[] @@ -69,6 +68,7 @@ const navigationHandler = async (params: any) => { try { const strategy = new NetworkFirst({ cacheName: 'pages', + networkTimeoutSeconds: 3, // Fallback to cache if network is slow plugins: [ { cacheWillUpdate: async ({ response }) => { @@ -91,3 +91,7 @@ registerRoute(new NavigationRoute( denylist: [/^\/upload-share-target/] } )) + +self.addEventListener('activate', () => { + clientsClaim(); +});