Merge pull request #76 from Tria-plc/alpha

Build issues resolution
This commit is contained in:
Stephanos A.
2026-06-02 22:35:49 +03:00
committed by GitHub
20 changed files with 125 additions and 51 deletions

View File

@@ -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,
},

View File

@@ -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,
},

View File

@@ -193,7 +193,7 @@ export default function CoachesPage() {
columns={columns}
data={coaches}
actions={actions}
isLoading={isLoading}
loading={isLoading}
/>
</div>

View File

@@ -56,7 +56,7 @@ export default function LoyaltyPage() {
</div>
<DataTable
data={data?.items || data || []}
data={Array.isArray(data) ? data : (data?.items || [])}
columns={columns}
loading={isLoading}
emptyMessage="No loyalty program found"

View File

@@ -65,7 +65,7 @@ export default function PassengersPage() {
},
];
const actions = [
const actions: any[] = [
// TODO: Create passenger detail page
// {
// label: 'View Details',

View File

@@ -26,6 +26,8 @@ export default function PaymentsPage() {
{ key: 'createdAt', label: 'Created', render: (payment: any) => formatDateTime(payment.createdAt) },
];
const actions: any[] = [];
return (
<div className="space-y-6">
<div className="flex items-center justify-between">
@@ -57,8 +59,9 @@ export default function PaymentsPage() {
</div>
<DataTable
data={data?.items || data || []}
data={(data as any)?.items || (Array.isArray(data) ? data : [])}
columns={columns}
actions={actions}
loading={isLoading}
emptyMessage="No payments found"
/>

View File

@@ -239,7 +239,7 @@ export default function RoutesPage() {
</div>
<DataTable
data={routes?.items || routes || []}
data={(routes as any)?.items || (Array.isArray(routes) ? routes : [])}
columns={routeColumns}
actions={routeActions}
loading={routesLoading}

View File

@@ -264,7 +264,7 @@ export default function SchedulesPage() {
</div>
<DataTable
data={data?.items || data || []}
data={(data as any)?.items || (Array.isArray(data) ? data : [])}
columns={columns}
actions={actions}
loading={isLoading}

View File

@@ -125,7 +125,7 @@ export default function SeatClassesPage() {
</div>
<DataTable
data={data?.items || data || []}
data={(data as any)?.items || (Array.isArray(data) ? data : [])}
columns={columns}
actions={actions}
loading={isLoading}

View File

@@ -6,7 +6,7 @@ export const bookingsApi = {
getAll: async (params?: any) => {
const cleanParams = Object.fromEntries(
Object.entries(params || {}).filter(([_, value]) => value !== '' && value !== undefined && value !== null)
);
) as Record<string, string>;
const query = new URLSearchParams(cleanParams).toString();
const response = await apiClient.get<any>(`/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<string, string>;
const query = new URLSearchParams(cleanParams).toString();
const response = await apiClient.get<any>(`/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<string, string>;
const query = new URLSearchParams(cleanParams).toString();
const response = await apiClient.get<any>(`/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<string, string>;
const query = new URLSearchParams(cleanParams).toString();
const response = await apiClient.get<any>(`/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<string, string>;
const query = new URLSearchParams(cleanParams).toString();
const response = await apiClient.get<any>(`/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<string, string>).toString();
const response = await apiClient.get<any>(`/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<string, string>).toString();
const response = await apiClient.get<any>(`/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<string, string>).toString();
const response = await apiClient.get<any>(`/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<string, string>).toString();
const response = await apiClient.get<any>(`/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<string, string>).toString();
const response = await apiClient.get<any>(`/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<string, string>).toString();
const response = await apiClient.get<any>(`/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<string, string>).toString();
const response = await apiClient.get<any>(`/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<string, string>).toString();
const response = await apiClient.get<any>(`/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<any>(`/notifications/templates/${id}`, data),
send: (data: any) => apiClient.post<any>('/notifications/send', data),
getHistory: async (params?: any) => {
const query = new URLSearchParams(params).toString();
const query = new URLSearchParams(params as Record<string, string>).toString();
const response = await apiClient.get<any>(`/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<string, string>).toString();
const response = await apiClient.get<any>(`/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<string, string>).toString();
const response = await apiClient.get<any>(`/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<string, string>).toString();
const response = await apiClient.get<any>(`/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<any[]>('/food/categories'),
getMenuItems: (scheduleId: string) => apiClient.get<any[]>(`/food/menu/${scheduleId}`),
getOrders: async (params?: any) => {
const query = new URLSearchParams(params).toString();
const query = new URLSearchParams(params as Record<string, string>).toString();
const response = await apiClient.get<any>(`/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<string, string>).toString();
const response = await apiClient.get<any>(`/reports/operational${query ? `?${query}` : ''}`);
if (response?.data) {
return Array.isArray(response.data) ? { items: response.data } : response;

View File

@@ -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';

View File

@@ -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);

View File

@@ -0,0 +1,5 @@
import { Suspense } from 'react';
export default function ResultsLayout({ children }: { children: React.ReactNode }) {
return <Suspense fallback={<div className="min-h-screen bg-gray-50 dark:bg-gray-900 flex items-center justify-center"><p className="text-gray-600 dark:text-gray-400">Loading...</p></div>}>{children}</Suspense>;
}

View File

@@ -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);

View File

@@ -0,0 +1,5 @@
import { Suspense } from 'react';
export default function SearchLayout({ children }: { children: React.ReactNode }) {
return <Suspense fallback={<div className="min-h-screen bg-gray-50 dark:bg-gray-900 flex items-center justify-center"><p className="text-gray-600 dark:text-gray-400">Loading...</p></div>}>{children}</Suspense>;
}

View File

@@ -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';

View File

@@ -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<typeof loginSchema>;
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() {
</div>
);
}
export default function LoginPage() {
return (
<Suspense fallback={
<div className="min-h-screen bg-gradient-to-br from-primary-50 to-primary-100 dark:from-gray-900 dark:to-gray-800 flex items-center justify-center">
<div className="text-center">
<Train className="w-12 h-12 text-primary animate-pulse mx-auto mb-4" />
<p className="text-gray-600 dark:text-gray-400">Loading...</p>
</div>
</div>
}>
<LoginContent />
</Suspense>
);
}

View File

@@ -67,6 +67,7 @@ export const useAuthStore = create<AuthState>((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<AuthState>((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<AuthState>((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<AuthState>((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<AuthState>((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<AuthState>((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<User>) => {
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 });
}
},
})
);

View File

@@ -102,6 +102,15 @@ export const useBookingStore = create<BookingState>()(persist(
}),
{
name: 'booking-storage',
storage: createJSONStorage(() => localStorage),
storage: createJSONStorage(() => {
if (typeof window !== 'undefined') {
return localStorage;
}
return {
getItem: () => null,
setItem: () => {},
removeItem: () => {},
};
}),
}
));

View File

@@ -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"