diff --git a/frontend/package/pages/CarrierProfile/CarrierProfilePage.tsx b/frontend/package/pages/CarrierProfile/CarrierProfilePage.tsx new file mode 100644 index 00000000..2f9be3a2 --- /dev/null +++ b/frontend/package/pages/CarrierProfile/CarrierProfilePage.tsx @@ -0,0 +1,323 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { useParams } from 'next/navigation'; +import Link from 'next/link'; +import { useQuery } from '@tanstack/react-query'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { Skeleton } from '@/components/ui/skeleton'; +import { apiClient } from '@/lib/api/client'; + +interface CarrierProfileData { + id: string; + firstName: string; + lastName: string; + email: string; + createdAt: string; + completedShipments: number; + averageRating: number; + totalReviews: number; + certifications: string[]; + bio?: string; + avatarUrl?: string; + onTimeRate?: number; + responseRate?: number; + totalEarnings?: number; +} + +interface Review { + id: string; + rating: number; + comment: string; + reviewerName: string; + createdAt: string; +} + +function StarRating({ rating }: { rating: number }) { + return ( +
+ {Array.from({ length: 5 }).map((_, i) => ( + + ))} + {rating.toFixed(1)} +
+ ); +} + +function ReputationBar({ score }: { score: number }) { + const percentage = Math.min((score / 5) * 100, 100); + const color = + score >= 4.5 ? 'bg-green-500' : score >= 3.5 ? 'bg-blue-500' : score >= 2.5 ? 'bg-amber-500' : 'bg-red-500'; + + return ( +
+
+ Reputation Score + {score.toFixed(1)} / 5.0 +
+
+
+
+
+ ); +} + +export function CarrierProfilePage() { + const { id } = useParams<{ id: string }>(); + + const { + data: carrier, + isLoading, + isError, + error, + refetch, + } = useQuery({ + queryKey: ['carrier-profile', id], + queryFn: () => apiClient(`/carriers/${id}`), + enabled: !!id, + }); + + const { + data: reviews, + isLoading: reviewsLoading, + } = useQuery({ + queryKey: ['carrier-reviews', id], + queryFn: () => apiClient(`/carriers/${id}/reviews`), + enabled: !!id, + }); + + if (isError) { + return ( +
+ + +

+ Failed to load carrier profile. +

+

+ {(error as Error)?.message || 'An unexpected error occurred.'} +

+ +
+
+
+ ); + } + + return ( +
+ {/* Profile Header */} + + + {isLoading ? ( +
+ +
+ + + +
+
+ ) : carrier ? ( +
+
+ {carrier.avatarUrl ? ( + {`${carrier.firstName} + ) : ( + `${carrier.firstName[0]}${carrier.lastName[0]}` + )} +
+
+

+ {carrier.firstName} {carrier.lastName} +

+

+ Member since{' '} + {new Date(carrier.createdAt).toLocaleDateString('en-US', { + month: 'long', + year: 'numeric', + })} +

+ {carrier.bio && ( +

{carrier.bio}

+ )} +
+ +
+ ) : null} +
+
+ + {/* Stats Grid */} +
+ {isLoading + ? Array.from({ length: 4 }).map((_, i) => ( + + + + + + + + + )) + : carrier && ( + <> + + + + Completed + + + +

{carrier.completedShipments}

+
+
+ + + + Rating + + + + + + + + + + On-Time + + + +

+ {carrier.onTimeRate != null ? `${carrier.onTimeRate}%` : '—'} +

+
+
+ + + + Response Rate + + + +

+ {carrier.responseRate != null ? `${carrier.responseRate}%` : '—'} +

+
+
+ + )} +
+ + {/* Reputation Score */} + {!isLoading && carrier && ( + + + + + + )} + + {/* Certifications */} + {!isLoading && carrier && carrier.certifications.length > 0 && ( + + + Certifications + + +
+ {carrier.certifications.map((cert) => ( + + {cert} + + ))} +
+
+
+ )} + + {/* Reviews */} + + + + Reviews {reviews && `(${reviews.length})`} + + + + {reviewsLoading ? ( +
+ {Array.from({ length: 3 }).map((_, i) => ( +
+ + + +
+ ))} +
+ ) : !reviews || reviews.length === 0 ? ( +

No reviews yet.

+ ) : ( +
+ {reviews.slice(0, 5).map((review) => ( +
+
+
+
+ {review.reviewerName[0]} +
+ {review.reviewerName} +
+ +
+ {review.comment && ( +

{review.comment}

+ )} +

+ {new Date(review.createdAt).toLocaleDateString('en-US', { + month: 'short', + day: 'numeric', + year: 'numeric', + })} +

+
+ ))} +
+ )} +
+
+
+ ); +} diff --git a/frontend/package/pages/CarrierProfile/index.ts b/frontend/package/pages/CarrierProfile/index.ts new file mode 100644 index 00000000..fbc55b1e --- /dev/null +++ b/frontend/package/pages/CarrierProfile/index.ts @@ -0,0 +1 @@ +export { CarrierProfilePage } from './CarrierProfilePage';