diff --git a/apps/edr-passenger-web/backoffice/src/app/agents/page.tsx b/apps/edr-passenger-web/backoffice/src/app/agents/page.tsx index c8213ee0a..d220bdd1a 100644 --- a/apps/edr-passenger-web/backoffice/src/app/agents/page.tsx +++ b/apps/edr-passenger-web/backoffice/src/app/agents/page.tsx @@ -53,13 +53,17 @@ export default function AgentsPage() { const actions = [ { label: 'View Shifts', - onClick: (agent: any) => window.location.href = `/agents/${agent.id}/shifts`, + onClick: (agent: any) => { + window.location.href = `/agents/${agent.id}/shifts`; + }, variant: 'secondary' as const, icon: Clock, }, { label: 'View Commissions', - onClick: (agent: any) => window.location.href = `/agents/${agent.id}/commissions`, + onClick: (agent: any) => { + window.location.href = `/agents/${agent.id}/commissions`; + }, variant: 'secondary' as const, icon: DollarSign, }, diff --git a/apps/edr-passenger-web/backoffice/src/app/audit/page.tsx b/apps/edr-passenger-web/backoffice/src/app/audit/page.tsx index 95fc248e9..6f73de13e 100644 --- a/apps/edr-passenger-web/backoffice/src/app/audit/page.tsx +++ b/apps/edr-passenger-web/backoffice/src/app/audit/page.tsx @@ -58,7 +58,9 @@ export default function AuditLogsPage() { const actions = [ { label: 'View Details', - onClick: (log: any) => window.location.href = `/audit/${log.id}`, + onClick: (log: any) => { + window.location.href = `/audit/${log.id}`; + }, variant: 'secondary' as const, icon: Eye, }, diff --git a/apps/edr-passenger-web/backoffice/src/app/coaches/page.tsx b/apps/edr-passenger-web/backoffice/src/app/coaches/page.tsx index 371fa0c96..8ae8dd642 100644 --- a/apps/edr-passenger-web/backoffice/src/app/coaches/page.tsx +++ b/apps/edr-passenger-web/backoffice/src/app/coaches/page.tsx @@ -193,7 +193,7 @@ export default function CoachesPage() { columns={columns} data={coaches} actions={actions} - isLoading={isLoading} + loading={isLoading} /> diff --git a/apps/edr-passenger-web/backoffice/src/app/loyalty/page.tsx b/apps/edr-passenger-web/backoffice/src/app/loyalty/page.tsx index 0e5938b4e..75d52f940 100644 --- a/apps/edr-passenger-web/backoffice/src/app/loyalty/page.tsx +++ b/apps/edr-passenger-web/backoffice/src/app/loyalty/page.tsx @@ -56,7 +56,7 @@ export default function LoyaltyPage() { formatDateTime(payment.createdAt) }, ]; + const actions: any[] = []; + return (
@@ -57,8 +59,9 @@ export default function PaymentsPage() {
diff --git a/apps/edr-passenger-web/backoffice/src/app/routes/page.tsx b/apps/edr-passenger-web/backoffice/src/app/routes/page.tsx index 1c67df1b2..c555cd114 100644 --- a/apps/edr-passenger-web/backoffice/src/app/routes/page.tsx +++ b/apps/edr-passenger-web/backoffice/src/app/routes/page.tsx @@ -239,7 +239,7 @@ export default function RoutesPage() {
{ const cleanParams = Object.fromEntries( Object.entries(params || {}).filter(([_, value]) => value !== '' && value !== undefined && value !== null) - ); + ) as Record; const query = new URLSearchParams(cleanParams).toString(); const response = await apiClient.get(`/bookings${query ? `?${query}` : ''}`); if (response?.data) { @@ -24,7 +24,7 @@ export const passengersApi = { getAll: async (params?: any) => { const cleanParams = Object.fromEntries( Object.entries(params || {}).filter(([_, value]) => value !== '' && value !== undefined && value !== null) - ); + ) as Record; const query = new URLSearchParams(cleanParams).toString(); const response = await apiClient.get(`/passengers${query ? `?${query}` : ''}`); if (response?.data) { @@ -40,7 +40,7 @@ export const stationsApi = { getAll: async (params?: any) => { const cleanParams = Object.fromEntries( Object.entries(params || {}).filter(([_, value]) => value !== '' && value !== undefined && value !== null) - ); + ) as Record; const query = new URLSearchParams(cleanParams).toString(); const response = await apiClient.get(`/stations${query ? `?${query}` : ''}`); // Handle wrapped response: { success, data: [...], timestamp } @@ -60,7 +60,7 @@ export const fleetApi = { getTrains: async (params?: any) => { const cleanParams = Object.fromEntries( Object.entries(params || {}).filter(([_, value]) => value !== '' && value !== undefined && value !== null) - ); + ) as Record; const query = new URLSearchParams(cleanParams).toString(); const response = await apiClient.get(`/fleet/trains${query ? `?${query}` : ''}`); if (response?.data) { @@ -71,7 +71,7 @@ export const fleetApi = { getCoaches: async (params?: any) => { const cleanParams = Object.fromEntries( Object.entries(params || {}).filter(([_, value]) => value !== '' && value !== undefined && value !== null) - ); + ) as Record; const query = new URLSearchParams(cleanParams).toString(); const response = await apiClient.get(`/fleet/coaches${query ? `?${query}` : ''}`); if (response?.data) { @@ -90,7 +90,7 @@ export const fleetApi = { // Schedules API export const schedulesApi = { getAll: async (params?: any) => { - const query = new URLSearchParams(params).toString(); + const query = new URLSearchParams(params as Record).toString(); const response = await apiClient.get(`/schedules${query ? `?${query}` : ''}`); if (response?.data) { return Array.isArray(response.data) ? { items: response.data } : response; @@ -125,7 +125,7 @@ export const seatsApi = { // Payments API export const paymentsApi = { getAll: async (params?: any) => { - const query = new URLSearchParams(params).toString(); + const query = new URLSearchParams(params as Record).toString(); const response = await apiClient.get(`/payments${query ? `?${query}` : ''}`); if (response?.data) { return Array.isArray(response.data) ? { items: response.data } : response; @@ -140,7 +140,7 @@ export const paymentsApi = { // Tickets API export const ticketsApi = { getAll: async (params?: any) => { - const query = new URLSearchParams(params).toString(); + const query = new URLSearchParams(params as Record).toString(); const response = await apiClient.get(`/tickets${query ? `?${query}` : ''}`); if (response?.data) { return Array.isArray(response.data) ? { items: response.data } : response; @@ -155,7 +155,7 @@ export const ticketsApi = { // Agents API export const agentsApi = { getAll: async (params?: any) => { - const query = new URLSearchParams(params).toString(); + const query = new URLSearchParams(params as Record).toString(); const response = await apiClient.get(`/agents${query ? `?${query}` : ''}`); if (response?.data) { return Array.isArray(response.data) ? { items: response.data } : response; @@ -174,7 +174,7 @@ export const agentsApi = { // Loyalty API export const loyaltyApi = { getAccounts: async (params?: any) => { - const query = new URLSearchParams(params).toString(); + const query = new URLSearchParams(params as Record).toString(); const response = await apiClient.get(`/loyalty/accounts${query ? `?${query}` : ''}`); if (response?.data) { return Array.isArray(response.data) ? { items: response.data } : response; @@ -190,7 +190,7 @@ export const loyaltyApi = { // Wallet API export const walletApi = { getAccounts: async (params?: any) => { - const query = new URLSearchParams(params).toString(); + const query = new URLSearchParams(params as Record).toString(); const response = await apiClient.get(`/wallet/accounts${query ? `?${query}` : ''}`); if (response?.data) { return Array.isArray(response.data) ? { items: response.data } : response; @@ -205,7 +205,7 @@ export const walletApi = { // Promotions API export const promotionsApi = { getAll: async (params?: any) => { - const query = new URLSearchParams(params).toString(); + const query = new URLSearchParams(params as Record).toString(); const response = await apiClient.get(`/promos${query ? `?${query}` : ''}`); if (response?.data) { return Array.isArray(response.data) ? { items: response.data } : response; @@ -221,7 +221,7 @@ export const promotionsApi = { // Support API export const supportApi = { getConversations: async (params?: any) => { - const query = new URLSearchParams(params).toString(); + const query = new URLSearchParams(params as Record).toString(); const response = await apiClient.get(`/support/conversations${query ? `?${query}` : ''}`); if (response?.data) { return Array.isArray(response.data) ? { items: response.data } : response; @@ -242,7 +242,7 @@ export const notificationsApi = { updateTemplate: (id: string, data: any) => apiClient.patch(`/notifications/templates/${id}`, data), send: (data: any) => apiClient.post('/notifications/send', data), getHistory: async (params?: any) => { - const query = new URLSearchParams(params).toString(); + const query = new URLSearchParams(params as Record).toString(); const response = await apiClient.get(`/notifications/history${query ? `?${query}` : ''}`); if (response?.data) { return Array.isArray(response.data) ? { items: response.data } : response; @@ -254,7 +254,7 @@ export const notificationsApi = { // Fraud API export const fraudApi = { getAlerts: async (params?: any) => { - const query = new URLSearchParams(params).toString(); + const query = new URLSearchParams(params as Record).toString(); const response = await apiClient.get(`/fraud/alerts${query ? `?${query}` : ''}`); if (response?.data) { return Array.isArray(response.data) ? { items: response.data } : response; @@ -270,7 +270,7 @@ export const fraudApi = { // Verifayda API export const verifaydaApi = { getVerifications: async (params?: any) => { - const query = new URLSearchParams(params).toString(); + const query = new URLSearchParams(params as Record).toString(); const response = await apiClient.get(`/passengers/verifications${query ? `?${query}` : ''}`); if (response?.data) { return Array.isArray(response.data) ? { items: response.data } : response; @@ -284,7 +284,7 @@ export const verifaydaApi = { // Audit API export const auditApi = { getLogs: async (params?: any) => { - const query = new URLSearchParams(params).toString(); + const query = new URLSearchParams(params as Record).toString(); const response = await apiClient.get(`/audit/logs${query ? `?${query}` : ''}`); if (response?.data) { return Array.isArray(response.data) ? { items: response.data } : response; @@ -316,7 +316,7 @@ export const foodApi = { getCategories: () => apiClient.get('/food/categories'), getMenuItems: (scheduleId: string) => apiClient.get(`/food/menu/${scheduleId}`), getOrders: async (params?: any) => { - const query = new URLSearchParams(params).toString(); + const query = new URLSearchParams(params as Record).toString(); const response = await apiClient.get(`/food/orders${query ? `?${query}` : ''}`); if (response?.data) { return Array.isArray(response.data) ? { items: response.data } : response; @@ -330,7 +330,7 @@ export const foodApi = { // Reports API export const reportsApi = { getOperationalReports: async (params?: any) => { - const query = new URLSearchParams(params).toString(); + const query = new URLSearchParams(params as Record).toString(); const response = await apiClient.get(`/reports/operational${query ? `?${query}` : ''}`); if (response?.data) { return Array.isArray(response.data) ? { items: response.data } : response; diff --git a/apps/edr-passenger-web/portal/src/app/booking/confirmation/page.tsx b/apps/edr-passenger-web/portal/src/app/booking/confirmation/page.tsx index 2302a814d..6bc0aa117 100644 --- a/apps/edr-passenger-web/portal/src/app/booking/confirmation/page.tsx +++ b/apps/edr-passenger-web/portal/src/app/booking/confirmation/page.tsx @@ -1,5 +1,7 @@ 'use client'; +export const dynamic = 'force-dynamic'; + import { useRouter } from 'next/navigation'; import { useBookingStore } from '@/lib/booking-store'; import { useMutation, useQuery } from '@tanstack/react-query'; diff --git a/apps/edr-passenger-web/portal/src/app/booking/passengers/page.tsx b/apps/edr-passenger-web/portal/src/app/booking/passengers/page.tsx index 61cff20e7..6eb667f4b 100644 --- a/apps/edr-passenger-web/portal/src/app/booking/passengers/page.tsx +++ b/apps/edr-passenger-web/portal/src/app/booking/passengers/page.tsx @@ -1,5 +1,7 @@ 'use client'; +export const dynamic = 'force-dynamic'; + import { useForm, useFieldArray } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; @@ -110,6 +112,8 @@ export default function PassengersPage() { }, [isAuthenticated, user, searchCriteria, setValue]); const openFaydaVerification = async (index: number) => { + if (typeof window === 'undefined') return; // Guard for SSR + const faydaUrl = process.env.NEXT_PUBLIC_FAYDA_URL || 'https://fayda.gov.et/verify'; const callbackUrl = `${window.location.origin}/booking/passengers?faydaCallback=${index}`; const width = 600; @@ -185,10 +189,14 @@ export default function PassengersPage() { })); // Save passenger details to database before proceeding + const deviceId = typeof window !== 'undefined' + ? (localStorage.getItem('deviceId') || crypto.randomUUID()) + : crypto.randomUUID(); + await apiClient.post('/passengers/save-details', { passengers: passengerDetails, userId: user?.id, - deviceId: localStorage.getItem('deviceId') || crypto.randomUUID(), + deviceId, }); setPassengers(passengerDetails); diff --git a/apps/edr-passenger-web/portal/src/app/booking/results/layout.tsx b/apps/edr-passenger-web/portal/src/app/booking/results/layout.tsx new file mode 100644 index 000000000..c5236d89c --- /dev/null +++ b/apps/edr-passenger-web/portal/src/app/booking/results/layout.tsx @@ -0,0 +1,5 @@ +import { Suspense } from 'react'; + +export default function ResultsLayout({ children }: { children: React.ReactNode }) { + return

Loading...

}>{children}
; +} diff --git a/apps/edr-passenger-web/portal/src/app/booking/review/page.tsx b/apps/edr-passenger-web/portal/src/app/booking/review/page.tsx index 84e3d9585..036cb5e36 100644 --- a/apps/edr-passenger-web/portal/src/app/booking/review/page.tsx +++ b/apps/edr-passenger-web/portal/src/app/booking/review/page.tsx @@ -174,8 +174,8 @@ export default function ReviewPage() { }; // Save deviceId for future use - if (typeof window !== 'undefined' && !localStorage.getItem('deviceId')) { - localStorage.setItem('deviceId', bookingData.deviceId!); + if (typeof window !== 'undefined' && bookingData.deviceId && !localStorage.getItem('deviceId')) { + localStorage.setItem('deviceId', bookingData.deviceId); } console.log('Creating booking with payload:', bookingData); diff --git a/apps/edr-passenger-web/portal/src/app/booking/search/layout.tsx b/apps/edr-passenger-web/portal/src/app/booking/search/layout.tsx new file mode 100644 index 000000000..69d70e85d --- /dev/null +++ b/apps/edr-passenger-web/portal/src/app/booking/search/layout.tsx @@ -0,0 +1,5 @@ +import { Suspense } from 'react'; + +export default function SearchLayout({ children }: { children: React.ReactNode }) { + return

Loading...

}>{children}
; +} diff --git a/apps/edr-passenger-web/portal/src/app/booking/seats/page.tsx b/apps/edr-passenger-web/portal/src/app/booking/seats/page.tsx index df4866eb2..124fb39f1 100644 --- a/apps/edr-passenger-web/portal/src/app/booking/seats/page.tsx +++ b/apps/edr-passenger-web/portal/src/app/booking/seats/page.tsx @@ -1,5 +1,7 @@ 'use client'; +export const dynamic = 'force-dynamic'; + import { useRouter } from 'next/navigation'; import { useBookingStore } from '@/lib/booking-store'; import { useQuery, useMutation } from '@tanstack/react-query'; diff --git a/apps/edr-passenger-web/portal/src/app/login/page.tsx b/apps/edr-passenger-web/portal/src/app/login/page.tsx index 8eacd9c10..fecbd7ae6 100644 --- a/apps/edr-passenger-web/portal/src/app/login/page.tsx +++ b/apps/edr-passenger-web/portal/src/app/login/page.tsx @@ -6,7 +6,7 @@ import { z } from 'zod'; import { useRouter, useSearchParams } from 'next/navigation'; import { useAuthStore } from '@/lib/auth-store'; import { useBookingStore } from '@/lib/booking-store'; -import { useState } from 'react'; +import { useState, Suspense } from 'react'; import { Train } from 'lucide-react'; const loginSchema = z.object({ @@ -16,7 +16,7 @@ const loginSchema = z.object({ type LoginForm = z.infer; -export default function LoginPage() { +function LoginContent() { const router = useRouter(); const searchParams = useSearchParams(); const login = useAuthStore((s) => s.login); @@ -110,3 +110,18 @@ export default function LoginPage() { ); } + +export default function LoginPage() { + return ( + +
+ +

Loading...

+
+ + }> + +
+ ); +} diff --git a/apps/edr-passenger-web/portal/src/lib/auth-store.ts b/apps/edr-passenger-web/portal/src/lib/auth-store.ts index d304e56bb..bb3cd27b7 100644 --- a/apps/edr-passenger-web/portal/src/lib/auth-store.ts +++ b/apps/edr-passenger-web/portal/src/lib/auth-store.ts @@ -67,6 +67,7 @@ export const useAuthStore = create((set, get) => ({ }, fetchProfile: async () => { + if (typeof window === 'undefined') return; const token = localStorage.getItem('auth_token'); if (!token) return; @@ -76,13 +77,17 @@ export const useAuthStore = create((set, get) => ({ }); const userData = response.data || response; - localStorage.setItem('auth_user', JSON.stringify(userData)); + if (typeof window !== 'undefined') { + localStorage.setItem('auth_user', JSON.stringify(userData)); + } set({ user: userData }); } catch (error: any) { // If 401, token is invalid - logout if (error.response?.status === 401) { - localStorage.removeItem('auth_token'); - localStorage.removeItem('auth_user'); + if (typeof window !== 'undefined') { + localStorage.removeItem('auth_token'); + localStorage.removeItem('auth_user'); + } set({ user: null, token: null, isAuthenticated: false }); } throw error; @@ -93,8 +98,10 @@ export const useAuthStore = create((set, get) => ({ const response: any = await apiClient.post('/auth/login', { email, password }); const { token, user } = response.data || response; - localStorage.setItem('auth_token', token); - localStorage.setItem('auth_user', JSON.stringify(user)); + if (typeof window !== 'undefined') { + localStorage.setItem('auth_token', token); + localStorage.setItem('auth_user', JSON.stringify(user)); + } set({ user, token, isAuthenticated: true }); }, @@ -103,15 +110,17 @@ export const useAuthStore = create((set, get) => ({ const response: any = await apiClient.post('/auth/register', data); const { token, user } = response.data || response; - localStorage.setItem('auth_token', token); - localStorage.setItem('auth_user', JSON.stringify(user)); + if (typeof window !== 'undefined') { + localStorage.setItem('auth_token', token); + localStorage.setItem('auth_user', JSON.stringify(user)); + } set({ user, token, isAuthenticated: true }); }, logout: async () => { try { - const token = localStorage.getItem('auth_token'); + const token = typeof window !== 'undefined' ? localStorage.getItem('auth_token') : null; if (token) { // Call logout endpoint to invalidate session on backend await apiClient.post('/auth/logout', {}, { @@ -126,8 +135,10 @@ export const useAuthStore = create((set, get) => ({ } // Clear local storage and state - localStorage.removeItem('auth_token'); - localStorage.removeItem('auth_user'); + if (typeof window !== 'undefined') { + localStorage.removeItem('auth_token'); + localStorage.removeItem('auth_user'); + } set({ user: null, token: null, isAuthenticated: false }); // Redirect to home page after state is updated @@ -139,16 +150,20 @@ export const useAuthStore = create((set, get) => ({ }, setUser: (user: User, token: string) => { - localStorage.setItem('auth_token', token); - localStorage.setItem('auth_user', JSON.stringify(user)); + if (typeof window !== 'undefined') { + localStorage.setItem('auth_token', token); + localStorage.setItem('auth_user', JSON.stringify(user)); + } set({ user, token, isAuthenticated: true }); }, updateUser: (userData: Partial) => { - const currentUser = JSON.parse(localStorage.getItem('auth_user') || '{}'); - const updatedUser = { ...currentUser, ...userData }; - localStorage.setItem('auth_user', JSON.stringify(updatedUser)); - set({ user: updatedUser }); + if (typeof window !== 'undefined') { + const currentUser = JSON.parse(localStorage.getItem('auth_user') || '{}'); + const updatedUser = { ...currentUser, ...userData }; + localStorage.setItem('auth_user', JSON.stringify(updatedUser)); + set({ user: updatedUser }); + } }, }) ); diff --git a/apps/edr-passenger-web/portal/src/lib/booking-store.ts b/apps/edr-passenger-web/portal/src/lib/booking-store.ts index b759c9145..7ddb09983 100644 --- a/apps/edr-passenger-web/portal/src/lib/booking-store.ts +++ b/apps/edr-passenger-web/portal/src/lib/booking-store.ts @@ -102,6 +102,15 @@ export const useBookingStore = create()(persist( }), { name: 'booking-storage', - storage: createJSONStorage(() => localStorage), + storage: createJSONStorage(() => { + if (typeof window !== 'undefined') { + return localStorage; + } + return { + getItem: () => null, + setItem: () => {}, + removeItem: () => {}, + }; + }), } )); diff --git a/docker-compose.yaml b/docker-compose.yaml index 2438b70fa..c767b57f2 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -61,6 +61,8 @@ services: TURBO_FILTER: "@edr/passenger-portal" APP_PATH: apps/edr-passenger-web/portal VITE_API_URL: ${PASSENGER_VITE_API_URL:-http://localhost:4000} + secrets: + - npmrc ports: - "${PASSENGER_PORTAL_PORT:-5174}:80" @@ -72,6 +74,8 @@ services: TURBO_FILTER: "@edr/passenger-backoffice" APP_PATH: apps/edr-passenger-web/backoffice VITE_API_URL: ${PASSENGER_VITE_API_URL:-http://localhost:4000} + secrets: + - npmrc ports: - "${PASSENGER_BACKOFFICE_PORT:-5184}:80"