Refactor UI imports and update component paths
Some checks failed
Build Frontend / build (push) Failing after 7s
Some checks failed
Build Frontend / build (push) Failing after 7s
Replaces imports from 'components/ui' with 'components/common' across the app and dashboard pages, and updates model and API imports to use new paths under 'lib'. Removes redundant authentication checks from several dashboard pages. Adds new dashboard components and utility files, and reorganizes hooks and services into the 'lib' directory for improved structure.
This commit is contained in:
320
lib/api/server-api.ts
Normal file
320
lib/api/server-api.ts
Normal file
@@ -0,0 +1,320 @@
|
||||
// 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<T = unknown>(
|
||||
endpoint: string,
|
||||
options: RequestInit = {}
|
||||
): Promise<T> {
|
||||
// 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('/auth/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('/auth/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<CustomerResponse> => {
|
||||
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<CustomerStats> => {
|
||||
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<AnalyticsOverview> => {
|
||||
const url = storeId ? `/analytics/overview?storeId=${storeId}` : '/analytics/overview';
|
||||
return fetchServer<AnalyticsOverview>(url);
|
||||
};
|
||||
|
||||
export const getRevenueTrendsServer = async (period: string = '30', storeId?: string): Promise<RevenueData[]> => {
|
||||
const params = new URLSearchParams({ period });
|
||||
if (storeId) params.append('storeId', storeId);
|
||||
|
||||
const url = `/analytics/revenue-trends?${params.toString()}`;
|
||||
return fetchServer<RevenueData[]>(url);
|
||||
};
|
||||
|
||||
export const getProductPerformanceServer = async (storeId?: string): Promise<ProductPerformance[]> => {
|
||||
const url = storeId ? `/analytics/product-performance?storeId=${storeId}` : '/analytics/product-performance';
|
||||
return fetchServer<ProductPerformance[]>(url);
|
||||
};
|
||||
|
||||
export const getCustomerInsightsServer = async (storeId?: string): Promise<CustomerInsights> => {
|
||||
const url = storeId ? `/analytics/customer-insights?storeId=${storeId}` : '/analytics/customer-insights';
|
||||
return fetchServer<CustomerInsights>(url);
|
||||
};
|
||||
|
||||
export const getOrderAnalyticsServer = async (period: string = '30', storeId?: string): Promise<OrderAnalytics> => {
|
||||
const params = new URLSearchParams({ period });
|
||||
if (storeId) params.append('storeId', storeId);
|
||||
|
||||
const url = `/analytics/order-analytics?${params.toString()}`;
|
||||
return fetchServer<OrderAnalytics>(url);
|
||||
};
|
||||
Reference in New Issue
Block a user