diff --git a/apps/web/app/jobs/[id]/page.tsx b/apps/web/app/jobs/[id]/page.tsx index 1c845359..7024a7e2 100644 --- a/apps/web/app/jobs/[id]/page.tsx +++ b/apps/web/app/jobs/[id]/page.tsx @@ -40,6 +40,7 @@ export default function JobDetailsPage() { const [deliverableLink, setDeliverableLink] = useState(""); const [deliverableFile, setDeliverableFile] = useState(null); const [busyAction, setBusyAction] = useState(null); + const [proposal, setProposal] = useState(null); useEffect(() => { void getConnectedWalletAddress().then(setViewerAddress); @@ -52,6 +53,9 @@ export default function JobDetailsPage() { return connected; } + + + async function handleSubmitDeliverable(event: React.FormEvent) { event.preventDefault(); if (!workspace.job) return; @@ -232,8 +236,9 @@ export default function JobDetailsPage() { - ) : null} + ) :
p
} +
{job.status === "open" ? (
@@ -328,7 +333,7 @@ export default function JobDetailsPage() { Boolean(viewerAddress) && viewerAddress === job.client_address } - workflowLocked={workflowLocked} + workflowLocked ={workflowLocked} busyMilestoneId={ busyAction?.startsWith("release-") ? busyAction.replace("release-", "") @@ -388,13 +393,17 @@ export default function JobDetailsPage() { {deliverableFile ? deliverableFile.name : "Upload ZIP, image, JSON, or PDF evidence"} + { + const file = event.target.files?.[0]; + if (file) setDeliverableFile(file); + }} + className="hidden" + id="deliverable-file" + /> - ) : ( -
- -

Deliverables are locked during dispute resolution

-
- )} + ) : null}
) : null} diff --git a/apps/web/components/jobs/accept-bid-modal.tsx b/apps/web/components/jobs/accept-bid-modal.tsx index 77295458..99561af1 100644 --- a/apps/web/components/jobs/accept-bid-modal.tsx +++ b/apps/web/components/jobs/accept-bid-modal.tsx @@ -1,7 +1,7 @@ "use client"; -import { LoaderCircle } from "lucide-react"; import { type Bid, type Job } from "@/lib/api"; +import { formatUsdc, shortenAddress } from "@/lib/format"; interface AcceptBidModalProps { isOpen: boolean; @@ -36,66 +36,76 @@ export function AcceptBidModal({ className="w-full max-w-2xl rounded-[1.75rem] border border-white/10 bg-zinc-950/95 p-6 shadow-2xl" onClick={(event) => event.stopPropagation()} > -
+
-

- Review & Accept -

-

+

Accept Freelancer Bid -

-

- Review the freelancer's proposal and budget. Once accepted, you'll need to fund the job. + +

+ Review the freelancer's proposal and accept their bid to start the contract.

+
-
-
- Freelancer Address - {bid.freelancer_address} -
- -
- Proposal -

{bid.proposal}

+
+
+
+

Freelancer

+

+ {shortenAddress(bid.freelancer_address)} +

+
+
+

Bid Amount

+

+ {formatUsdc(job.budget_usdc)} +

+
+
-
- Job Budget - - ${(job.budget_usdc / 10_000_000).toLocaleString()} USDC - -
+
+

Proposal

+

+ {bid.proposal} +

-
- - +
+

Contract Details

+
+

• Contract Value: {formatUsdc(job.budget_usdc)}

+

• Milestones: {job.milestones}

+

• Client: {shortenAddress(job.client_address)}

+
+ +
+ + +
); diff --git a/apps/web/components/jobs/bid-list.tsx b/apps/web/components/jobs/bid-list.tsx index ea52bdb6..3ff0a43b 100644 --- a/apps/web/components/jobs/bid-list.tsx +++ b/apps/web/components/jobs/bid-list.tsx @@ -197,6 +197,36 @@ export function BidList({ )} + {/* Accept action */} + {canAccept && !isAccepted && ( +
+ +
+ )} + + {isAccepted && ( +

+

+ )} + {/* Accept action */} {canAccept && !isAccepted && (
diff --git a/apps/web/components/jobs/job-card.tsx b/apps/web/components/jobs/job-card.tsx index 8db94792..78e30aba 100644 --- a/apps/web/components/jobs/job-card.tsx +++ b/apps/web/components/jobs/job-card.tsx @@ -115,8 +115,6 @@ function StarRating({ rating }: { rating: number }) { } export function JobCard({ job }: JobCardProps) { - const statusConfig = STATUS_CONFIG[job.status] || STATUS_CONFIG.open; - return (
| string; + details: unknown; created_at: string; } -export interface AuthChallengeResponse { - address: string; - challenge: string; -} - -export interface AuthVerifyResponse { - address: string; - token: string; -} - diff --git a/apps/web/lib/job-registry.test.ts b/apps/web/lib/job-registry.test.ts index c59d4bce..4b73eee1 100644 --- a/apps/web/lib/job-registry.test.ts +++ b/apps/web/lib/job-registry.test.ts @@ -22,9 +22,9 @@ beforeEach(() => { describe("acceptBid", () => { it("should throw error when clientAddress is missing", async () => { const params: AcceptBidParams = { - jobId: 1n, + jobId: BigInt(1), clientAddress: "", - bidId: 1n, + freelancerAddress: "GD...", }; await expect(acceptBid(params)).rejects.toThrow( @@ -36,7 +36,7 @@ describe("acceptBid", () => { const params: AcceptBidParams = { jobId: 0n, clientAddress: "GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", - bidId: 1n, + freelancerAddress: "GD...", }; await expect(acceptBid(params)).rejects.toThrow( @@ -44,15 +44,15 @@ describe("acceptBid", () => { ); }); - it("should throw error when bidId is zero or negative", async () => { + it("should throw error when freelancerAddress is missing", async () => { const params: AcceptBidParams = { jobId: 1n, clientAddress: "GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", - bidId: 0n, + freelancerAddress: "", }; await expect(acceptBid(params)).rejects.toThrow( - "bidId must be greater than zero." + "freelancerAddress is required." ); }); @@ -62,7 +62,7 @@ describe("acceptBid", () => { const params: AcceptBidParams = { jobId: 1n, clientAddress: "GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", - bidId: 1n, + freelancerAddress: "GD...", }; const lifecycleSteps: string[] = []; @@ -91,7 +91,7 @@ describe("acceptBid", () => { const params: AcceptBidParams = { jobId: 1n, clientAddress: "GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", - bidId: 1n, + freelancerAddress: "GD...", }; await expect(acceptBid(params)).rejects.toThrow( @@ -105,7 +105,7 @@ describe("acceptBid", () => { const params: AcceptBidParams = { jobId: 123n, clientAddress: "GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", - bidId: 456n, + freelancerAddress: "GD...", }; const steps: string[] = [];