Files
2026-05-03 13:26:31 +01:00

268 lines
12 KiB
TypeScript

"use client";
import { ContractUploadForm } from "@/features/contracts/components/forms/contract-upload-form";
import { EmptyContractsState } from "@/features/contracts/components/list/empty-contracts-state";
import { ContractsList } from "@/features/contracts/components/list/contracts-list";
import { ContractsHeader } from "@/components/layout/contacts-header";
import { useState, useEffect } from "react";
import { getContracts } from "@/features/contracts/api/contract.action";
import { motion, AnimatePresence } from "motion/react";
import {
Upload,
FileText,
Sparkles,
Shield,
Zap,
ChevronRight,
Loader2,
} from "lucide-react";
export default function ContactsPage() {
const [refreshTrigger, setRefreshTrigger] = useState(0);
const [showContracts, setShowContracts] = useState(false);
const [isChecking, setIsChecking] = useState(true);
// Check if there are any existing contracts on mount
useEffect(() => {
const checkContracts = async () => {
try {
const result = await getContracts();
if (
result.success &&
Array.isArray(result.contracts) &&
result.contracts.length > 0
) {
setShowContracts(true);
}
} catch (error) {
console.error("Failed to check contracts:", error);
} finally {
setIsChecking(false);
}
};
checkContracts();
}, []);
const handleUploadSuccess = () => {
setRefreshTrigger((prev) => prev + 1);
setShowContracts(true);
};
if (isChecking) {
return (
<div className="min-h-screen bg-background text-foreground relative overflow-hidden">
{/* Ambient loading background */}
<div className="fixed inset-0 pointer-events-none">
<div className="absolute top-1/4 left-1/3 w-[500px] h-[500px] bg-primary/10 rounded-full blur-[120px] animate-pulse" />
<div className="absolute bottom-1/4 right-1/3 w-[400px] h-[400px] bg-violet-500/10 rounded-full blur-[100px] animate-pulse delay-700" />
<div className="absolute inset-0 bg-[linear-gradient(rgba(255,255,255,0.03)_1px,transparent_1px),linear-gradient(90deg,rgba(255,255,255,0.03)_1px,transparent_1px)] bg-[size:64px_64px] [mask-image:radial-gradient(ellipse_60%_60%_at_50%_50%,#000_30%,transparent_100%)]" />
</div>
<main className="relative z-10 flex flex-col h-screen items-center justify-center">
<motion.div
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
className="text-center space-y-6"
>
<div className="relative inline-flex">
<div className="absolute inset-0 bg-primary/30 blur-2xl rounded-full" />
<div className="relative p-6 bg-background/80 dark:bg-card/80 rounded-3xl border border-border/50 backdrop-blur-xl shadow-2xl">
<Loader2 className="w-10 h-10 text-primary animate-spin" />
</div>
</div>
<div className="space-y-2">
<p className="text-lg font-semibold text-foreground">
Loading workspace
</p>
<p className="text-sm text-muted-foreground">
Preparing your contract environment...
</p>
</div>
</motion.div>
</main>
</div>
);
}
return (
<div className="min-h-screen bg-background text-foreground relative overflow-hidden">
{/* Ambient Background */}
<div className="fixed inset-0 pointer-events-none">
<div className="absolute top-[-10%] left-[-5%] w-[600px] h-[600px] bg-primary/5 rounded-full blur-[120px]" />
<div className="absolute top-[20%] right-[-10%] w-[500px] h-[500px] bg-violet-500/5 rounded-full blur-[100px]" />
<div className="absolute bottom-[-10%] left-[20%] w-[400px] h-[400px] bg-emerald-500/5 rounded-full blur-[100px]" />
<div className="absolute inset-0 bg-[linear-gradient(rgba(255,255,255,0.02)_1px,transparent_1px),linear-gradient(90deg,rgba(255,255,255,0.02)_1px,transparent_1px)] bg-[size:64px_64px] [mask-image:radial-gradient(ellipse_80%_80%_at_50%_50%,#000_20%,transparent_100%)]" />
</div>
<ContractsHeader />
<main className="relative z-10 flex flex-col min-h-[calc(100vh-64px)]">
<div className="flex-1 overflow-auto">
<div className="max-w-7xl mx-auto px-6 lg:px-8 py-10 space-y-10">
{/* Upload Section */}
<motion.section
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.1, duration: 0.5 }}
>
<div className="flex items-center gap-3 mb-5">
<div className="p-2 rounded-xl bg-primary/10 border border-primary/20">
<Upload className="w-4 h-4 text-primary" />
</div>
<div>
<h2 className="text-xl font-bold tracking-tight">
Upload Contract
</h2>
<p className="text-xs text-muted-foreground">
PDF documents supported
</p>
</div>
</div>
<div className="relative group">
<div className="absolute -inset-0.5 bg-gradient-to-r from-primary/20 via-violet-500/20 to-primary/20 rounded-2xl blur opacity-30 group-hover:opacity-50 transition duration-500" />
<div className="relative rounded-2xl border border-border/60 bg-background/60 backdrop-blur-2xl p-6 md:p-8 shadow-xl shadow-black/5">
<div className="mb-6 flex items-start justify-between">
<div className="space-y-1">
<h3 className="text-sm font-semibold text-foreground">
New Document
</h3>
<p className="text-xs text-muted-foreground max-w-md">
Our AI pipeline will automatically extract summaries,
key clauses, risk factors, and generate actionable
business insights.
</p>
</div>
<div className="hidden sm:flex items-center gap-1.5 text-[10px] font-medium px-3 py-1.5 rounded-full bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 border border-emerald-500/20">
<span className="relative flex h-1.5 w-1.5">
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75" />
<span className="relative inline-flex rounded-full h-1.5 w-1.5 bg-emerald-500" />
</span>
AI Ready
</div>
</div>
<ContractUploadForm onUploadSuccess={handleUploadSuccess} />
</div>
</div>
</motion.section>
{/* Contracts List Section */}
<motion.section
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.2, duration: 0.5 }}
>
<div className="flex items-center justify-between mb-5">
<div className="flex items-center gap-3">
<div className="p-2 rounded-xl bg-violet-500/10 border border-violet-500/20">
<FileText className="w-4 h-4 text-violet-500" />
</div>
<div>
<h2 className="text-xl font-bold tracking-tight">
Your Contracts
</h2>
<p className="text-xs text-muted-foreground">
Manage, analyze, and query your document library
</p>
</div>
</div>
{showContracts && (
<motion.button
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
onClick={() => setRefreshTrigger((prev) => prev + 1)}
className="flex items-center gap-1.5 text-xs font-medium text-muted-foreground hover:text-foreground transition-colors px-3 py-1.5 rounded-lg hover:bg-muted/50"
>
<Zap className="w-3 h-3" />
Refresh
</motion.button>
)}
</div>
<div className="relative rounded-2xl border border-border/40 bg-background/40 backdrop-blur-2xl overflow-hidden shadow-xl shadow-black/5 min-h-[400px]">
<div className="absolute inset-x-0 top-0 h-px bg-gradient-to-r from-transparent via-primary/20 to-transparent" />
<AnimatePresence mode="wait">
{showContracts ? (
<motion.div
key="list"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="p-6 md:p-8"
>
<ContractsList refreshTrigger={refreshTrigger} />
</motion.div>
) : (
<motion.div
key="empty"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="p-6 md:p-8"
>
<EmptyContractsState />
</motion.div>
)}
</AnimatePresence>
</div>
</motion.section>
{/* Bottom Info Cards */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.3 }}
className="grid grid-cols-1 md:grid-cols-3 gap-4 pb-8"
>
{[
{
icon: <Shield className="w-4 h-4" />,
title: "Secure Processing",
desc: "Documents are encrypted in transit and at rest. Your data never leaves your infrastructure.",
color: "emerald",
},
{
icon: <Sparkles className="w-4 h-4" />,
title: "AI Extraction",
desc: "Advanced NLP models identify parties, obligations, risks, and key dates automatically.",
color: "primary",
},
{
icon: <Zap className="w-4 h-4" />,
title: "Instant Insights",
desc: "Get executive summaries and red-flag alerts within seconds of upload completion.",
color: "violet",
},
].map((feature, i) => (
<motion.div
key={feature.title}
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.4 + i * 0.05 }}
className="group relative rounded-2xl border border-border/40 bg-background/40 backdrop-blur-xl p-5 hover:bg-background/60 transition-all duration-300"
>
<div
className={`p-2 rounded-lg bg-${feature.color}-500/10 border border-${feature.color}-500/20 w-fit mb-3 text-${feature.color}-500`}
>
{feature.icon}
</div>
<h4 className="text-sm font-semibold mb-1">
{feature.title}
</h4>
<p className="text-xs text-muted-foreground leading-relaxed">
{feature.desc}
</p>
<ChevronRight className="w-4 h-4 text-muted-foreground/30 absolute bottom-5 right-5 group-hover:text-muted-foreground group-hover:translate-x-0.5 transition-all" />
</motion.div>
))}
</motion.div>
</div>
</div>
</main>
</div>
);
}