Refactor UI imports and update component paths
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:
g
2026-01-13 05:02:13 +00:00
parent a6e6cd0757
commit fe01f31538
173 changed files with 1512 additions and 867 deletions

320
lib/api/server-api.ts Normal file
View 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);
};