// This module is only meant to be used in Server Components in the app/ directory // It cannot be imported in Client Components or pages/ directory import { redirect } from 'next/navigation'; import { CustomerResponse, CustomerStats } from './api-client'; // Dynamically import cookies to prevent build errors let cookiesModule: any; try { // This will only work in server components cookiesModule = require('next/headers'); } catch (error) { console.warn('Warning: next/headers only works in Server Components in the app/ directory'); } /** * Constructs a server-side API URL for backend requests * Used in Server Components and API routes to directly access the backend API * * @param endpoint The API endpoint path * @returns A complete URL to the backend API */ function getServerApiUrl(endpoint: string): string { const apiUrl = process.env.SERVER_API_URL || 'http://localhost:3001/api'; // Validate API URL to prevent 500 errors if (!apiUrl || apiUrl === 'undefined' || apiUrl === 'null') { console.warn('SERVER_API_URL not properly set, using localhost fallback'); const fallbackUrl = 'http://localhost:3001/api'; const cleanEndpoint = endpoint.startsWith('/') ? endpoint.substring(1) : endpoint; return fallbackUrl.endsWith('/') ? `${fallbackUrl}${cleanEndpoint}` : `${fallbackUrl}/${cleanEndpoint}`; } const cleanEndpoint = endpoint.startsWith('/') ? endpoint.substring(1) : endpoint; return apiUrl.endsWith('/') ? `${apiUrl}${cleanEndpoint}` : `${apiUrl}/${cleanEndpoint}`; } /** * Server-side fetch wrapper with authentication. * Used in Server Components to make authenticated API requests to the backend. * This uses the SERVER_API_URL environment variable and is different from client-side fetching. * * @throws Error if used outside of a Server Component in the app/ directory */ export async function fetchServer( endpoint: string, options: RequestInit = {} ): Promise { // Check if we're in a server component context if (!cookiesModule?.cookies) { throw new Error( "fetchServer can only be used in Server Components in the app/ directory. " + "For client components, use clientFetch or fetchClient instead." ); } // Get auth token from cookies const cookieStore = await cookiesModule.cookies(); const authToken = cookieStore.get('Authorization')?.value; // Redirect to login if not authenticated if (!authToken) redirect('/login'); try { // Get the complete backend URL using the utility function const url = getServerApiUrl(endpoint); // Make the request with proper auth headers const res = await fetch(url, { ...options, headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${authToken}`, ...options.headers, }, cache: 'no-store', // Always fetch fresh data }); // Handle auth failures if (res.status === 401) redirect('/login'); // Handle other errors if (!res.ok) { let errorData; try { errorData = await res.json(); } catch { errorData = {}; } // Handle new error format: { success: false, error: { message: "...", code: "..." } } // or old format: { error: "...", message: "..." } let errorMessage: string; if (errorData.error?.message) { errorMessage = errorData.error.message; } else if (typeof errorData.error === 'string') { errorMessage = errorData.error; } else if (errorData.message) { errorMessage = errorData.message; } else { errorMessage = `Request failed: ${res.status} ${res.statusText}`; } throw new Error(errorMessage); } // Handle 204 No Content responses if (res.status === 204) { return {} as T; } try { const data = await res.json(); return data; } catch (parseError) { // If JSON parsing fails, throw a proper error throw new Error(`Failed to parse response from ${endpoint}: ${parseError instanceof Error ? parseError.message : String(parseError)}`); } } catch (error) { console.error(`Server request to ${endpoint} failed:`, error); // Ensure we always throw an Error instance, not an object if (error instanceof Error) { throw error; } else if (typeof error === 'string') { throw new Error(error); } else { const errorStr = error && typeof error === 'object' ? JSON.stringify(error) : String(error); throw new Error(`Request failed: ${errorStr}`); } } } /** * Get a paginated list of customers (server-side) * @param page Page number (starting from 1) * @param limit Number of items per page * @returns Promise with customers data and total count */ export const getCustomersServer = async (page: number = 1, limit: number = 25): Promise => { return fetchServer(`/customers?page=${page}&limit=${limit}`); }; /** * Get detailed stats for a specific customer (server-side) * @param userId The customer's user ID * @returns Promise with detailed customer stats */ export const getCustomerDetailsServer = async (userId: string): Promise => { return fetchServer(`/customers/${userId}`); }; // Server-side platform stats function export async function getPlatformStatsServer() { try { const url = getServerApiUrl('/stats/platform'); // Make direct request without auth const res = await fetch(url, { cache: 'no-store', headers: { 'Content-Type': 'application/json' } }); const data = await res.json(); // If we have real data, use it if (data && Object.keys(data).length > 0) { return data; } console.info('Using sample stats data for demo'); return { orders: { completed: 1289 }, vendors: { total: 15 }, transactions: { volume: 38450, averageOrderValue: 29.83 } }; } catch (error) { console.error('Error fetching platform stats (server):', error); // Return default stats to prevent UI breakage return { orders: { completed: 0 }, vendors: { total: 0 }, transactions: { volume: 0, averageOrderValue: 0 } }; } } // Analytics Types for server-side export interface AnalyticsOverview { orders: { total: number; completed: number; pending: number; completionRate: string; }; revenue: { total: number; monthly: number; weekly: number; averageOrderValue: number; }; products: { total: number; }; customers: { unique: number; }; userType?: 'vendor' | 'staff'; } export interface RevenueData { _id: { year: number; month: number; day: number; }; revenue: number; orders: number; } export interface ProductPerformance { productId: string; name: string; image: string; unitType: string; currentStock: number; stockStatus: string; totalSold: number; totalRevenue: number; orderCount: number; averagePrice: number; } export interface CustomerInsights { totalCustomers: number; segments: { new: number; returning: number; loyal: number; vip: number; }; topCustomers: Array<{ _id: string; orderCount: number; totalSpent: number; averageOrderValue: number; firstOrder: string; lastOrder: string; }>; averageOrdersPerCustomer: string; } export interface OrderAnalytics { statusDistribution: Array<{ _id: string; count: number; }>; dailyOrders: Array<{ _id: { year: number; month: number; day: number; }; orders: number; revenue: number; }>; averageProcessingDays: number; } // Server-side analytics functions export const getAnalyticsOverviewServer = async (storeId?: string): Promise => { const url = storeId ? `/analytics/overview?storeId=${storeId}` : '/analytics/overview'; return fetchServer(url); }; export const getRevenueTrendsServer = async (period: string = '30', storeId?: string): Promise => { const params = new URLSearchParams({ period }); if (storeId) params.append('storeId', storeId); const url = `/analytics/revenue-trends?${params.toString()}`; return fetchServer(url); }; export const getProductPerformanceServer = async (storeId?: string): Promise => { const url = storeId ? `/analytics/product-performance?storeId=${storeId}` : '/analytics/product-performance'; return fetchServer(url); }; export const getCustomerInsightsServer = async (storeId?: string): Promise => { const url = storeId ? `/analytics/customer-insights?storeId=${storeId}` : '/analytics/customer-insights'; return fetchServer(url); }; export const getOrderAnalyticsServer = async (period: string = '30', storeId?: string): Promise => { const params = new URLSearchParams({ period }); if (storeId) params.append('storeId', storeId); const url = `/analytics/order-analytics?${params.toString()}`; return fetchServer(url); };