Create reusable Contracts widget for marketplace
cgen-a66166315fa44c029eb7f030b503f035
This commit is contained in:
parent
5cee4908df
commit
72eb16a4f6
1 changed files with 194 additions and 0 deletions
194
client/components/ContractsWidget.tsx
Normal file
194
client/components/ContractsWidget.tsx
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { FileText, AlertCircle, CheckCircle, Clock, DollarSign } from "lucide-react";
|
||||
|
||||
export interface Contract {
|
||||
id: string;
|
||||
title: string;
|
||||
client_name?: string;
|
||||
creator_name?: string;
|
||||
status: "active" | "completed" | "paused" | "cancelled";
|
||||
total_amount: number;
|
||||
paid_amount?: number;
|
||||
start_date?: string;
|
||||
end_date?: string;
|
||||
description?: string;
|
||||
milestones?: Milestone[];
|
||||
}
|
||||
|
||||
export interface Milestone {
|
||||
id: string;
|
||||
description: string;
|
||||
amount: number;
|
||||
status: "pending" | "approved" | "paid";
|
||||
}
|
||||
|
||||
interface ContractsWidgetProps {
|
||||
contracts: Contract[];
|
||||
title?: string;
|
||||
description?: string;
|
||||
type?: "creator" | "client";
|
||||
accentColor?: "purple" | "blue" | "cyan" | "green" | "amber" | "red";
|
||||
}
|
||||
|
||||
const colorMap = {
|
||||
purple: {
|
||||
bg: "bg-gradient-to-br from-purple-950/40 to-purple-900/20",
|
||||
border: "border-purple-500/20",
|
||||
text: "text-purple-300",
|
||||
},
|
||||
blue: {
|
||||
bg: "bg-gradient-to-br from-blue-950/40 to-blue-900/20",
|
||||
border: "border-blue-500/20",
|
||||
text: "text-blue-300",
|
||||
},
|
||||
cyan: {
|
||||
bg: "bg-gradient-to-br from-cyan-950/40 to-cyan-900/20",
|
||||
border: "border-cyan-500/20",
|
||||
text: "text-cyan-300",
|
||||
},
|
||||
green: {
|
||||
bg: "bg-gradient-to-br from-green-950/40 to-green-900/20",
|
||||
border: "border-green-500/20",
|
||||
text: "text-green-300",
|
||||
},
|
||||
amber: {
|
||||
bg: "bg-gradient-to-br from-amber-950/40 to-amber-900/20",
|
||||
border: "border-amber-500/20",
|
||||
text: "text-amber-300",
|
||||
},
|
||||
red: {
|
||||
bg: "bg-gradient-to-br from-red-950/40 to-red-900/20",
|
||||
border: "border-red-500/20",
|
||||
text: "text-red-300",
|
||||
},
|
||||
};
|
||||
|
||||
const statusMap = {
|
||||
active: { color: "bg-green-600/50 text-green-100", icon: CheckCircle },
|
||||
completed: { color: "bg-blue-600/50 text-blue-100", icon: CheckCircle },
|
||||
paused: { color: "bg-yellow-600/50 text-yellow-100", icon: Clock },
|
||||
cancelled: { color: "bg-red-600/50 text-red-100", icon: AlertCircle },
|
||||
};
|
||||
|
||||
export function ContractsWidget({
|
||||
contracts,
|
||||
title = "My Contracts",
|
||||
description = "Active and completed projects",
|
||||
type = "creator",
|
||||
accentColor = "purple",
|
||||
}: ContractsWidgetProps) {
|
||||
const colors = colorMap[accentColor];
|
||||
const activeContracts = contracts.filter(c => c.status === "active");
|
||||
|
||||
return (
|
||||
<Card className={`${colors.bg} border ${colors.border}`}>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<FileText className="h-5 w-5" />
|
||||
{title}
|
||||
</CardTitle>
|
||||
<CardDescription>{description}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{contracts.length === 0 ? (
|
||||
<div className="text-center py-12">
|
||||
<FileText className="h-12 w-12 mx-auto text-gray-500 opacity-50 mb-4" />
|
||||
<p className="text-gray-400">No contracts yet</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
{contracts.map((contract) => {
|
||||
const StatusIcon = statusMap[contract.status].icon;
|
||||
const progress = contract.paid_amount && contract.total_amount
|
||||
? Math.round((contract.paid_amount / contract.total_amount) * 100)
|
||||
: 0;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={contract.id}
|
||||
className="p-4 bg-black/30 rounded-lg border border-gray-500/10 hover:border-gray-500/30 transition space-y-4"
|
||||
>
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div className="flex-1 min-w-0">
|
||||
<h4 className="font-semibold text-white truncate">
|
||||
{contract.title}
|
||||
</h4>
|
||||
<p className="text-sm text-gray-400 mt-1">
|
||||
{type === "creator" ? (
|
||||
<>with {contract.client_name || "Client"}</>
|
||||
) : (
|
||||
<>with {contract.creator_name || "Creator"}</>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<Badge className={statusMap[contract.status].color}>
|
||||
{contract.status}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
{contract.description && (
|
||||
<p className="text-sm text-gray-400">{contract.description}</p>
|
||||
)}
|
||||
|
||||
<div className="grid grid-cols-2 gap-4 text-sm">
|
||||
<div>
|
||||
<p className="text-gray-400">Total Value</p>
|
||||
<p className="font-semibold text-white">
|
||||
${contract.total_amount.toLocaleString()}
|
||||
</p>
|
||||
</div>
|
||||
{contract.paid_amount !== undefined && (
|
||||
<div>
|
||||
<p className="text-gray-400">Paid</p>
|
||||
<p className="font-semibold text-green-400">
|
||||
${contract.paid_amount.toLocaleString()}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{contract.milestones && contract.milestones.length > 0 && (
|
||||
<div className="space-y-2 pt-2 border-t border-gray-500/10">
|
||||
<p className="text-xs font-semibold text-gray-300 uppercase">Milestones</p>
|
||||
<div className="space-y-2">
|
||||
{contract.milestones.map((m) => (
|
||||
<div
|
||||
key={m.id}
|
||||
className="flex items-center justify-between gap-2 text-xs"
|
||||
>
|
||||
<div className="flex items-center gap-2 flex-1 min-w-0">
|
||||
<div
|
||||
className="h-2 w-2 rounded-full flex-shrink-0"
|
||||
style={{
|
||||
backgroundColor:
|
||||
m.status === "paid"
|
||||
? "#22c55e"
|
||||
: m.status === "approved"
|
||||
? "#3b82f6"
|
||||
: "#666",
|
||||
}}
|
||||
/>
|
||||
<span className="text-gray-300 truncate">
|
||||
{m.description}
|
||||
</span>
|
||||
</div>
|
||||
<span className="text-gray-400 flex-shrink-0">
|
||||
${m.amount.toLocaleString()}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export default ContractsWidget;
|
||||
Loading…
Reference in a new issue