From a3dfc55481fdae5f0030ec72e8772642fcdbeb8f Mon Sep 17 00:00:00 2001 From: emdevelopa Date: Wed, 25 Feb 2026 19:22:14 +0000 Subject: [PATCH] feat: build reusable ConfirmDialog component --- .../freelancer/freelancer-dashboard.tsx | 23 +- components/milestone-modal.tsx | 208 + components/ui/ThemeToggle.tsx | 1 + components/ui/button.tsx | 50 +- components/ui/calendar.tsx | 220 + components/ui/dialog.tsx | 158 + components/ui/form.tsx | 167 + components/ui/input.tsx | 21 + components/ui/label.tsx | 24 + components/ui/popover.tsx | 89 + components/ui/textarea.tsx | 18 + package-lock.json | 9611 +++++++++++------ package.json | 9 +- 13 files changed, 6980 insertions(+), 3619 deletions(-) create mode 100644 components/milestone-modal.tsx create mode 100644 components/ui/calendar.tsx create mode 100644 components/ui/dialog.tsx create mode 100644 components/ui/form.tsx create mode 100644 components/ui/input.tsx create mode 100644 components/ui/label.tsx create mode 100644 components/ui/popover.tsx create mode 100644 components/ui/textarea.tsx diff --git a/components/freelancer/freelancer-dashboard.tsx b/components/freelancer/freelancer-dashboard.tsx index 20a0405..abe611f 100644 --- a/components/freelancer/freelancer-dashboard.tsx +++ b/components/freelancer/freelancer-dashboard.tsx @@ -9,6 +9,9 @@ import { type EscrowEntry, type FreelancerDashboardData, } from '@/lib/freelancer-dashboard' +import { MilestoneModal } from '@/components/milestone-modal' +import { Plus } from 'lucide-react' +import { Button } from '@/components/ui/button' function formatCurrency(value: number): string { return new Intl.NumberFormat('en-US', { @@ -153,11 +156,21 @@ export function FreelancerDashboard() { return (
-
-

Freelancer Dashboard

-

- Last updated: {formatDate(data.updatedAt)}. Data refreshes every 30 seconds. -

+
+
+

Freelancer Dashboard

+

+ Last updated: {formatDate(data.updatedAt)}. Data refreshes every 30 seconds. +

+
+ + + New Milestone + + } + />
diff --git a/components/milestone-modal.tsx b/components/milestone-modal.tsx new file mode 100644 index 0000000..aa5a8d1 --- /dev/null +++ b/components/milestone-modal.tsx @@ -0,0 +1,208 @@ +"use client" + +import * as React from "react" +import { zodResolver } from "@hookform/resolvers/zod" +import { useForm } from "react-hook-form" +import * as z from "zod" +import { format } from "date-fns" +import { CalendarIcon, Loader2 } from "lucide-react" + +import { cn } from "@/lib/utils" +import { Button } from "@/components/ui/button" +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog" +import { + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form" +import { Input } from "@/components/ui/input" +import { Textarea } from "@/components/ui/textarea" +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover" +import { Calendar } from "@/components/ui/calendar" + +const milestoneSchema = z.object({ + title: z.string().min(2, { + message: "Title must be at least 2 characters.", + }), + description: z.string().min(10, { + message: "Description must be at least 10 characters.", + }), + amount: z.coerce.number().positive({ + message: "Amount must be a positive number.", + }), + dueDate: z.date({ + required_error: "A due date is required.", + }).refine((date) => date > new Date(), { + message: "Due date must be in the future.", + }), +}) + +type MilestoneFormValues = z.infer + +interface MilestoneModalProps { + trigger?: React.ReactNode + projectId?: string + onSuccess?: (data: MilestoneFormValues) => void +} + +export function MilestoneModal({ trigger, projectId, onSuccess }: MilestoneModalProps) { + const [open, setOpen] = React.useState(false) + const [isSubmitting, setIsSubmitting] = React.useState(false) + + const form = useForm({ + resolver: zodResolver(milestoneSchema), + defaultValues: { + title: "", + description: "", + amount: 0, + }, + }) + + async function onSubmit(data: MilestoneFormValues) { + setIsSubmitting(true) + try { + // Simulate API call + console.log("Submitting milestone:", { ...data, projectId }) + await new Promise((resolve) => setTimeout(resolve, 1000)) + + onSuccess?.(data) + setOpen(false) + form.reset() + } catch (error) { + console.error("Failed to create milestone:", error) + } finally { + setIsSubmitting(false) + } + } + + return ( + + + {trigger || } + + + + Create Project Milestone + + Hold funds in escrow and release them once the work is completed. + + +
+ + ( + + Milestone Title + + + + + + )} + /> + ( + + Description + +