Refactor API URLs and add environment config example

Replaces hardcoded production API URLs with localhost defaults for local development in both server and client code. Updates Dockerfile to require API URLs via deployment environment variables. Improves ChatTable to use a batch endpoint for chats and unread counts, with backward compatibility. Adds an env.example file to document required environment variables. Updates next.config.mjs to use environment variables for backend API rewrites and image domains.
This commit is contained in:
NotII
2025-09-01 15:35:10 +01:00
parent 3528ca45cc
commit 29ec1be68c
8 changed files with 74 additions and 29 deletions

View File

@@ -39,7 +39,10 @@ EXPOSE 3000
ENV NODE_ENV=production ENV NODE_ENV=production
ENV NEXT_PUBLIC_API_URL=/api ENV NEXT_PUBLIC_API_URL=/api
ENV SERVER_API_URL=https://internal-api.inboxi.ng/api # Backend API URL should be set via deployment environment
# ENV SERVER_API_URL=set_via_deployment
# ENV API_BASE_URL=set_via_deployment
# ENV API_HOSTNAME=set_via_deployment
# Set GIT_COMMIT_SHA environment variable in the final image by reading the file # Set GIT_COMMIT_SHA environment variable in the final image by reading the file
ENV GIT_COMMIT_SHA="$(cat /app/git_commit_sha)" ENV GIT_COMMIT_SHA="$(cat /app/git_commit_sha)"

View File

@@ -30,7 +30,7 @@ export async function GET(req: NextRequest) {
console.log('Auth check: Token found -', token.substring(0, 15) + '...'); console.log('Auth check: Token found -', token.substring(0, 15) + '...');
const apiUrl = process.env.SERVER_API_URL || 'https://internal-api.inboxi.ng/api'; const apiUrl = process.env.SERVER_API_URL || 'http://localhost:3001/api';
console.log(`Auth check: Calling external API: ${apiUrl}/auth/me`); console.log(`Auth check: Calling external API: ${apiUrl}/auth/me`);
try { try {

View File

@@ -213,7 +213,9 @@ export default function ChatDetail({ chatId }: { chatId: string }) {
try { try {
setLoading(true); setLoading(true);
// Use clientFetch instead of direct axios calls // Use clientFetch to load chat data
// For now, we're only loading chat data, but this could be extended
// to load additional data in parallel (user profiles, order details, etc.)
const response = await clientFetch(`/chats/${chatId}`); const response = await clientFetch(`/chats/${chatId}`);
setChatData(response); setChatData(response);

View File

@@ -176,24 +176,39 @@ export default function ChatTable() {
// Get the vendor ID from the auth token // Get the vendor ID from the auth token
const { vendorId } = getVendorIdFromToken(); const { vendorId } = getVendorIdFromToken();
// Now fetch chats for this vendor using clientFetch with pagination // Use the optimized batch endpoint that fetches chats and unread counts together
const response = await clientFetch(`/chats/vendor/${vendorId}?page=${page}&limit=${limit}`); const batchResponse = await clientFetch(`/chats/vendor/${vendorId}/batch?page=${page}&limit=${limit}`);
// Check if the response is the old format (array) or new paginated format // Handle batch response (contains both chats and unread counts)
if (Array.isArray(response)) { if (Array.isArray(batchResponse)) {
// Handle old API response format (backward compatibility) // Fallback to old API response format (backward compatibility)
setChats(response); setChats(batchResponse);
setTotalPages(1); setTotalPages(1);
setTotalChats(response.length); setTotalChats(batchResponse.length);
// Try to fetch unread counts separately if using old endpoint
try {
const unreadResponse = await clientFetch(`/chats/vendor/${vendorId}/unread`);
setUnreadCounts(unreadResponse);
} catch (error) {
console.warn("Failed to fetch unread counts:", error);
}
} else { } else {
// Handle new paginated response format // Handle new batch response format
setChats(response.chats || []); setChats(batchResponse.chats || []);
setTotalPages(response.totalPages || 1); setTotalPages(batchResponse.totalPages || 1);
setCurrentPage(response.page || 1); setCurrentPage(batchResponse.page || 1);
setTotalChats(response.totalChats || 0); setTotalChats(batchResponse.totalChats || 0);
// Handle unread counts from batch response
const newUnreadCounts = batchResponse.unreadCounts || { totalUnread: 0, chatCounts: {} };
// Play sound if there are new messages
if (newUnreadCounts.totalUnread > unreadCounts.totalUnread) {
//playNotificationSound();
}
setUnreadCounts(newUnreadCounts);
} }
await fetchUnreadCounts();
} catch (error) { } catch (error) {
console.error("Failed to fetch chats:", error); console.error("Failed to fetch chats:", error);
toast.error("Failed to load chat conversations"); toast.error("Failed to load chat conversations");

23
env.example Normal file
View File

@@ -0,0 +1,23 @@
# Frontend Environment Configuration
# Copy this file to .env.local for development or .env for production
# ===========================================
# FRONTEND CONFIGURATION (Safe for browser)
# ===========================================
NEXT_PUBLIC_API_URL=/api
# ===========================================
# BACKEND CONFIGURATION (Server-side only)
# ===========================================
# Replace with your actual backend domain
SERVER_API_URL=https://internal-api.inboxi.ng/api
API_BASE_URL=https://internal-api.inboxi.ng
API_HOSTNAME=internal-api.inboxi.ng
# ===========================================
# DEVELOPMENT OVERRIDES
# ===========================================
# For local development, uncomment these:
# SERVER_API_URL=http://localhost:3001/api
# API_BASE_URL=http://localhost:3001
# API_HOSTNAME=localhost

View File

@@ -249,17 +249,18 @@ export async function fetchClient<T>(
// Ensure the endpoint starts with a slash // Ensure the endpoint starts with a slash
const normalizedEndpoint = endpoint.startsWith('/') ? endpoint : `/${endpoint}`; const normalizedEndpoint = endpoint.startsWith('/') ? endpoint : `/${endpoint}`;
// For the specific case of internal-api.inboxi.ng - remove duplicate /api // Handle API endpoint construction without exposing internal domains
let url; let url;
if (apiUrl.includes('internal-api.inboxi.ng')) { // Check if this is a proxied API call (relative URLs starting with /api)
// Special case for internal-api.inboxi.ng if (apiUrl === '/api' || apiUrl.startsWith('/api')) {
// For proxied requests, use relative URLs
if (normalizedEndpoint.startsWith('/api/')) { if (normalizedEndpoint.startsWith('/api/')) {
url = `${apiUrl}${normalizedEndpoint.substring(4)}`; // Remove the /api part url = normalizedEndpoint; // Use as-is for proxied requests
} else { } else {
url = `${apiUrl}${normalizedEndpoint}`; url = `/api${normalizedEndpoint}`;
} }
} else { } else {
// Normal case for other environments // For direct API calls, construct full URL
url = `${apiUrl}${normalizedEndpoint}`; url = `${apiUrl}${normalizedEndpoint}`;
} }

View File

@@ -21,7 +21,7 @@ try {
* @returns A complete URL to the backend API * @returns A complete URL to the backend API
*/ */
function getServerApiUrl(endpoint: string): string { function getServerApiUrl(endpoint: string): string {
const apiUrl = process.env.SERVER_API_URL || 'https://internal-api.inboxi.ng/api'; const apiUrl = process.env.SERVER_API_URL || 'http://localhost:3001/api';
const cleanEndpoint = endpoint.startsWith('/') ? endpoint.substring(1) : endpoint; const cleanEndpoint = endpoint.startsWith('/') ? endpoint.substring(1) : endpoint;
return apiUrl.endsWith('/') return apiUrl.endsWith('/')

View File

@@ -16,22 +16,23 @@ const baseConfig = {
protocol: "https", protocol: "https",
hostname: "telegram.org", hostname: "telegram.org",
}, },
{ // Backend API hostname configured via environment variable
...(process.env.API_HOSTNAME ? [{
protocol: "https", protocol: "https",
hostname: "internal-api.inboxi.ng", hostname: process.env.API_HOSTNAME,
}, }] : []),
], ],
}, },
async rewrites() { async rewrites() {
return [ return [
{ {
source: '/api/:path*', source: '/api/:path*',
destination: 'https://internal-api.inboxi.ng/api/:path*', destination: `${process.env.API_BASE_URL || 'http://localhost:3001'}/api/:path*`,
}, },
]; ];
}, },
experimental: { experimental: {
serverExternalPackages: [], // serverExternalPackages has been deprecated in Next.js 15
}, },
onDemandEntries: { onDemandEntries: {
maxInactiveAge: 15 * 1000, maxInactiveAge: 15 * 1000,