diff --git a/frontend/components/CertificationUpload/CertificationCard.tsx b/frontend/components/CertificationUpload/CertificationCard.tsx
new file mode 100644
index 00000000..181225d1
--- /dev/null
+++ b/frontend/components/CertificationUpload/CertificationCard.tsx
@@ -0,0 +1,76 @@
+import {
+ CarrierCertification,
+} from "./certification.types";
+
+import {
+ CertificationStatusBadge,
+} from "./CertificationStatusBadge";
+
+interface Props {
+ certification:
+ CarrierCertification;
+
+ onDelete: (
+ id: string
+ ) => void;
+}
+
+export function CertificationCard({
+ certification,
+ onDelete,
+}: Props) {
+ const canDelete =
+ certification.status ===
+ "PENDING" ||
+ certification.status ===
+ "REJECTED";
+
+ return (
+
+
+
+
+ {certification.type}
+
+
+
+
+
+
+ Uploaded:
+ {" "}
+ {certification.uploadDate}
+
+
+ {certification.expiryDate && (
+
+ Expiry:
+ {" "}
+ {certification.expiryDate}
+
+ )}
+
+ {canDelete && (
+
+ )}
+
+ );
+}
\ No newline at end of file
diff --git a/frontend/components/CertificationUpload/CertificationList.tsx b/frontend/components/CertificationUpload/CertificationList.tsx
new file mode 100644
index 00000000..b5c5b2f1
--- /dev/null
+++ b/frontend/components/CertificationUpload/CertificationList.tsx
@@ -0,0 +1,39 @@
+import {
+ CarrierCertification,
+} from "./certification.types";
+
+import {
+ CertificationCard,
+} from "./CertificationCard";
+
+interface Props {
+ certifications:
+ CarrierCertification[];
+
+ onDelete: (
+ id: string
+ ) => void;
+}
+
+export function CertificationList({
+ certifications,
+ onDelete,
+}: Props) {
+ return (
+
+ {certifications.map(
+ (certification) => (
+
+ )
+ )}
+
+ );
+}
\ No newline at end of file
diff --git a/frontend/components/CertificationUpload/CertificationStatusBadge.tsx b/frontend/components/CertificationUpload/CertificationStatusBadge.tsx
new file mode 100644
index 00000000..6ad10d56
--- /dev/null
+++ b/frontend/components/CertificationUpload/CertificationStatusBadge.tsx
@@ -0,0 +1,35 @@
+interface Props {
+ status:
+ | "PENDING"
+ | "VERIFIED"
+ | "REJECTED";
+}
+
+export function CertificationStatusBadge({
+ status,
+}: Props) {
+ const styles = {
+ PENDING:
+ "bg-yellow-100 text-yellow-800",
+ VERIFIED:
+ "bg-green-100 text-green-800",
+ REJECTED:
+ "bg-red-100 text-red-800",
+ };
+
+ return (
+
+ {status}
+
+ );
+}
\ No newline at end of file
diff --git a/frontend/components/CertificationUpload/CertificationUploadForm.tsx b/frontend/components/CertificationUpload/CertificationUploadForm.tsx
new file mode 100644
index 00000000..a564bcb1
--- /dev/null
+++ b/frontend/components/CertificationUpload/CertificationUploadForm.tsx
@@ -0,0 +1,194 @@
+import { useState } from "react";
+
+import {
+ CERTIFICATION_OPTIONS,
+} from "./certification.constants";
+
+import {
+ validateCertificationFile,
+} from "./certification.validation";
+
+import {
+ uploadCertification,
+ deleteCertification,
+} from "../../services/certifications";
+
+import {
+ useCarrierCertifications,
+} from "../../hooks/useCarrierCertifications";
+
+import {
+ CertificationList,
+} from "./CertificationList";
+
+export function CertificationUploadForm() {
+ const {
+ certifications,
+ refresh,
+ } =
+ useCarrierCertifications();
+
+ const [type, setType] =
+ useState("");
+
+ const [file, setFile] =
+ useState(null);
+
+ const [expiryDate, setExpiryDate] =
+ useState("");
+
+ const [notes, setNotes] =
+ useState("");
+
+ const [error, setError] =
+ useState("");
+
+ async function handleSubmit(
+ e: React.FormEvent
+ ) {
+ e.preventDefault();
+
+ if (!file) {
+ setError(
+ "Please upload a PDF file."
+ );
+ return;
+ }
+
+ const validation =
+ validateCertificationFile(
+ file
+ );
+
+ if (validation) {
+ setError(validation);
+ return;
+ }
+
+ const formData =
+ new FormData();
+
+ formData.append("type", type);
+ formData.append("file", file);
+
+ if (expiryDate) {
+ formData.append(
+ "expiryDate",
+ expiryDate
+ );
+ }
+
+ if (notes) {
+ formData.append(
+ "notes",
+ notes
+ );
+ }
+
+ await uploadCertification(
+ formData
+ );
+
+ setType("");
+ setFile(null);
+ setExpiryDate("");
+ setNotes("");
+
+ await refresh();
+ }
+
+ async function handleDelete(
+ id: string
+ ) {
+ await deleteCertification(id);
+
+ await refresh();
+ }
+
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/frontend/components/CertificationUpload/certification.constants.ts b/frontend/components/CertificationUpload/certification.constants.ts
new file mode 100644
index 00000000..3fda9a15
--- /dev/null
+++ b/frontend/components/CertificationUpload/certification.constants.ts
@@ -0,0 +1,27 @@
+import { CertificationType } from "./certification.types";
+
+export const CERTIFICATION_OPTIONS: {
+ label: string;
+ value: CertificationType;
+}[] = [
+ {
+ label: "Operating License",
+ value: "OPERATING_LICENSE",
+ },
+ {
+ label: "Insurance Certificate",
+ value: "INSURANCE_CERTIFICATE",
+ },
+ {
+ label: "Safety Certification",
+ value: "SAFETY_CERTIFICATION",
+ },
+ {
+ label: "Hazmat License",
+ value: "HAZMAT_LICENSE",
+ },
+ {
+ label: "Vehicle Registration",
+ value: "VEHICLE_REGISTRATION",
+ },
+];
\ No newline at end of file
diff --git a/frontend/components/CertificationUpload/certification.types.ts b/frontend/components/CertificationUpload/certification.types.ts
new file mode 100644
index 00000000..aa6e8bcc
--- /dev/null
+++ b/frontend/components/CertificationUpload/certification.types.ts
@@ -0,0 +1,27 @@
+export type CertificationType =
+ | "OPERATING_LICENSE"
+ | "INSURANCE_CERTIFICATE"
+ | "SAFETY_CERTIFICATION"
+ | "HAZMAT_LICENSE"
+ | "VEHICLE_REGISTRATION";
+
+export type CertificationStatus =
+ | "PENDING"
+ | "VERIFIED"
+ | "REJECTED";
+
+export interface CarrierCertification {
+ id: string;
+
+ type: CertificationType;
+
+ fileName: string;
+
+ uploadDate: string;
+
+ expiryDate?: string;
+
+ notes?: string;
+
+ status: CertificationStatus;
+}
\ No newline at end of file
diff --git a/frontend/components/CertificationUpload/certification.validation.ts b/frontend/components/CertificationUpload/certification.validation.ts
new file mode 100644
index 00000000..dbee177d
--- /dev/null
+++ b/frontend/components/CertificationUpload/certification.validation.ts
@@ -0,0 +1,18 @@
+const MAX_FILE_SIZE =
+ 5 * 1024 * 1024;
+
+export function validateCertificationFile(
+ file: File
+) {
+ if (
+ file.type !== "application/pdf"
+ ) {
+ return "Only PDF files are allowed.";
+ }
+
+ if (file.size > MAX_FILE_SIZE) {
+ return "Maximum file size is 5MB.";
+ }
+
+ return null;
+}
\ No newline at end of file
diff --git a/frontend/components/CertificationUpload/certifications.ts b/frontend/components/CertificationUpload/certifications.ts
new file mode 100644
index 00000000..1dcd9084
--- /dev/null
+++ b/frontend/components/CertificationUpload/certifications.ts
@@ -0,0 +1,56 @@
+import {
+ CarrierCertification,
+} from "../components/CertificationUpload/certification.types";
+
+export async function uploadCertification(
+ formData: FormData
+) {
+ const response = await fetch(
+ "/api/carriers/certifications",
+ {
+ method: "POST",
+ body: formData,
+ }
+ );
+
+ if (!response.ok) {
+ throw new Error(
+ "Failed to upload certification"
+ );
+ }
+
+ return response.json();
+}
+
+export async function getCertifications() {
+ const response = await fetch(
+ "/api/carriers/certifications"
+ );
+
+ if (!response.ok) {
+ throw new Error(
+ "Failed to load certifications"
+ );
+ }
+
+ return response.json() as Promise<
+ CarrierCertification[]
+ >;
+}
+
+export async function deleteCertification(
+ certificationId: string
+) {
+ const response = await fetch(
+ `/api/carriers/certifications/${certificationId}`,
+ {
+ method: "DELETE",
+ }
+ );
+
+ if (!response.ok) {
+ throw new Error(
+ "Failed to delete certification"
+ );
+ }
+}
\ No newline at end of file
diff --git a/frontend/components/CertificationUpload/index.ts b/frontend/components/CertificationUpload/index.ts
new file mode 100644
index 00000000..cc26a682
--- /dev/null
+++ b/frontend/components/CertificationUpload/index.ts
@@ -0,0 +1,4 @@
+export * from "./CertificationUploadForm";
+export * from "./CertificationList";
+export * from "./CertificationCard";
+export * from "./CertificationStatusBadge";
\ No newline at end of file
diff --git a/frontend/hooks/useCarrierCertifications.ts b/frontend/hooks/useCarrierCertifications.ts
new file mode 100644
index 00000000..bb16a49e
--- /dev/null
+++ b/frontend/hooks/useCarrierCertifications.ts
@@ -0,0 +1,44 @@
+import { useEffect, useState } from "react";
+
+import {
+ getCertifications,
+} from "../services/certifications";
+
+import {
+ CarrierCertification,
+} from "../components/CertificationUpload/certification.types";
+
+export function useCarrierCertifications() {
+ const [
+ certifications,
+ setCertifications,
+ ] = useState<
+ CarrierCertification[]
+ >([]);
+
+ const [loading, setLoading] =
+ useState(true);
+
+ async function load() {
+ setLoading(true);
+
+ try {
+ const data =
+ await getCertifications();
+
+ setCertifications(data);
+ } finally {
+ setLoading(false);
+ }
+ }
+
+ useEffect(() => {
+ load();
+ }, []);
+
+ return {
+ certifications,
+ loading,
+ refresh: load,
+ };
+}
\ No newline at end of file