From ee642f2fe3c237e25cef31784ca49ecff61a8343 Mon Sep 17 00:00:00 2001 From: Peter Valdez Date: Sun, 23 Feb 2025 17:30:45 -0500 Subject: [PATCH] Factor out remaining API fetches --- frontend/src/api/index.ts | 3 + frontend/src/app/hooks/useStoreRoles.ts | 17 +--- frontend/src/app/orders/[id]/page.tsx | 67 +++------------- frontend/src/app/orders/page.tsx | 79 ++++--------------- .../src/app/store/[id]/dashboard/page.tsx | 75 +++++------------- .../src/app/store/[id]/inventory/page.tsx | 65 +++------------ frontend/src/components/Search.tsx | 9 +-- 7 files changed, 65 insertions(+), 250 deletions(-) diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts index 1b30df8..a3747bf 100644 --- a/frontend/src/api/index.ts +++ b/frontend/src/api/index.ts @@ -44,6 +44,9 @@ export interface Order { city: string; state: string; zip_code: string; + country?: string; + customer_name?: string; + customer_phone?: string; }; customer_phone?: string; stores: { diff --git a/frontend/src/app/hooks/useStoreRoles.ts b/frontend/src/app/hooks/useStoreRoles.ts index 6212777..9cb3f4e 100644 --- a/frontend/src/app/hooks/useStoreRoles.ts +++ b/frontend/src/app/hooks/useStoreRoles.ts @@ -1,6 +1,6 @@ import { useState, useEffect } from 'react'; import { useAuth } from '../contexts/auth'; -import { config } from '@/config'; +import { storesApi } from '@/api'; export function useStoreRoles(storeId: string) { const { user } = useAuth(); @@ -15,20 +15,7 @@ export function useStoreRoles(storeId: string) { } try { - const response = await fetch( - `${config.apiUrl}/api/v0/stores/${storeId}/roles`, - { - headers: { - 'Authorization': `Bearer ${user.token}` - } - } - ); - - if (!response.ok) { - throw new Error('Failed to fetch store roles'); - } - - const data = await response.json(); + const data = await storesApi.getStoreRoles(user.token, storeId); setRoles(data.roles); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to fetch store roles'); diff --git a/frontend/src/app/orders/[id]/page.tsx b/frontend/src/app/orders/[id]/page.tsx index b483eb0..3295f83 100644 --- a/frontend/src/app/orders/[id]/page.tsx +++ b/frontend/src/app/orders/[id]/page.tsx @@ -2,7 +2,6 @@ import { useState, useEffect } from 'react'; import { useAuth } from '@/app/contexts/auth'; -import { config } from '@/config'; import { formatCurrency } from '@/utils/currency'; import { MapPinIcon, BuildingStorefrontIcon, XMarkIcon, ClockIcon } from '@heroicons/react/24/outline'; import { useRouter, useParams } from 'next/navigation'; @@ -12,6 +11,7 @@ import type { LatLngExpression, LatLngTuple } from 'leaflet'; import styles from './map.module.css'; import ReactDOMServer from 'react-dom/server'; import L from 'leaflet'; +import { ordersApi, Order } from '@/api'; // Dynamically import the map components to avoid SSR issues const MapContainer = dynamic( @@ -35,48 +35,6 @@ const Polyline = dynamic( { ssr: false } ); -interface OrderItem { - id: string; - name: string; - quantity: number; - price: number; -} - -interface Store { - store: { - id: string; - name: string; - }; - items: OrderItem[]; -} - -interface Order { - id: string; - created: string; - status: string; - payment_status: string; - delivery_fee: number; - total_amount: number; - tax_amount: number; - delivery_address?: { - street_address: string[]; - city: string; - state: string; - zip_code: string; - }; - customer_phone?: string; - stores: Store[]; -} - -const formatDateTime = (isoString: string) => { - const utcDate = new Date(isoString + 'Z'); - return new Intl.DateTimeFormat('en-US', { - dateStyle: 'medium', - timeStyle: 'short', - timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone, - }).format(utcDate); -}; - // Mock coordinates for demo (we'll replace these with real geocoding later) const MOCK_COORDINATES: { store: LatLngTuple; delivery: LatLngTuple } = { store: [40.7594, -73.9229], @@ -96,6 +54,15 @@ const DeliveryMarker = () => ( ); +const formatDateTime = (isoString: string) => { + const utcDate = new Date(isoString + 'Z'); + return new Intl.DateTimeFormat('en-US', { + dateStyle: 'medium', + timeStyle: 'short', + timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone, + }).format(utcDate); +}; + export default function OrderViewPage() { const { user } = useAuth(); const [order, setOrder] = useState(null); @@ -112,18 +79,8 @@ export default function OrderViewPage() { if (!user?.token || !orderId) return; try { - const response = await fetch(`${config.apiUrl}/api/v0/orders`, { - headers: { - 'Authorization': `Bearer ${user.token}`, - }, - }); - - if (!response.ok) { - throw new Error('Failed to fetch orders'); - } - - const orders = await response.json(); - const order = orders.find((o: Order) => o.id === orderId); + const orders = await ordersApi.getAdminOrders(user.token); + const order = orders.find(o => o.id === orderId); if (!order) { throw new Error('Order not found'); diff --git a/frontend/src/app/orders/page.tsx b/frontend/src/app/orders/page.tsx index 259f2a7..73fc68e 100644 --- a/frontend/src/app/orders/page.tsx +++ b/frontend/src/app/orders/page.tsx @@ -7,6 +7,7 @@ import { config } from '@/config'; import { CurrencyDollarIcon, ShoppingBagIcon, ClockIcon, ChartBarIcon } from '@heroicons/react/24/outline'; import { BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer, Legend } from 'recharts'; import { useRouter } from 'next/navigation'; +import { ordersApi, Order } from '@/api'; interface OrderItem { id: string; @@ -19,31 +20,6 @@ interface OrderItem { }; } -interface Order { - id: string; - created: string; - status: string; - payment_status: string; - delivery_fee: number; - total_amount: number; - delivery_address: { - street_address: string[]; - city: string; - state: string; - zip_code: string; - country: string; - customer_name: string; - customer_phone?: string; - } | null; - stores: Array<{ - store: { - id: string; - name: string; - }; - items: OrderItem[]; - }>; -} - interface DailyCount { date: string; timestamp: number; @@ -240,57 +216,34 @@ export default function OrdersDashboard() { return; } - const response = await fetch(`${config.apiUrl}/api/v0/orders`, { - headers: { - 'Authorization': `Bearer ${user?.token}`, - }, - }); - - if (!response.ok) { - throw new Error('Failed to fetch orders'); - } - - const data = await response.json(); - setOrders(data); - } catch (err) { - setError(err instanceof Error ? err.message : 'Failed to fetch orders'); + const orders = await ordersApi.getAdminOrders(user.token); + setOrders(orders); + } catch (error) { + console.error('Error fetching orders:', error); + setError(error instanceof Error ? error.message : 'Failed to fetch orders'); } finally { setLoading(false); } }; - if (user) { + if (user?.token) { fetchOrders(); } }, [user]); const updateOrderStatus = async (orderId: string, newStatus: string) => { - try { - const response = await fetch(`${config.apiUrl}/api/v0/orders/${orderId}/status`, { - method: 'PATCH', - headers: { - 'Authorization': `Bearer ${user?.token}`, - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ status: newStatus }) - }); - - if (!response.ok) { - const errorData = await response.json(); - throw new Error(errorData.detail || 'Failed to update order status'); - } + if (!user?.token) return; - setOrders(currentOrders => - currentOrders.map(order => - order.id === orderId - ? { ...order, status: newStatus } - : order + try { + const updatedOrder = await ordersApi.updateOrderStatus(user.token, orderId, newStatus); + setOrders(prevOrders => + prevOrders.map(order => + order.id === orderId ? updatedOrder : order ) ); - - toast.success('Order status updated'); - } catch (err) { - console.error('Status update error:', err); + toast.success('Order status updated successfully'); + } catch (error) { + console.error('Error updating order status:', error); toast.error('Failed to update order status'); } }; diff --git a/frontend/src/app/store/[id]/dashboard/page.tsx b/frontend/src/app/store/[id]/dashboard/page.tsx index d78265c..b3357f4 100644 --- a/frontend/src/app/store/[id]/dashboard/page.tsx +++ b/frontend/src/app/store/[id]/dashboard/page.tsx @@ -10,6 +10,7 @@ import { use } from 'react'; import { CurrencyDollarIcon, ShoppingBagIcon, ClockIcon, ChartBarIcon } from '@heroicons/react/24/outline'; import { BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer, Legend } from 'recharts'; import Link from 'next/link'; +import { storesApi, ordersApi, Order } from '@/api'; interface OrderItem { id: string; @@ -26,25 +27,6 @@ interface Store { items: OrderItem[]; } -interface Order { - id: string; - created: string; - status: string; - payment_status: string; - delivery_fee: number; - total_amount: number; - delivery_address: { - street_address: string[]; - city: string; - state: string; - zip_code: string; - country: string; - customer_name: string; - customer_phone?: string; - } | null; - stores: Store[]; -} - interface DailyCount { date: string; timestamp: number; @@ -249,51 +231,34 @@ export default function StoreDashboard({ params }: { params: Promise<{ id: strin useEffect(() => { const fetchOrders = async () => { - if (!user?.token || rolesLoading) return; // Don't fetch if still loading roles + if (!user?.token) return; try { - const response = await fetch(`${config.apiUrl}/api/v0/stores/${storeId}/orders`, { - headers: { - 'Authorization': `Bearer ${user.token}`, - }, - }); - - if (!response.ok) { - throw new Error('Failed to fetch orders'); - } - - const data = await response.json(); - setOrders(data); - } catch (err) { - setError(err instanceof Error ? err.message : 'Failed to fetch orders'); - } finally { - setLoading(false); + const orders = await storesApi.getStoreOrders(user.token, storeId); + setOrders(orders); + } catch (error) { + console.error('Error fetching orders:', error); + setError(error instanceof Error ? error.message : 'Failed to fetch orders'); } }; - if (isAdmin) { - fetchOrders(); - } - }, [user, storeId, isAdmin, rolesLoading]); - - // Fetch store details - useEffect(() => { const fetchStore = async () => { try { - const response = await fetch(`${config.apiUrl}/api/v0/stores/${storeId}`); - if (!response.ok) { - throw new Error('Failed to fetch store details'); - } - const data = await response.json(); - setStore(data); - } catch (err) { - console.error('Error fetching store:', err); - setError(err instanceof Error ? err.message : 'Failed to fetch store details'); + const store = await storesApi.getStore(storeId); + setStore(store); + } catch (error) { + console.error('Error fetching store:', error); + setError(error instanceof Error ? error.message : 'Failed to fetch store'); + } finally { + setLoading(false); } }; - fetchStore(); - }, [storeId]); + if (isAdmin) { + fetchOrders(); + fetchStore(); + } + }, [storeId, isAdmin, user]); // Toggle status filter const toggleStatus = (status: string) => { @@ -327,7 +292,7 @@ export default function StoreDashboard({ params }: { params: Promise<{ id: strin const lowercaseSearch = searchTerm.toLowerCase(); result = result.filter(order => order.id.toLowerCase().includes(lowercaseSearch) || - order.delivery_address?.customer_name.toLowerCase().includes(lowercaseSearch) + order.delivery_address?.customer_name?.toLowerCase().includes(lowercaseSearch) || false ); } diff --git a/frontend/src/app/store/[id]/inventory/page.tsx b/frontend/src/app/store/[id]/inventory/page.tsx index bc8af32..969f625 100644 --- a/frontend/src/app/store/[id]/inventory/page.tsx +++ b/frontend/src/app/store/[id]/inventory/page.tsx @@ -4,19 +4,11 @@ import { useState, useEffect } from 'react'; import { useAuth } from '@/app/contexts/auth'; import { useStoreRoles } from '@/app/hooks/useStoreRoles'; import { toast } from 'react-hot-toast'; -import { config } from '@/config'; import { useRouter } from 'next/navigation'; import { use } from 'react'; import { PlusIcon, PencilIcon, TrashIcon } from '@heroicons/react/24/outline'; import Link from 'next/link'; - -interface StoreItem { - id: string; - name: string; - price: number; - description?: string; - imageUrl?: string; -} +import { storesApi, StoreItem } from '@/api'; interface EditItemModalProps { item?: StoreItem; @@ -140,20 +132,11 @@ export default function StoreInventory({ params }: { params: Promise<{ id: strin useEffect(() => { const fetchStoreAndItems = async () => { try { - // Fetch store details - const storeResponse = await fetch(`${config.apiUrl}/api/v0/stores/${storeId}`); - if (!storeResponse.ok) { - throw new Error('Failed to fetch store'); - } - const storeData = await storeResponse.json(); + // Fetch store details and items + const storeData = await storesApi.getStore(storeId); setStore(storeData); - // Fetch store items - const itemsResponse = await fetch(`${config.apiUrl}/api/v0/stores/${storeId}/items`); - if (!itemsResponse.ok) { - throw new Error('Failed to fetch store items'); - } - const itemsData = await itemsResponse.json(); + const itemsData = await storesApi.getStoreItems(storeId); setItems(itemsData); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to fetch store data'); @@ -178,37 +161,17 @@ export default function StoreInventory({ params }: { params: Promise<{ id: strin }; const handleSaveItem = async (itemData: Partial) => { + if (!user?.token) return; + try { if (editingItem) { // Update existing item - const response = await fetch(`${config.apiUrl}/api/v0/stores/${storeId}/items/${editingItem.id}`, { - method: 'PATCH', - headers: { - 'Authorization': `Bearer ${user?.token}`, - 'Content-Type': 'application/json' - }, - body: JSON.stringify(itemData) - }); - - if (!response.ok) throw new Error('Failed to update item'); - - const updatedItem = await response.json(); + const updatedItem = await storesApi.updateStoreItem(user.token, storeId, editingItem.id, itemData); setItems(items.map(item => item.id === editingItem.id ? updatedItem : item)); toast.success('Item updated successfully'); } else { // Create new item - const response = await fetch(`${config.apiUrl}/api/v0/stores/${storeId}/items`, { - method: 'POST', - headers: { - 'Authorization': `Bearer ${user?.token}`, - 'Content-Type': 'application/json' - }, - body: JSON.stringify(itemData) - }); - - if (!response.ok) throw new Error('Failed to create item'); - - const newItem = await response.json(); + const newItem = await storesApi.createStoreItem(user.token, storeId, itemData); setItems([...items, newItem]); toast.success('Item created successfully'); } @@ -221,18 +184,10 @@ export default function StoreInventory({ params }: { params: Promise<{ id: strin }; const handleDeleteItem = async (itemId: string) => { - if (!confirm('Are you sure you want to delete this item?')) return; + if (!user?.token || !confirm('Are you sure you want to delete this item?')) return; try { - const response = await fetch(`${config.apiUrl}/api/v0/stores/${storeId}/items/${itemId}`, { - method: 'DELETE', - headers: { - 'Authorization': `Bearer ${user?.token}` - } - }); - - if (!response.ok) throw new Error('Failed to delete item'); - + await storesApi.deleteStoreItem(user.token, storeId, itemId); setItems(items.filter(item => item.id !== itemId)); toast.success('Item deleted successfully'); } catch (error) { diff --git a/frontend/src/components/Search.tsx b/frontend/src/components/Search.tsx index c3beb09..c79076b 100644 --- a/frontend/src/components/Search.tsx +++ b/frontend/src/components/Search.tsx @@ -3,7 +3,7 @@ import { useState, useEffect, useRef } from "react"; import { useSearch } from "@/app/contexts/search"; import { SearchItem } from "@/app/contexts/search"; -import { config } from "@/config"; +import { searchApi } from "@/api"; export function Search() { const { setSearchItems, setIsLoading, isLoading } = useSearch(); @@ -15,12 +15,7 @@ export function Search() { const handleSearch = async (searchQuery: string) => { try { setIsLoading(true); - const response = await fetch( - `${config.searchUrl}/api/search?query=${encodeURIComponent( - searchQuery, - )}&index=${productIndex}`, - ); - const { hits } = await response.json(); + const hits = await searchApi.searchProducts(searchQuery); setSearchItems(hits as SearchItem[]); } catch (error) { console.error("Search failed:", error);