+ If something went wrong with a booking, let us know. We'll review
+ the case and help both parties reach a fair resolution.
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/DisputeForm.tsx b/src/components/DisputeForm.tsx
new file mode 100644
index 0000000..ca6faa3
--- /dev/null
+++ b/src/components/DisputeForm.tsx
@@ -0,0 +1,278 @@
+"use client";
+
+import { useState } from "react";
+import { useForm } from "react-hook-form";
+import { yupResolver } from "@hookform/resolvers/yup";
+import * as yup from "yup";
+import { HiExclamationCircle, HiCheckCircle } from "react-icons/hi";
+import Button from "@/components/ui/Button";
+import Card from "@/components/ui/Card";
+import Badge from "@/components/ui/Badge";
+import Input from "@/components/ui/Input";
+import Select from "@/components/ui/Select";
+import EvidenceDropzone from "@/components/EvidenceDropzone";
+
+/* ─── Schema ─────────────────────────────────────── */
+
+const DISPUTE_REASONS = [
+ { value: "service_not_completed", label: "Service not completed" },
+ { value: "poor_quality", label: "Poor quality of work" },
+ { value: "no_show", label: "Worker did not show up" },
+ { value: "incorrect_charges", label: "Incorrect charges / overpayment" },
+ { value: "damage_to_property", label: "Damage to property" },
+ { value: "misrepresentation", label: "Misrepresentation of service" },
+ { value: "other", label: "Other" },
+] as const;
+
+const schema = yup.object({
+ disputeReason: yup.string().required("Please select a reason for the dispute"),
+ description: yup
+ .string()
+ .min(20, "Please provide at least 20 characters of detail")
+ .max(5000, "Description may not exceed 5,000 characters")
+ .required("Please describe the issue in detail"),
+ desiredResolution: yup
+ .string()
+ .min(10, "Please describe your desired resolution")
+ .max(2000, "Resolution may not exceed 2,000 characters")
+ .required("Please state your desired resolution"),
+ appointmentId: yup.string().optional(),
+ contactEmail: yup
+ .string()
+ .email("Please enter a valid email address")
+ .required("A contact email is required so we can reach you"),
+});
+
+type DisputeFormValues = yup.InferType;
+
+/* ─── Evidence file type ──────────────────────────── */
+
+interface EvidenceFile {
+ file: File;
+ id: string;
+}
+
+let nextId = 1;
+function newEvidenceId(): string {
+ return `ev-${nextId++}-${Date.now()}`;
+}
+
+/* ─── Submission helper ───────────────────────────── */
+
+interface SubmitResult {
+ status: "idle" | "submitting" | "success" | "error";
+ message?: string;
+ referenceId?: string;
+}
+
+async function submitDispute(
+ _values: DisputeFormValues,
+ _evidence: EvidenceFile[],
+): Promise<{ referenceId: string }> {
+ // Simulate API call with a realistic delay.
+ // In production this would POST to /api/disputes with FormData.
+ await new Promise((resolve) => setTimeout(resolve, 1500));
+ return { referenceId: `DSP-${Date.now().toString(36).toUpperCase()}` };
+}
+
+/* ─── Component ───────────────────────────────────── */
+
+export default function DisputeForm() {
+ const {
+ register,
+ handleSubmit,
+ formState: { errors, isValid },
+ watch,
+ } = useForm({
+ mode: "onChange",
+ resolver: yupResolver(schema),
+ defaultValues: {
+ disputeReason: "",
+ description: "",
+ desiredResolution: "",
+ appointmentId: "",
+ contactEmail: "",
+ },
+ });
+
+ const [evidence, setEvidence] = useState([]);
+ const [submitResult, setSubmitResult] = useState({ status: "idle" });
+
+ const descriptionLength = watch("description")?.length ?? 0;
+
+ function handleAddEvidence(file: File) {
+ setEvidence((prev) => [...prev, { file, id: newEvidenceId() }]);
+ }
+
+ function handleRemoveEvidence(id: string) {
+ setEvidence((prev) => prev.filter((ef) => ef.id !== id));
+ }
+
+ async function onSubmit(values: DisputeFormValues) {
+ setSubmitResult({ status: "submitting" });
+ try {
+ const result = await submitDispute(values, evidence);
+ setSubmitResult({
+ status: "success",
+ referenceId: result.referenceId,
+ });
+ } catch (error) {
+ setSubmitResult({
+ status: "error",
+ message:
+ error instanceof Error
+ ? error.message
+ : "Something went wrong. Please try again.",
+ });
+ }
+ }
+
+ /* ─── Success state ──────────────────────────────── */
+ if (submitResult.status === "success") {
+ return (
+
+
+ Dispute filed
+
+
+
Dispute submitted
+
+ Your dispute has been received. Reference{" "}
+ {submitResult.referenceId}.
+ We'll review it and reach out to both parties within 1–2 business
+ days.
+