Add client portal themes (Cyberpunk etc.), settings page, semantic Tailwind classes
Build & Auto-Repair / build (push) Has been cancelled

This commit is contained in:
JCBSComputer
2026-07-01 15:47:44 +02:00
parent ef2e12ec5e
commit 6f622b6329
19 changed files with 832 additions and 222 deletions
+68
View File
@@ -318,3 +318,71 @@
color: #1BB0CE !important; color: #1BB0CE !important;
background: none !important; background: none !important;
} }
/* ============================================================================
Client Portal Overrides — Background for portal pages
============================================================================ */
/* Make the outer portal container transparent so body::before background shows */
.dark.theme-bw .min-h-screen.bg-background {
background: transparent !important;
}
/* Portal sidebar — dark glass */
.dark.theme-bw aside.bg-card.border-r.border-border {
background: hsl(240, 15%, 3%) !important;
border-right-color: hsl(270, 25%, 10%) !important;
}
/* Portal sidebar nav items — muted text */
.dark.theme-bw aside .text-muted-foreground {
color: hsl(240, 8%, 50%) !important;
}
.dark.theme-bw aside button:hover {
background: hsla(330, 100%, 50%, 0.08) !important;
color: hsl(0, 0%, 90%) !important;
}
/* Portal sidebar active nav item */
.dark.theme-bw aside .bg-primary\/10.text-primary {
background: hsl(330, 100%, 50%) !important;
color: hsl(0, 0%, 100%) !important;
box-shadow: 0 0 10px rgba(255, 0, 127, 0.3), 0 0 20px rgba(255, 0, 127, 0.1);
border-color: hsl(330, 100%, 50%) !important;
}
.dark.theme-bw aside .bg-primary\/10.text-primary svg {
color: hsl(0, 0%, 100%) !important;
}
/* Portal header — dark glass */
.dark.theme-bw header.bg-card\/80 {
background: hsl(240, 15%, 3%) !important;
border-bottom-color: hsl(270, 25%, 10%) !important;
}
/* Portal card backgrounds — semi-transparent glass */
.dark.theme-bw .bg-card {
background: hsla(240, 15%, 7%, 0.65) !important;
backdrop-filter: blur(10px) !important;
border-color: hsla(270, 30%, 20%, 0.35) !important;
}
/* Portal borders */
.dark.theme-bw .border-border {
border-color: hsl(270, 25%, 12%) !important;
}
/* Portal input fields */
.dark.theme-bw .bg-muted {
background: hsl(240, 12%, 9%) !important;
}
/* Portal primary buttons */
.dark.theme-bw .bg-primary {
background: hsl(330, 100%, 50%) !important;
box-shadow: 0 0 10px rgba(255, 0, 127, 0.2);
}
.dark.theme-bw .bg-primary:hover {
background: hsl(330, 100%, 55%) !important;
box-shadow: 0 0 15px rgba(255, 0, 127, 0.4);
}
+68
View File
@@ -287,3 +287,71 @@
color: #1BB0CE !important; color: #1BB0CE !important;
background: none !important; background: none !important;
} }
/* ============================================================================
Client Portal Overrides — Background for portal pages
============================================================================ */
/* Make the outer portal container transparent so body::before background shows */
.dark.theme-cosmic .min-h-screen.bg-background {
background: transparent !important;
}
/* Portal sidebar — dark glass */
.dark.theme-cosmic aside.bg-card.border-r.border-border {
background: hsl(240, 15%, 3%) !important;
border-right-color: hsl(270, 25%, 10%) !important;
}
/* Portal sidebar nav items — muted text */
.dark.theme-cosmic aside .text-muted-foreground {
color: hsl(240, 8%, 50%) !important;
}
.dark.theme-cosmic aside button:hover {
background: hsla(330, 100%, 50%, 0.08) !important;
color: hsl(0, 0%, 90%) !important;
}
/* Portal sidebar active nav item */
.dark.theme-cosmic aside .bg-primary\/10.text-primary {
background: hsl(330, 100%, 50%) !important;
color: hsl(0, 0%, 100%) !important;
box-shadow: 0 0 10px rgba(255, 0, 127, 0.3), 0 0 20px rgba(255, 0, 127, 0.1);
border-color: hsl(330, 100%, 50%) !important;
}
.dark.theme-cosmic aside .bg-primary\/10.text-primary svg {
color: hsl(0, 0%, 100%) !important;
}
/* Portal header — dark glass */
.dark.theme-cosmic header.bg-card\/80 {
background: hsl(240, 15%, 3%) !important;
border-bottom-color: hsl(270, 25%, 10%) !important;
}
/* Portal card backgrounds — semi-transparent glass */
.dark.theme-cosmic .bg-card {
background: hsla(240, 15%, 7%, 0.65) !important;
backdrop-filter: blur(10px) !important;
border-color: hsla(270, 30%, 20%, 0.35) !important;
}
/* Portal borders */
.dark.theme-cosmic .border-border {
border-color: hsl(270, 25%, 12%) !important;
}
/* Portal input fields */
.dark.theme-cosmic .bg-muted {
background: hsl(240, 12%, 9%) !important;
}
/* Portal primary buttons */
.dark.theme-cosmic .bg-primary {
background: hsl(330, 100%, 50%) !important;
box-shadow: 0 0 10px rgba(255, 0, 127, 0.2);
}
.dark.theme-cosmic .bg-primary:hover {
background: hsl(330, 100%, 55%) !important;
box-shadow: 0 0 15px rgba(255, 0, 127, 0.4);
}
+68
View File
@@ -318,3 +318,71 @@
color: #1BB0CE !important; color: #1BB0CE !important;
background: none !important; background: none !important;
} }
/* ============================================================================
Client Portal Overrides — Background for portal pages
============================================================================ */
/* Make the outer portal container transparent so body::before background shows */
.dark.theme-cyber2 .min-h-screen.bg-background {
background: transparent !important;
}
/* Portal sidebar — dark glass */
.dark.theme-cyber2 aside.bg-card.border-r.border-border {
background: hsl(240, 15%, 3%) !important;
border-right-color: hsl(270, 25%, 10%) !important;
}
/* Portal sidebar nav items — muted text */
.dark.theme-cyber2 aside .text-muted-foreground {
color: hsl(240, 8%, 50%) !important;
}
.dark.theme-cyber2 aside button:hover {
background: hsla(330, 100%, 50%, 0.08) !important;
color: hsl(0, 0%, 90%) !important;
}
/* Portal sidebar active nav item */
.dark.theme-cyber2 aside .bg-primary\/10.text-primary {
background: hsl(330, 100%, 50%) !important;
color: hsl(0, 0%, 100%) !important;
box-shadow: 0 0 10px rgba(255, 0, 127, 0.3), 0 0 20px rgba(255, 0, 127, 0.1);
border-color: hsl(330, 100%, 50%) !important;
}
.dark.theme-cyber2 aside .bg-primary\/10.text-primary svg {
color: hsl(0, 0%, 100%) !important;
}
/* Portal header — dark glass */
.dark.theme-cyber2 header.bg-card\/80 {
background: hsl(240, 15%, 3%) !important;
border-bottom-color: hsl(270, 25%, 10%) !important;
}
/* Portal card backgrounds — semi-transparent glass */
.dark.theme-cyber2 .bg-card {
background: hsla(240, 15%, 7%, 0.65) !important;
backdrop-filter: blur(10px) !important;
border-color: hsla(270, 30%, 20%, 0.35) !important;
}
/* Portal borders */
.dark.theme-cyber2 .border-border {
border-color: hsl(270, 25%, 12%) !important;
}
/* Portal input fields */
.dark.theme-cyber2 .bg-muted {
background: hsl(240, 12%, 9%) !important;
}
/* Portal primary buttons */
.dark.theme-cyber2 .bg-primary {
background: hsl(330, 100%, 50%) !important;
box-shadow: 0 0 10px rgba(255, 0, 127, 0.2);
}
.dark.theme-cyber2 .bg-primary:hover {
background: hsl(330, 100%, 55%) !important;
box-shadow: 0 0 15px rgba(255, 0, 127, 0.4);
}
+81
View File
@@ -1233,3 +1233,84 @@
.light.theme-cyberpunk span.h-1\.5.w-1\.5.rounded-full.bg-zinc-500 { .light.theme-cyberpunk span.h-1\.5.w-1\.5.rounded-full.bg-zinc-500 {
background: hsl(0, 0%, 40%) !important; background: hsl(0, 0%, 40%) !important;
} }
/* ============================================================================
Client Portal Overrides — Cyberpunk background for portal pages
============================================================================ */
/* Make the outer portal container transparent so body::before background shows */
.dark.theme-cyberpunk .min-h-screen.bg-background {
background: transparent !important;
}
/* Portal sidebar — dark glass */
.dark.theme-cyberpunk aside.bg-card.border-r.border-border {
background: hsl(240, 15%, 3%) !important;
border-right-color: hsl(270, 25%, 10%) !important;
}
/* Portal sidebar nav items — muted text */
.dark.theme-cyberpunk aside .text-muted-foreground {
color: hsl(240, 8%, 50%) !important;
}
.dark.theme-cyberpunk aside button:hover {
background: hsla(330, 100%, 50%, 0.08) !important;
color: hsl(0, 0%, 90%) !important;
}
/* Portal sidebar active nav item — neon pink pill */
.dark.theme-cyberpunk aside .bg-primary\/10.text-primary {
background: hsl(330, 100%, 50%) !important;
color: hsl(0, 0%, 100%) !important;
box-shadow: 0 0 10px rgba(255, 0, 127, 0.3), 0 0 20px rgba(255, 0, 127, 0.1);
border-color: hsl(330, 100%, 50%) !important;
}
.dark.theme-cyberpunk aside .bg-primary\/10.text-primary svg {
color: hsl(0, 0%, 100%) !important;
}
/* Portal header — dark glass */
.dark.theme-cyberpunk header.bg-card\/80 {
background: hsl(240, 15%, 3%) !important;
border-bottom-color: hsl(270, 25%, 10%) !important;
}
/* Portal card backgrounds — semi-transparent glass */
.dark.theme-cyberpunk .bg-card {
background: hsla(240, 15%, 7%, 0.65) !important;
backdrop-filter: blur(10px) !important;
border-color: hsla(270, 30%, 20%, 0.35) !important;
}
/* Portal borders */
.dark.theme-cyberpunk .border-border {
border-color: hsl(270, 25%, 12%) !important;
}
/* Portal input fields */
.dark.theme-cyberpunk .bg-muted {
background: hsl(240, 12%, 9%) !important;
}
.dark.theme-cyberpunk .border-border:focus {
border-color: hsl(330, 100%, 50%) !important;
}
/* Portal primary buttons */
.dark.theme-cyberpunk .bg-primary {
background: hsl(330, 100%, 50%) !important;
box-shadow: 0 0 10px rgba(255, 0, 127, 0.2);
}
.dark.theme-cyberpunk .bg-primary:hover {
background: hsl(330, 100%, 55%) !important;
box-shadow: 0 0 15px rgba(255, 0, 127, 0.4);
}
/* Portal primary text highlights */
.dark.theme-cyberpunk .text-primary {
color: hsl(330, 100%, 55%) !important;
}
/* Portal muted text */
.dark.theme-cyberpunk .text-muted-foreground {
color: hsl(240, 8%, 50%) !important;
}
+68
View File
@@ -273,3 +273,71 @@
color: #1BB0CE !important; color: #1BB0CE !important;
background: none !important; background: none !important;
} }
/* ============================================================================
Client Portal Overrides Background for portal pages
============================================================================ */
/* Make the outer portal container transparent so body::before background shows */
.dark.theme-pumpkin .min-h-screen.bg-background {
background: transparent !important;
}
/* Portal sidebar — dark glass */
.dark.theme-pumpkin aside.bg-card.border-r.border-border {
background: hsl(240, 15%, 3%) !important;
border-right-color: hsl(270, 25%, 10%) !important;
}
/* Portal sidebar nav items — muted text */
.dark.theme-pumpkin aside .text-muted-foreground {
color: hsl(240, 8%, 50%) !important;
}
.dark.theme-pumpkin aside button:hover {
background: hsla(330, 100%, 50%, 0.08) !important;
color: hsl(0, 0%, 90%) !important;
}
/* Portal sidebar active nav item */
.dark.theme-pumpkin aside .bg-primary\/10.text-primary {
background: hsl(330, 100%, 50%) !important;
color: hsl(0, 0%, 100%) !important;
box-shadow: 0 0 10px rgba(255, 0, 127, 0.3), 0 0 20px rgba(255, 0, 127, 0.1);
border-color: hsl(330, 100%, 50%) !important;
}
.dark.theme-pumpkin aside .bg-primary\/10.text-primary svg {
color: hsl(0, 0%, 100%) !important;
}
/* Portal header — dark glass */
.dark.theme-pumpkin header.bg-card\/80 {
background: hsl(240, 15%, 3%) !important;
border-bottom-color: hsl(270, 25%, 10%) !important;
}
/* Portal card backgrounds — semi-transparent glass */
.dark.theme-pumpkin .bg-card {
background: hsla(240, 15%, 7%, 0.65) !important;
backdrop-filter: blur(10px) !important;
border-color: hsla(270, 30%, 20%, 0.35) !important;
}
/* Portal borders */
.dark.theme-pumpkin .border-border {
border-color: hsl(270, 25%, 12%) !important;
}
/* Portal input fields */
.dark.theme-pumpkin .bg-muted {
background: hsl(240, 12%, 9%) !important;
}
/* Portal primary buttons */
.dark.theme-pumpkin .bg-primary {
background: hsl(330, 100%, 50%) !important;
box-shadow: 0 0 10px rgba(255, 0, 127, 0.2);
}
.dark.theme-pumpkin .bg-primary:hover {
background: hsl(330, 100%, 55%) !important;
box-shadow: 0 0 15px rgba(255, 0, 127, 0.4);
}
+68
View File
@@ -183,3 +183,71 @@
color: #1BB0CE !important; color: #1BB0CE !important;
background: none !important; background: none !important;
} }
/* ============================================================================
Client Portal Overrides Background for portal pages
============================================================================ */
/* Make the outer portal container transparent so body::before background shows */
.dark.theme-spidey .min-h-screen.bg-background {
background: transparent !important;
}
/* Portal sidebar — dark glass */
.dark.theme-spidey aside.bg-card.border-r.border-border {
background: hsl(240, 15%, 3%) !important;
border-right-color: hsl(270, 25%, 10%) !important;
}
/* Portal sidebar nav items — muted text */
.dark.theme-spidey aside .text-muted-foreground {
color: hsl(240, 8%, 50%) !important;
}
.dark.theme-spidey aside button:hover {
background: hsla(0, 100%, 50%, 0.08) !important;
color: hsl(0, 0%, 90%) !important;
}
/* Portal sidebar active nav item */
.dark.theme-spidey aside .bg-primary\/10.text-primary {
background: hsl(0, 100%, 50%) !important;
color: hsl(0, 0%, 100%) !important;
box-shadow: 0 0 10px rgba(255, 0, 0, 0.3), 0 0 20px rgba(255, 0, 0, 0.1);
border-color: hsl(0, 100%, 50%) !important;
}
.dark.theme-spidey aside .bg-primary\/10.text-primary svg {
color: hsl(0, 0%, 100%) !important;
}
/* Portal header — dark glass */
.dark.theme-spidey header.bg-card\/80 {
background: hsl(240, 15%, 3%) !important;
border-bottom-color: hsl(270, 25%, 10%) !important;
}
/* Portal card backgrounds — semi-transparent glass */
.dark.theme-spidey .bg-card {
background: hsla(240, 15%, 7%, 0.65) !important;
backdrop-filter: blur(10px) !important;
border-color: hsla(270, 30%, 20%, 0.35) !important;
}
/* Portal borders */
.dark.theme-spidey .border-border {
border-color: hsl(270, 25%, 12%) !important;
}
/* Portal input fields */
.dark.theme-spidey .bg-muted {
background: hsl(240, 12%, 9%) !important;
}
/* Portal primary buttons */
.dark.theme-spidey .bg-primary {
background: hsl(0, 100%, 50%) !important;
box-shadow: 0 0 10px rgba(255, 0, 0, 0.2);
}
.dark.theme-spidey .bg-primary:hover {
background: hsl(0, 100%, 55%) !important;
box-shadow: 0 0 15px rgba(255, 0, 0, 0.4);
}
+15 -15
View File
@@ -31,9 +31,9 @@ export default function ClientActivity() {
}, []) }, [])
const getIcon = (type: string) => { const getIcon = (type: string) => {
if (type === "milestone") return <GitMerge className="h-4 w-4 text-[#1BB0CE]" /> if (type === "milestone") return <GitMerge className="h-4 w-4 text-primary" />
if (type === "invoice") return <FileText className="h-4 w-4 text-[#a855f7]" /> if (type === "invoice") return <FileText className="h-4 w-4 text-purple-400" />
return <LifeBuoy className="h-4 w-4 text-[#f59e0b]" /> return <LifeBuoy className="h-4 w-4 text-amber-400" />
} }
const getStatusColor = (type: string, status: string) => { const getStatusColor = (type: string, status: string) => {
@@ -59,35 +59,35 @@ export default function ClientActivity() {
return ( return (
<div> <div>
<h1 className="text-xl font-bold text-white mb-1">Activity</h1> <h1 className="text-xl font-bold text-foreground mb-1">Activity</h1>
<p className="text-sm text-[#6a6a75] mb-6">Recent project updates and changes</p> <p className="text-sm text-muted-foreground mb-6">Recent project updates and changes</p>
{events.length === 0 ? ( {events.length === 0 ? (
<div className="text-center py-16"> <div className="text-center py-16">
<Activity className="h-10 w-10 text-[#2a2a35] mx-auto mb-3" /> <Activity className="h-10 w-10 text-muted-foreground mx-auto mb-3" />
<p className="text-sm text-[#6a6a75]">No recent activity</p> <p className="text-sm text-muted-foreground">No recent activity</p>
</div> </div>
) : ( ) : (
<div className="relative"> <div className="relative">
<div className="absolute left-[17px] top-2 bottom-2 w-px bg-[#1a1a24]" /> <div className="absolute left-[17px] top-2 bottom-2 w-px bg-muted" />
<div className="space-y-0"> <div className="space-y-0">
{(events || []).map((e, i) => ( {(events || []).map((e, i) => (
<button <button
key={`${e.event_type}-${e.id}-${i}`} key={`${e.event_type}-${e.id}-${i}`}
onClick={() => handleClick(e)} onClick={() => handleClick(e)}
className="w-full flex items-start gap-4 py-3 pl-0 pr-4 text-left hover:bg-[#0d1117]/50 rounded-xl transition-all group" className="w-full flex items-start gap-4 py-3 pl-0 pr-4 text-left hover:bg-card/50 rounded-xl transition-all group"
> >
<div className="relative z-10 w-9 h-9 rounded-full bg-[#0a0a0f] border-2 border-[#1a1a24] flex items-center justify-center flex-shrink-0"> <div className="relative z-10 w-9 h-9 rounded-full bg-background border-2 border-border flex items-center justify-center flex-shrink-0">
{getIcon(e.event_type)} {getIcon(e.event_type)}
</div> </div>
<div className="flex-1 min-w-0 pt-0.5"> <div className="flex-1 min-w-0 pt-0.5">
<div className="flex items-center justify-between gap-2"> <div className="flex items-center justify-between gap-2">
<p className="text-sm text-white font-medium truncate"> <p className="text-sm text-foreground font-medium truncate">
{e.event_type === "milestone" && (e.name || "Milestone updated")} {e.event_type === "milestone" && (e.name || "Milestone updated")}
{e.event_type === "invoice" && `${e.invoice_number || "Invoice"}${e.currency || ""} ${Number(e.total_amount || 0).toLocaleString()}`} {e.event_type === "invoice" && `${e.invoice_number || "Invoice"}${e.currency || ""} ${Number(e.total_amount || 0).toLocaleString()}`}
{e.event_type === "ticket" && (e.title || "Ticket created")} {e.event_type === "ticket" && (e.title || "Ticket created")}
</p> </p>
<span className="text-[11px] text-[#6a6a75] flex-shrink-0">{formatDate(e.date)}</span> <span className="text-[11px] text-muted-foreground flex-shrink-0">{formatDate(e.date)}</span>
</div> </div>
<div className="flex items-center gap-2 mt-0.5"> <div className="flex items-center gap-2 mt-0.5">
<span className={`text-[11px] capitalize ${getStatusColor(e.event_type, e.status || e.status_name || "")}`}> <span className={`text-[11px] capitalize ${getStatusColor(e.event_type, e.status || e.status_name || "")}`}>
@@ -95,13 +95,13 @@ export default function ClientActivity() {
</span> </span>
{e.event_type === "milestone" && e.project_name && ( {e.event_type === "milestone" && e.project_name && (
<> <>
<span className="text-[#2a2a35] text-[10px]"></span> <span className="text-muted-foreground text-[10px]"></span>
<span className="text-[11px] text-[#6a6a75] truncate">{e.project_name}</span> <span className="text-[11px] text-muted-foreground truncate">{e.project_name}</span>
</> </>
)} )}
</div> </div>
</div> </div>
<ChevronRight className="h-4 w-4 text-[#2a2a35] group-hover:text-[#1BB0CE] transition-colors flex-shrink-0 mt-1" /> <ChevronRight className="h-4 w-4 text-muted-foreground group-hover:text-primary transition-colors flex-shrink-0 mt-1" />
</button> </button>
))} ))}
</div> </div>
+20 -20
View File
@@ -60,8 +60,8 @@ export default function ClientDashboard() {
return ( return (
<div> <div>
<h1 className="text-xl font-bold text-white mb-1">Welcome, {name}</h1> <h1 className="text-xl font-bold text-foreground mb-1">Welcome, {name}</h1>
<p className="text-sm text-[#6a6a75] mb-6">Your project overview at a glance</p> <p className="text-sm text-muted-foreground mb-6">Your project overview at a glance</p>
{/* Count cards */} {/* Count cards */}
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4 mb-6"> <div className="grid grid-cols-1 sm:grid-cols-3 gap-4 mb-6">
@@ -71,15 +71,15 @@ export default function ClientDashboard() {
<button <button
key={card.label} key={card.label}
onClick={() => router.push(card.href)} onClick={() => router.push(card.href)}
className="bg-[#0d1117] border border-[#1a1a24] rounded-xl p-5 text-left hover:border-[#1BB0CE]/30 transition-all group" className="bg-card border border-border rounded-xl p-5 text-left hover:border-primary/30 transition-all group"
> >
<div className="flex items-center gap-3 mb-3"> <div className="flex items-center gap-3 mb-3">
<div className="w-10 h-10 rounded-lg flex items-center justify-center" style={{ backgroundColor: `${card.color}15` }}> <div className="w-10 h-10 rounded-lg flex items-center justify-center" style={{ backgroundColor: `${card.color}15` }}>
<Icon className="h-5 w-5" style={{ color: card.color }} /> <Icon className="h-5 w-5" style={{ color: card.color }} />
</div> </div>
<span className="text-2xl font-bold text-white">{card.value}</span> <span className="text-2xl font-bold text-foreground">{card.value}</span>
</div> </div>
<p className="text-sm text-[#8a8a95]">{card.label}</p> <p className="text-sm text-muted-foreground">{card.label}</p>
</button> </button>
) )
})} })}
@@ -87,24 +87,24 @@ export default function ClientDashboard() {
{/* Financial stats */} {/* Financial stats */}
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4 mb-6"> <div className="grid grid-cols-1 sm:grid-cols-3 gap-4 mb-6">
<div className="bg-[#0d1117] border border-[#1a1a24] rounded-xl p-5"> <div className="bg-card border border-border rounded-xl p-5">
<div className="flex items-center gap-2 mb-2"> <div className="flex items-center gap-2 mb-2">
<DollarSign className="h-4 w-4 text-[#1BB0CE]" /> <DollarSign className="h-4 w-4 text-primary" />
<span className="text-xs text-[#6a6a75] uppercase tracking-wider">Total Billed</span> <span className="text-xs text-muted-foreground uppercase tracking-wider">Total Billed</span>
</div> </div>
<p className="text-xl font-bold text-white">{formatCurrency(stats.totalBilled)}</p> <p className="text-xl font-bold text-foreground">{formatCurrency(stats.totalBilled)}</p>
</div> </div>
<div className="bg-[#0d1117] border border-[#1a1a24] rounded-xl p-5"> <div className="bg-card border border-border rounded-xl p-5">
<div className="flex items-center gap-2 mb-2"> <div className="flex items-center gap-2 mb-2">
<TrendingUp className="h-4 w-4 text-emerald-400" /> <TrendingUp className="h-4 w-4 text-emerald-400" />
<span className="text-xs text-[#6a6a75] uppercase tracking-wider">Total Paid</span> <span className="text-xs text-muted-foreground uppercase tracking-wider">Total Paid</span>
</div> </div>
<p className="text-xl font-bold text-emerald-400">{formatCurrency(stats.totalPaid)}</p> <p className="text-xl font-bold text-emerald-400">{formatCurrency(stats.totalPaid)}</p>
</div> </div>
<div className="bg-[#0d1117] border border-[#1a1a24] rounded-xl p-5"> <div className="bg-card border border-border rounded-xl p-5">
<div className="flex items-center gap-2 mb-2"> <div className="flex items-center gap-2 mb-2">
<AlertTriangle className="h-4 w-4 text-red-400" /> <AlertTriangle className="h-4 w-4 text-red-400" />
<span className="text-xs text-[#6a6a75] uppercase tracking-wider">Overdue</span> <span className="text-xs text-muted-foreground uppercase tracking-wider">Overdue</span>
</div> </div>
<p className="text-xl font-bold text-red-400">{formatCurrency(stats.totalOverdue)}</p> <p className="text-xl font-bold text-red-400">{formatCurrency(stats.totalOverdue)}</p>
</div> </div>
@@ -112,24 +112,24 @@ export default function ClientDashboard() {
{/* Upcoming Deadlines */} {/* Upcoming Deadlines */}
{stats.upcomingDeadlines.length > 0 && ( {stats.upcomingDeadlines.length > 0 && (
<div className="bg-[#0d1117] border border-[#1a1a24] rounded-xl p-5"> <div className="bg-card border border-border rounded-xl p-5">
<div className="flex items-center gap-2 mb-4"> <div className="flex items-center gap-2 mb-4">
<Calendar className="h-4 w-4 text-[#f59e0b]" /> <Calendar className="h-4 w-4 text-amber-400" />
<span className="text-sm font-semibold text-white">Upcoming Deadlines</span> <span className="text-sm font-semibold text-foreground">Upcoming Deadlines</span>
</div> </div>
<div className="space-y-3"> <div className="space-y-3">
{(stats.upcomingDeadlines || []).map((d, i) => ( {(stats.upcomingDeadlines || []).map((d, i) => (
<div key={`${d.type}-${d.id}-${i}`} className="flex items-center justify-between"> <div key={`${d.type}-${d.id}-${i}`} className="flex items-center justify-between">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className={`w-2 h-2 rounded-full ${ <div className={`w-2 h-2 rounded-full ${
d.type === "invoice" ? "bg-[#a855f7]" : "bg-[#1BB0CE]" d.type === "invoice" ? "bg-purple-400" : "bg-primary"
}`} /> }`} />
<div> <div>
<p className="text-sm text-white">{d.name}</p> <p className="text-sm text-foreground">{d.name}</p>
<p className="text-[11px] text-[#6a6a75] capitalize">{d.type} &middot; {d.status_name?.replace("_", " ")}</p> <p className="text-[11px] text-muted-foreground capitalize">{d.type} &middot; {d.status_name?.replace("_", " ")}</p>
</div> </div>
</div> </div>
<span className="text-xs text-[#6a6a75]"> <span className="text-xs text-muted-foreground">
{new Date(d.date).toLocaleDateString()} {new Date(d.date).toLocaleDateString()}
</span> </span>
</div> </div>
+34 -34
View File
@@ -147,7 +147,7 @@ export default function InvoiceDetail() {
if (!data) { if (!data) {
return ( return (
<div className="flex items-center justify-center h-64"> <div className="flex items-center justify-center h-64">
<div className="w-8 h-8 border-2 border-[#1BB0CE] border-t-transparent rounded-full animate-spin" /> <div className="w-8 h-8 border-2 border-primary border-t-transparent rounded-full animate-spin" />
</div> </div>
) )
} }
@@ -157,10 +157,10 @@ export default function InvoiceDetail() {
return ( return (
<div ref={printRef}> <div ref={printRef}>
<div className="flex items-center justify-between mb-1"> <div className="flex items-center justify-between mb-1">
<h1 className="text-xl font-bold text-white">Invoice {invoice.invoice_number}</h1> <h1 className="text-xl font-bold text-foreground">Invoice {invoice.invoice_number}</h1>
<button <button
onClick={handlePrint} onClick={handlePrint}
className="flex items-center gap-1.5 bg-[#1BB0CE] hover:bg-[#1BB0CE]/80 text-white text-xs px-3 py-2 rounded-lg transition-all" className="flex items-center gap-1.5 bg-primary hover:bg-primary/80 text-foreground text-xs px-3 py-2 rounded-lg transition-all"
> >
<Printer className="h-3.5 w-3.5" /> <Printer className="h-3.5 w-3.5" />
Print / PDF Print / PDF
@@ -175,40 +175,40 @@ export default function InvoiceDetail() {
</span> </span>
<div className="grid grid-cols-2 gap-4 mt-6 mb-6"> <div className="grid grid-cols-2 gap-4 mt-6 mb-6">
<div className="bg-[#0d1117] border border-[#1a1a24] rounded-xl p-4"> <div className="bg-card border border-border rounded-xl p-4">
<span className="text-[11px] text-[#6a6a75] uppercase tracking-wider">Issued</span> <span className="text-[11px] text-muted-foreground uppercase tracking-wider">Issued</span>
<p className="text-white text-sm mt-1"> <p className="text-foreground text-sm mt-1">
{invoice.issued_date ? new Date(invoice.issued_date).toLocaleDateString() : "-"} {invoice.issued_date ? new Date(invoice.issued_date).toLocaleDateString() : "-"}
</p> </p>
</div> </div>
<div className="bg-[#0d1117] border border-[#1a1a24] rounded-xl p-4"> <div className="bg-card border border-border rounded-xl p-4">
<span className="text-[11px] text-[#6a6a75] uppercase tracking-wider">Due</span> <span className="text-[11px] text-muted-foreground uppercase tracking-wider">Due</span>
<p className="text-white text-sm mt-1"> <p className="text-foreground text-sm mt-1">
{invoice.due_date ? new Date(invoice.due_date).toLocaleDateString() : "-"} {invoice.due_date ? new Date(invoice.due_date).toLocaleDateString() : "-"}
</p> </p>
</div> </div>
</div> </div>
<h2 className="text-base font-semibold text-white mb-3">Items</h2> <h2 className="text-base font-semibold text-foreground mb-3">Items</h2>
<div className="bg-[#0d1117] border border-[#1a1a24] rounded-xl overflow-hidden mb-6 print:hidden"> <div className="bg-card border border-border rounded-xl overflow-hidden mb-6 print:hidden">
<table className="w-full"> <table className="w-full">
<thead> <thead>
<tr className="border-b border-[#1a1a24]"> <tr className="border-b border-border">
<th className="text-left text-[11px] text-[#6a6a75] uppercase tracking-wider px-4 py-3">Description</th> <th className="text-left text-[11px] text-muted-foreground uppercase tracking-wider px-4 py-3">Description</th>
<th className="text-right text-[11px] text-[#6a6a75] uppercase tracking-wider px-4 py-3">Qty</th> <th className="text-right text-[11px] text-muted-foreground uppercase tracking-wider px-4 py-3">Qty</th>
<th className="text-right text-[11px] text-[#6a6a75] uppercase tracking-wider px-4 py-3">Unit Price</th> <th className="text-right text-[11px] text-muted-foreground uppercase tracking-wider px-4 py-3">Unit Price</th>
<th className="text-right text-[11px] text-[#6a6a75] uppercase tracking-wider px-4 py-3">Total</th> <th className="text-right text-[11px] text-muted-foreground uppercase tracking-wider px-4 py-3">Total</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{(items || []).map((item, i) => ( {(items || []).map((item, i) => (
<tr key={i} className="border-b border-[#1a1a24]/50"> <tr key={i} className="border-b border-border/50">
<td className="text-sm text-white px-4 py-3">{item.description}</td> <td className="text-sm text-foreground px-4 py-3">{item.description}</td>
<td className="text-sm text-white px-4 py-3 text-right">{item.quantity}</td> <td className="text-sm text-foreground px-4 py-3 text-right">{item.quantity}</td>
<td className="text-sm text-white px-4 py-3 text-right"> <td className="text-sm text-foreground px-4 py-3 text-right">
{invoice.currency} {Number(item.unit_price).toLocaleString()} {invoice.currency} {Number(item.unit_price).toLocaleString()}
</td> </td>
<td className="text-sm text-white px-4 py-3 text-right"> <td className="text-sm text-foreground px-4 py-3 text-right">
{invoice.currency} {Number(item.total_price).toLocaleString()} {invoice.currency} {Number(item.total_price).toLocaleString()}
</td> </td>
</tr> </tr>
@@ -217,23 +217,23 @@ export default function InvoiceDetail() {
<tfoot> <tfoot>
{invoice.subtotal && ( {invoice.subtotal && (
<tr> <tr>
<td colSpan={3} className="text-right text-sm text-[#8a8a95] px-4 py-2">Subtotal</td> <td colSpan={3} className="text-right text-sm text-muted-foreground px-4 py-2">Subtotal</td>
<td className="text-right text-sm text-white px-4 py-2"> <td className="text-right text-sm text-foreground px-4 py-2">
{invoice.currency} {Number(invoice.subtotal).toLocaleString()} {invoice.currency} {Number(invoice.subtotal).toLocaleString()}
</td> </td>
</tr> </tr>
)} )}
{invoice.tax_amount > 0 && ( {invoice.tax_amount > 0 && (
<tr> <tr>
<td colSpan={3} className="text-right text-sm text-[#8a8a95] px-4 py-2">Tax ({invoice.tax_percent}%)</td> <td colSpan={3} className="text-right text-sm text-muted-foreground px-4 py-2">Tax ({invoice.tax_percent}%)</td>
<td className="text-right text-sm text-white px-4 py-2"> <td className="text-right text-sm text-foreground px-4 py-2">
{invoice.currency} {Number(invoice.tax_amount).toLocaleString()} {invoice.currency} {Number(invoice.tax_amount).toLocaleString()}
</td> </td>
</tr> </tr>
)} )}
<tr className="border-t border-[#1a1a24]"> <tr className="border-t border-border">
<td colSpan={3} className="text-right text-sm font-semibold text-white px-4 py-3">Total</td> <td colSpan={3} className="text-right text-sm font-semibold text-foreground px-4 py-3">Total</td>
<td className="text-right text-sm font-bold text-[#1BB0CE] px-4 py-3"> <td className="text-right text-sm font-bold text-primary px-4 py-3">
{invoice.currency} {Number(invoice.total_amount).toLocaleString()} {invoice.currency} {Number(invoice.total_amount).toLocaleString()}
</td> </td>
</tr> </tr>
@@ -243,15 +243,15 @@ export default function InvoiceDetail() {
{receipts.length > 0 && ( {receipts.length > 0 && (
<> <>
<h2 className="text-base font-semibold text-white mb-3">Receipts</h2> <h2 className="text-base font-semibold text-foreground mb-3">Receipts</h2>
<div className="space-y-2"> <div className="space-y-2">
{(receipts || []).map((r) => ( {(receipts || []).map((r) => (
<div key={r.id} className="bg-[#0d1117] border border-[#1a1a24] rounded-xl p-4 flex items-center justify-between"> <div key={r.id} className="bg-card border border-border rounded-xl p-4 flex items-center justify-between">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<FileText className="h-4 w-4 text-[#1BB0CE]" /> <FileText className="h-4 w-4 text-primary" />
<div> <div>
<span className="text-sm text-white">{r.receipt_number}</span> <span className="text-sm text-foreground">{r.receipt_number}</span>
<p className="text-[11px] text-[#6a6a75]"> <p className="text-[11px] text-muted-foreground">
{new Date(r.issued_at).toLocaleDateString()} {new Date(r.issued_at).toLocaleDateString()}
</p> </p>
</div> </div>
@@ -260,7 +260,7 @@ export default function InvoiceDetail() {
<a <a
href={r.file_url} href={r.file_url}
target="_blank" target="_blank"
className="flex items-center gap-1.5 text-xs text-[#1BB0CE] hover:underline" className="flex items-center gap-1.5 text-xs text-primary hover:underline"
> >
<Download className="h-3.5 w-3.5" /> <Download className="h-3.5 w-3.5" />
Download Download
+9 -9
View File
@@ -28,13 +28,13 @@ export default function ClientInvoices() {
return ( return (
<div> <div>
<h1 className="text-xl font-bold text-white mb-1">Invoices</h1> <h1 className="text-xl font-bold text-foreground mb-1">Invoices</h1>
<p className="text-sm text-[#6a6a75] mb-6">View and download your invoices and receipts</p> <p className="text-sm text-muted-foreground mb-6">View and download your invoices and receipts</p>
{invoices.length === 0 ? ( {invoices.length === 0 ? (
<div className="text-center py-16"> <div className="text-center py-16">
<FileText className="h-10 w-10 text-[#2a2a35] mx-auto mb-3" /> <FileText className="h-10 w-10 text-muted-foreground mx-auto mb-3" />
<p className="text-sm text-[#6a6a75]">No invoices yet</p> <p className="text-sm text-muted-foreground">No invoices yet</p>
</div> </div>
) : ( ) : (
<div className="space-y-3"> <div className="space-y-3">
@@ -42,12 +42,12 @@ export default function ClientInvoices() {
<button <button
key={inv.id} key={inv.id}
onClick={() => router.push(`/client-portal/invoices/${inv.id}`)} onClick={() => router.push(`/client-portal/invoices/${inv.id}`)}
className="w-full bg-[#0d1117] border border-[#1a1a24] rounded-xl p-5 text-left hover:border-[#1BB0CE]/30 transition-all group" className="w-full bg-card border border-border rounded-xl p-5 text-left hover:border-primary/30 transition-all group"
> >
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div className="flex-1"> <div className="flex-1">
<div className="flex items-center gap-3 mb-1"> <div className="flex items-center gap-3 mb-1">
<h3 className="text-white font-semibold">{inv.invoice_number}</h3> <h3 className="text-foreground font-semibold">{inv.invoice_number}</h3>
<span className={`text-[10px] font-medium px-2 py-0.5 rounded-full ${ <span className={`text-[10px] font-medium px-2 py-0.5 rounded-full ${
inv.status_name === "Paid" ? "bg-emerald-500/10 text-emerald-400" : inv.status_name === "Paid" ? "bg-emerald-500/10 text-emerald-400" :
inv.status_name === "Overdue" ? "bg-red-500/10 text-red-400" : inv.status_name === "Overdue" ? "bg-red-500/10 text-red-400" :
@@ -57,17 +57,17 @@ export default function ClientInvoices() {
</span> </span>
</div> </div>
<div className="flex gap-4 mt-1"> <div className="flex gap-4 mt-1">
<span className="text-sm text-white font-medium"> <span className="text-sm text-foreground font-medium">
{inv.currency} {Number(inv.total_amount).toLocaleString()} {inv.currency} {Number(inv.total_amount).toLocaleString()}
</span> </span>
{inv.issued_date && ( {inv.issued_date && (
<span className="text-[11px] text-[#6a6a75]"> <span className="text-[11px] text-muted-foreground">
Issued: {new Date(inv.issued_date).toLocaleDateString()} Issued: {new Date(inv.issued_date).toLocaleDateString()}
</span> </span>
)} )}
</div> </div>
</div> </div>
<ChevronRight className="h-5 w-5 text-[#2a2a35] group-hover:text-[#1BB0CE] transition-colors" /> <ChevronRight className="h-5 w-5 text-muted-foreground group-hover:text-primary transition-colors" />
</div> </div>
</button> </button>
))} ))}
+45 -17
View File
@@ -2,7 +2,9 @@
import { useEffect, useState } from "react" import { useEffect, useState } from "react"
import { useRouter, usePathname } from "next/navigation" import { useRouter, usePathname } from "next/navigation"
import { Home, FolderOpen, FileText, LifeBuoy, User, Activity, Receipt, LogOut, Menu, X } from "lucide-react" import { Home, FolderOpen, FileText, LifeBuoy, User, Activity, Receipt, Settings, LogOut, Menu, X, Sun, Moon } from "lucide-react"
import { useTheme } from "next-themes"
import { useWebsiteTheme } from "@/providers/website-theme-provider"
const navItems = [ const navItems = [
{ href: "/client-portal/dashboard", label: "Dashboard", icon: Home }, { href: "/client-portal/dashboard", label: "Dashboard", icon: Home },
@@ -12,6 +14,7 @@ const navItems = [
{ href: "/client-portal/support", label: "Support", icon: LifeBuoy }, { href: "/client-portal/support", label: "Support", icon: LifeBuoy },
{ href: "/client-portal/activity", label: "Activity", icon: Activity }, { href: "/client-portal/activity", label: "Activity", icon: Activity },
{ href: "/client-portal/profile", label: "Profile", icon: User }, { href: "/client-portal/profile", label: "Profile", icon: User },
{ href: "/client-portal/settings", label: "Settings", icon: Settings },
] ]
export default function ClientPortalLayout({ children }: { children: React.ReactNode }) { export default function ClientPortalLayout({ children }: { children: React.ReactNode }) {
@@ -19,6 +22,11 @@ export default function ClientPortalLayout({ children }: { children: React.React
const pathname = usePathname() const pathname = usePathname()
const [auth, setAuth] = useState<boolean | null>(null) const [auth, setAuth] = useState<boolean | null>(null)
const [sidebarOpen, setSidebarOpen] = useState(false) const [sidebarOpen, setSidebarOpen] = useState(false)
const { theme, setTheme } = useTheme()
const { websiteTheme, setWebsiteTheme } = useWebsiteTheme()
const [mounted, setMounted] = useState(false)
useEffect(() => { setMounted(true) }, [])
useEffect(() => { useEffect(() => {
fetch("/api/auth/me", { credentials: "include" }) fetch("/api/auth/me", { credentials: "include" })
@@ -39,8 +47,8 @@ export default function ClientPortalLayout({ children }: { children: React.React
if (auth === null) { if (auth === null) {
return ( return (
<div className="flex min-h-screen bg-[#0a0a0f] items-center justify-center"> <div className="flex min-h-screen bg-background items-center justify-center">
<div className="w-8 h-8 border-2 border-[#1BB0CE] border-t-transparent rounded-full animate-spin" /> <div className="w-8 h-8 border-2 border-primary border-t-transparent rounded-full animate-spin" />
</div> </div>
) )
} }
@@ -48,18 +56,18 @@ export default function ClientPortalLayout({ children }: { children: React.React
if (!auth) return null if (!auth) return null
return ( return (
<div className="flex min-h-screen bg-[#0a0a0f]"> <div className="flex min-h-screen bg-background">
{/* Mobile overlay */} {/* Mobile overlay */}
{sidebarOpen && ( {sidebarOpen && (
<div className="fixed inset-0 bg-black/50 z-40 lg:hidden" onClick={() => setSidebarOpen(false)} /> <div className="fixed inset-0 bg-black/50 z-40 lg:hidden" onClick={() => setSidebarOpen(false)} />
)} )}
{/* Sidebar */} {/* Sidebar */}
<aside className={`fixed lg:static inset-y-0 left-0 z-50 w-64 bg-[#0d1117] border-r border-[#1a1a24] transform transition-transform duration-200 ${sidebarOpen ? "translate-x-0" : "-translate-x-full"} lg:translate-x-0 flex flex-col`}> <aside className={`fixed lg:static inset-y-0 left-0 z-50 w-64 bg-card border-r border-border transform transition-transform duration-200 ${sidebarOpen ? "translate-x-0" : "-translate-x-full"} lg:translate-x-0 flex flex-col`}>
<div className="p-5 border-b border-[#1a1a24]"> <div className="p-5 border-b border-border">
<div className="text-lg font-bold"> <div className="text-lg font-bold">
<span className="text-[#1BB0CE]">Client</span> <span className="text-primary">Client</span>
<span className="text-white"> Portal</span> <span className="text-foreground"> Portal</span>
</div> </div>
</div> </div>
@@ -73,8 +81,8 @@ export default function ClientPortalLayout({ children }: { children: React.React
onClick={() => { router.push(item.href); setSidebarOpen(false) }} onClick={() => { router.push(item.href); setSidebarOpen(false) }}
className={`w-full flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm transition-all ${ className={`w-full flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm transition-all ${
active active
? "bg-[#1BB0CE]/10 text-[#1BB0CE] border border-[#1BB0CE]/20" ? "bg-primary/10 text-primary border border-primary/20"
: "text-[#8a8a95] hover:text-white hover:bg-[#1a1a24]" : "text-muted-foreground hover:text-accent-foreground hover:bg-accent"
}`} }`}
> >
<Icon className="h-4 w-4" /> <Icon className="h-4 w-4" />
@@ -84,10 +92,10 @@ export default function ClientPortalLayout({ children }: { children: React.React
})} })}
</nav> </nav>
<div className="p-3 border-t border-[#1a1a24]"> <div className="p-3 border-t border-border">
<button <button
onClick={handleLogout} onClick={handleLogout}
className="w-full flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm text-[#8a8a95] hover:text-red-400 hover:bg-red-400/5 transition-all" className="w-full flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm text-muted-foreground hover:text-destructive hover:bg-destructive/5 transition-all"
> >
<LogOut className="h-4 w-4" /> <LogOut className="h-4 w-4" />
Logout Logout
@@ -97,11 +105,31 @@ export default function ClientPortalLayout({ children }: { children: React.React
{/* Main content */} {/* Main content */}
<div className="flex-1 flex flex-col min-h-screen"> <div className="flex-1 flex flex-col min-h-screen">
<header className="sticky top-0 z-30 bg-[#0d1117]/80 backdrop-blur-md border-b border-[#1a1a24] px-4 lg:px-6 h-14 flex items-center"> <header className="sticky top-0 z-30 bg-card/80 backdrop-blur-md border-b border-border px-4 lg:px-6 h-14 flex items-center justify-between">
<button className="lg:hidden mr-3 text-[#8a8a95] hover:text-white" onClick={() => setSidebarOpen(true)}> <div className="flex items-center">
<Menu className="h-5 w-5" /> <button className="lg:hidden mr-3 text-muted-foreground hover:text-accent-foreground" onClick={() => setSidebarOpen(true)}>
</button> <Menu className="h-5 w-5" />
<span className="text-sm text-[#6a6a75]">Client Portal</span> </button>
<span className="text-sm text-muted-foreground">Client Portal</span>
</div>
<div className="flex items-center gap-2">
{mounted && (
<button
onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
className="p-2 rounded-lg text-muted-foreground hover:text-accent-foreground hover:bg-accent transition-all"
title={`Switch to ${theme === "dark" ? "light" : "dark"} mode`}
>
{theme === "dark" ? <Sun className="h-4 w-4" /> : <Moon className="h-4 w-4" />}
</button>
)}
<button
onClick={() => router.push("/client-portal/settings")}
className="p-2 rounded-lg text-muted-foreground hover:text-accent-foreground hover:bg-accent transition-all"
title="Theme settings"
>
<Settings className="h-4 w-4" />
</button>
</div>
</header> </header>
<main className="flex-1 p-4 lg:p-6"> <main className="flex-1 p-4 lg:p-6">
+14 -14
View File
@@ -66,51 +66,51 @@ export default function ClientLoginPage() {
if (checking) { if (checking) {
return ( return (
<div className="flex min-h-screen bg-[#0a0a0f] items-center justify-center"> <div className="flex min-h-screen bg-background items-center justify-center">
<div className="w-8 h-8 border-2 border-[#1BB0CE] border-t-transparent rounded-full animate-spin" /> <div className="w-8 h-8 border-2 border-primary border-t-transparent rounded-full animate-spin" />
</div> </div>
) )
} }
return ( return (
<div className="flex min-h-screen bg-[#0a0a0f] items-center justify-center p-4"> <div className="flex min-h-screen bg-background items-center justify-center p-4">
<div className="w-full max-w-sm"> <div className="w-full max-w-sm">
<div className="text-center mb-8"> <div className="text-center mb-8">
<div className="text-2xl font-bold mb-1"> <div className="text-2xl font-bold mb-1">
<span className="text-[#1BB0CE]">Client</span> <span className="text-primary">Client</span>
<span className="text-white"> Portal</span> <span className="text-foreground"> Portal</span>
</div> </div>
<p className="text-sm text-[#6a6a75]">Sign in to view your projects and invoices</p> <p className="text-sm text-muted-foreground">Sign in to view your projects and invoices</p>
</div> </div>
<form onSubmit={handleSubmit} className="space-y-4"> <form onSubmit={handleSubmit} className="space-y-4">
<div> <div>
<label className="block text-sm text-[#8a8a95] mb-1.5">Email</label> <label className="block text-sm text-muted-foreground mb-1.5">Email</label>
<input <input
type="email" type="email"
value={email} value={email}
onChange={(e) => setEmail(e.target.value)} onChange={(e) => setEmail(e.target.value)}
className="w-full bg-[#1a1a24] border border-[#2a2a35] rounded-lg px-3 py-2.5 text-sm text-white placeholder-[#6a6a75] outline-none focus:border-[#1BB0CE]/50" className="w-full bg-muted border border-border rounded-lg px-3 py-2.5 text-sm text-foreground placeholder-muted-foreground outline-none focus:border-primary/50"
placeholder="your@email.com" placeholder="your@email.com"
required required
/> />
</div> </div>
<div> <div>
<label className="block text-sm text-[#8a8a95] mb-1.5">Password</label> <label className="block text-sm text-muted-foreground mb-1.5">Password</label>
<div className="relative"> <div className="relative">
<input <input
type={showPassword ? "text" : "password"} type={showPassword ? "text" : "password"}
value={password} value={password}
onChange={(e) => setPassword(e.target.value)} onChange={(e) => setPassword(e.target.value)}
className="w-full bg-[#1a1a24] border border-[#2a2a35] rounded-lg px-3 py-2.5 pr-10 text-sm text-white placeholder-[#6a6a75] outline-none focus:border-[#1BB0CE]/50" className="w-full bg-muted border border-border rounded-lg px-3 py-2.5 pr-10 text-sm text-foreground placeholder-muted-foreground outline-none focus:border-primary/50"
placeholder="Enter password" placeholder="Enter password"
required required
/> />
<button <button
type="button" type="button"
onClick={() => setShowPassword(!showPassword)} onClick={() => setShowPassword(!showPassword)}
className="absolute right-3 top-1/2 -translate-y-1/2 text-[#6a6a75] hover:text-white" className="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-accent-foreground"
> >
{showPassword ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />} {showPassword ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
</button> </button>
@@ -119,8 +119,8 @@ export default function ClientLoginPage() {
{error && ( {error && (
<div className={"text-sm bg-opacity-10 border rounded-lg px-3 py-2 " + (error === "Login successful! Redirecting..." <div className={"text-sm bg-opacity-10 border rounded-lg px-3 py-2 " + (error === "Login successful! Redirecting..."
? "text-[#1BB0CE] bg-[#1BB0CE]/5 border-[#1BB0CE]/20" ? "text-primary bg-primary/5 border-primary/20"
: "text-red-400 bg-red-400/5 border-red-400/10")}> : "text-destructive bg-destructive/5 border-destructive/10")}>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{error !== "Login successful! Redirecting..." && <AlertCircle className="h-4 w-4 shrink-0" />} {error !== "Login successful! Redirecting..." && <AlertCircle className="h-4 w-4 shrink-0" />}
<span className="font-medium">{error}</span> <span className="font-medium">{error}</span>
@@ -132,7 +132,7 @@ export default function ClientLoginPage() {
<button <button
type="submit" type="submit"
disabled={loading} disabled={loading}
className="w-full bg-[#1BB0CE] hover:bg-[#1BB0CE]/80 text-white rounded-lg px-4 py-2.5 text-sm font-medium transition-all disabled:opacity-40" className="w-full bg-primary hover:bg-primary/80 text-primary-foreground rounded-lg px-4 py-2.5 text-sm font-medium transition-all disabled:opacity-40"
> >
{loading ? "Signing in..." : "Sign In"} {loading ? "Signing in..." : "Sign In"}
</button> </button>
+41 -41
View File
@@ -46,7 +46,7 @@ export default function ClientProfile() {
if (!profile) { if (!profile) {
return ( return (
<div className="flex items-center justify-center h-32"> <div className="flex items-center justify-center h-32">
<div className="w-6 h-6 border-2 border-[#1BB0CE] border-t-transparent rounded-full animate-spin" /> <div className="w-6 h-6 border-2 border-primary border-t-transparent rounded-full animate-spin" />
</div> </div>
) )
} }
@@ -56,24 +56,24 @@ export default function ClientProfile() {
return ( return (
<div> <div>
<h1 className="text-xl font-bold text-white mb-1">Profile</h1> <h1 className="text-xl font-bold text-foreground mb-1">Profile</h1>
<p className="text-sm text-[#6a6a75] mb-6">Your account and company information</p> <p className="text-sm text-muted-foreground mb-6">Your account and company information</p>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6"> <div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Main info card */} {/* Main info card */}
<div className="lg:col-span-2 space-y-4"> <div className="lg:col-span-2 space-y-4">
<div className="bg-[#0d1117] border border-[#1a1a24] rounded-xl p-6"> <div className="bg-card border border-border rounded-xl p-6">
<div className="flex items-center gap-4 mb-5"> <div className="flex items-center gap-4 mb-5">
<div className="w-14 h-14 rounded-xl bg-[#1BB0CE]/10 flex items-center justify-center"> <div className="w-14 h-14 rounded-xl bg-primary/10 flex items-center justify-center">
<Building2 className="h-7 w-7 text-[#1BB0CE]" /> <Building2 className="h-7 w-7 text-primary" />
</div> </div>
<div> <div>
<h2 className="text-lg font-semibold text-white">{name || "Unnamed"}</h2> <h2 className="text-lg font-semibold text-foreground">{name || "Unnamed"}</h2>
<div className="flex items-center gap-2 mt-1"> <div className="flex items-center gap-2 mt-1">
<span className="text-[11px] px-2 py-0.5 rounded-full" style={{ backgroundColor: `${profile.status_color}15`, color: profile.status_color }}> <span className="text-[11px] px-2 py-0.5 rounded-full" style={{ backgroundColor: `${profile.status_color}15`, color: profile.status_color }}>
{profile.status_name} {profile.status_name}
</span> </span>
<span className="text-[11px] text-[#6a6a75]">{isCompany ? "Company" : "Individual"}</span> <span className="text-[11px] text-muted-foreground">{isCompany ? "Company" : "Individual"}</span>
</div> </div>
</div> </div>
</div> </div>
@@ -81,64 +81,64 @@ export default function ClientProfile() {
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4"> <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
{isCompany && profile.industry && ( {isCompany && profile.industry && (
<div className="flex items-center gap-3 text-sm"> <div className="flex items-center gap-3 text-sm">
<Hash className="h-4 w-4 text-[#6a6a75]" /> <Hash className="h-4 w-4 text-muted-foreground" />
<div> <div>
<p className="text-[#6a6a75] text-[11px]">Industry</p> <p className="text-muted-foreground text-[11px]">Industry</p>
<p className="text-white">{profile.industry}</p> <p className="text-foreground">{profile.industry}</p>
</div> </div>
</div> </div>
)} )}
{isCompany && profile.registration_number && ( {isCompany && profile.registration_number && (
<div className="flex items-center gap-3 text-sm"> <div className="flex items-center gap-3 text-sm">
<FileText className="h-4 w-4 text-[#6a6a75]" /> <FileText className="h-4 w-4 text-muted-foreground" />
<div> <div>
<p className="text-[#6a6a75] text-[11px]">Registration</p> <p className="text-muted-foreground text-[11px]">Registration</p>
<p className="text-white">{profile.registration_number}</p> <p className="text-foreground">{profile.registration_number}</p>
</div> </div>
</div> </div>
)} )}
{profile.tax_id && ( {profile.tax_id && (
<div className="flex items-center gap-3 text-sm"> <div className="flex items-center gap-3 text-sm">
<FileText className="h-4 w-4 text-[#6a6a75]" /> <FileText className="h-4 w-4 text-muted-foreground" />
<div> <div>
<p className="text-[#6a6a75] text-[11px]">Tax / VAT</p> <p className="text-muted-foreground text-[11px]">Tax / VAT</p>
<p className="text-white">{profile.tax_id}</p> <p className="text-foreground">{profile.tax_id}</p>
</div> </div>
</div> </div>
)} )}
{isCompany && profile.company_size && ( {isCompany && profile.company_size && (
<div className="flex items-center gap-3 text-sm"> <div className="flex items-center gap-3 text-sm">
<Users className="h-4 w-4 text-[#6a6a75]" /> <Users className="h-4 w-4 text-muted-foreground" />
<div> <div>
<p className="text-[#6a6a75] text-[11px]">Company Size</p> <p className="text-muted-foreground text-[11px]">Company Size</p>
<p className="text-white">{profile.company_size}</p> <p className="text-foreground">{profile.company_size}</p>
</div> </div>
</div> </div>
)} )}
{isCompany && profile.annual_revenue && ( {isCompany && profile.annual_revenue && (
<div className="flex items-center gap-3 text-sm"> <div className="flex items-center gap-3 text-sm">
<DollarSign className="h-4 w-4 text-[#6a6a75]" /> <DollarSign className="h-4 w-4 text-muted-foreground" />
<div> <div>
<p className="text-[#6a6a75] text-[11px]">Annual Revenue</p> <p className="text-muted-foreground text-[11px]">Annual Revenue</p>
<p className="text-white">{Number(profile.annual_revenue).toLocaleString()}</p> <p className="text-foreground">{Number(profile.annual_revenue).toLocaleString()}</p>
</div> </div>
</div> </div>
)} )}
{profile.website && ( {profile.website && (
<div className="flex items-center gap-3 text-sm"> <div className="flex items-center gap-3 text-sm">
<Globe className="h-4 w-4 text-[#6a6a75]" /> <Globe className="h-4 w-4 text-muted-foreground" />
<div> <div>
<p className="text-[#6a6a75] text-[11px]">Website</p> <p className="text-muted-foreground text-[11px]">Website</p>
<p className="text-[#1BB0CE]">{profile.website}</p> <p className="text-primary">{profile.website}</p>
</div> </div>
</div> </div>
)} )}
{!isCompany && profile.job_title && ( {!isCompany && profile.job_title && (
<div className="flex items-center gap-3 text-sm"> <div className="flex items-center gap-3 text-sm">
<Hash className="h-4 w-4 text-[#6a6a75]" /> <Hash className="h-4 w-4 text-muted-foreground" />
<div> <div>
<p className="text-[#6a6a75] text-[11px]">Job Title</p> <p className="text-muted-foreground text-[11px]">Job Title</p>
<p className="text-white">{profile.job_title}</p> <p className="text-foreground">{profile.job_title}</p>
</div> </div>
</div> </div>
)} )}
@@ -146,9 +146,9 @@ export default function ClientProfile() {
</div> </div>
{profile.customer_notes && ( {profile.customer_notes && (
<div className="bg-[#0d1117] border border-[#1a1a24] rounded-xl p-5"> <div className="bg-card border border-border rounded-xl p-5">
<h3 className="text-sm font-medium text-white mb-2">Notes</h3> <h3 className="text-sm font-medium text-foreground mb-2">Notes</h3>
<p className="text-sm text-[#8a8a95]">{profile.customer_notes}</p> <p className="text-sm text-muted-foreground">{profile.customer_notes}</p>
</div> </div>
)} )}
</div> </div>
@@ -156,24 +156,24 @@ export default function ClientProfile() {
{/* Contacts sidebar */} {/* Contacts sidebar */}
<div className="space-y-3"> <div className="space-y-3">
{(contacts || []).map((c, i) => ( {(contacts || []).map((c, i) => (
<div key={i} className="bg-[#0d1117] border border-[#1a1a24] rounded-xl p-4"> <div key={i} className="bg-card border border-border rounded-xl p-4">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
{c.type === "email" ? <Mail className="h-4 w-4 text-[#1BB0CE]" /> : {c.type === "email" ? <Mail className="h-4 w-4 text-primary" /> :
c.type === "phone" || c.type === "mobile" ? <Phone className="h-4 w-4 text-[#1BB0CE]" /> : c.type === "phone" || c.type === "mobile" ? <Phone className="h-4 w-4 text-primary" /> :
c.type === "website" ? <Globe className="h-4 w-4 text-[#1BB0CE]" /> : c.type === "website" ? <Globe className="h-4 w-4 text-primary" /> :
<Hash className="h-4 w-4 text-[#1BB0CE]" />} <Hash className="h-4 w-4 text-primary" />}
<div> <div>
<p className="text-[11px] text-[#6a6a75]">{c.label || c.type}</p> <p className="text-[11px] text-muted-foreground">{c.label || c.type}</p>
<p className="text-sm text-white">{c.value}</p> <p className="text-sm text-foreground">{c.value}</p>
</div> </div>
</div> </div>
{c.is_primary && ( {c.is_primary && (
<span className="text-[10px] text-[#1BB0CE] mt-1.5 block">Primary</span> <span className="text-[10px] text-primary mt-1.5 block">Primary</span>
)} )}
</div> </div>
))} ))}
{contacts.length === 0 && ( {contacts.length === 0 && (
<div className="text-center py-8 text-sm text-[#6a6a75] bg-[#0d1117] border border-[#1a1a24] rounded-xl"> <div className="text-center py-8 text-sm text-muted-foreground bg-card border border-border rounded-xl">
No contact info No contact info
</div> </div>
)} )}
+28 -28
View File
@@ -85,7 +85,7 @@ export default function ProjectDetail() {
if (!data) { if (!data) {
return ( return (
<div className="flex items-center justify-center h-64"> <div className="flex items-center justify-center h-64">
<div className="w-8 h-8 border-2 border-[#1BB0CE] border-t-transparent rounded-full animate-spin" /> <div className="w-8 h-8 border-2 border-primary border-t-transparent rounded-full animate-spin" />
</div> </div>
) )
} }
@@ -97,18 +97,18 @@ export default function ProjectDetail() {
return ( return (
<div> <div>
<h1 className="text-xl font-bold text-white mb-1">{project.name}</h1> <h1 className="text-xl font-bold text-foreground mb-1">{project.name}</h1>
{project.description && ( {project.description && (
<p className="text-sm text-[#8a8a95] mb-4">{project.description}</p> <p className="text-sm text-muted-foreground mb-4">{project.description}</p>
)} )}
{/* Overall progress bar */} {/* Overall progress bar */}
<div className="bg-[#0d1117] border border-[#1a1a24] rounded-xl p-5 mb-6"> <div className="bg-card border border-border rounded-xl p-5 mb-6">
<div className="flex items-center justify-between mb-2"> <div className="flex items-center justify-between mb-2">
<span className="text-sm text-[#8a8a95]">Overall Progress</span> <span className="text-sm text-muted-foreground">Overall Progress</span>
<span className="text-lg font-bold text-white">{overallProgress}%</span> <span className="text-lg font-bold text-foreground">{overallProgress}%</span>
</div> </div>
<div className="h-2 bg-[#1a1a24] rounded-full overflow-hidden"> <div className="h-2 bg-muted rounded-full overflow-hidden">
<div <div
className="h-full bg-gradient-to-r from-[#1BB0CE] to-emerald-400 rounded-full transition-all duration-500" className="h-full bg-gradient-to-r from-[#1BB0CE] to-emerald-400 rounded-full transition-all duration-500"
style={{ width: `${overallProgress}%` }} style={{ width: `${overallProgress}%` }}
@@ -125,8 +125,8 @@ export default function ProjectDetail() {
const today = new Date() const today = new Date()
return ( return (
<div className="bg-[#0d1117] border border-[#1a1a24] rounded-xl p-5 mb-6"> <div className="bg-card border border-border rounded-xl p-5 mb-6">
<h2 className="text-base font-semibold text-white mb-4">Timeline</h2> <h2 className="text-base font-semibold text-foreground mb-4">Timeline</h2>
<div className="relative"> <div className="relative">
{sorted.map((m, i) => { {sorted.map((m, i) => {
const mDate = new Date(m.due_date) const mDate = new Date(m.due_date)
@@ -138,9 +138,9 @@ export default function ProjectDetail() {
return ( return (
<div key={m.id} className="flex items-center gap-3 mb-3 last:mb-0"> <div key={m.id} className="flex items-center gap-3 mb-3 last:mb-0">
<div className="w-24 flex-shrink-0 text-right"> <div className="w-24 flex-shrink-0 text-right">
<span className="text-[10px] text-[#6a6a75]">{mDate.toLocaleDateString()}</span> <span className="text-[10px] text-muted-foreground">{mDate.toLocaleDateString()}</span>
</div> </div>
<div className="flex-1 h-7 bg-[#1a1a24] rounded-md overflow-hidden relative"> <div className="flex-1 h-7 bg-muted rounded-md overflow-hidden relative">
<div <div
className="h-full rounded-md transition-all flex items-center px-2" className="h-full rounded-md transition-all flex items-center px-2"
style={{ style={{
@@ -153,7 +153,7 @@ export default function ProjectDetail() {
{m.name} {m.name}
</span> </span>
</div> </div>
<span className="absolute right-2 top-1/2 -translate-y-1/2 text-[9px] text-[#6a6a75]"> <span className="absolute right-2 top-1/2 -translate-y-1/2 text-[9px] text-muted-foreground">
{m.progress}% {m.progress}%
</span> </span>
</div> </div>
@@ -166,10 +166,10 @@ export default function ProjectDetail() {
})()} })()}
{/* Milestones */} {/* Milestones */}
<h2 className="text-base font-semibold text-white mb-3">Milestones</h2> <h2 className="text-base font-semibold text-foreground mb-3">Milestones</h2>
<div className="space-y-2 mb-6"> <div className="space-y-2 mb-6">
{milestones.length === 0 ? ( {milestones.length === 0 ? (
<p className="text-sm text-[#6a6a75]">No milestones yet</p> <p className="text-sm text-muted-foreground">No milestones yet</p>
) : ( ) : (
(milestones || []).map((m) => { (milestones || []).map((m) => {
const si = statusIcon[m.status] || { icon: Clock, color: "#6a6a75" } const si = statusIcon[m.status] || { icon: Clock, color: "#6a6a75" }
@@ -177,48 +177,48 @@ export default function ProjectDetail() {
const milestoneDeliverables = deliverables.filter((d) => d.milestone_id === m.id) const milestoneDeliverables = deliverables.filter((d) => d.milestone_id === m.id)
return ( return (
<div key={m.id} className="bg-[#0d1117] border border-[#1a1a24] rounded-xl overflow-hidden"> <div key={m.id} className="bg-card border border-border rounded-xl overflow-hidden">
<button <button
onClick={() => toggleExpanded(m.id)} onClick={() => toggleExpanded(m.id)}
className="w-full flex items-center justify-between p-4 hover:bg-[#1a1a24]/50 transition-colors" className="w-full flex items-center justify-between p-4 hover:bg-accent/50 transition-colors"
> >
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<Icon className="h-5 w-5" style={{ color: si.color }} /> <Icon className="h-5 w-5" style={{ color: si.color }} />
<div className="text-left"> <div className="text-left">
<span className="text-white text-sm font-medium">{m.name}</span> <span className="text-foreground text-sm font-medium">{m.name}</span>
<div className="flex items-center gap-2 mt-0.5"> <div className="flex items-center gap-2 mt-0.5">
<div className="h-1.5 w-20 bg-[#1a1a24] rounded-full overflow-hidden"> <div className="h-1.5 w-20 bg-muted rounded-full overflow-hidden">
<div <div
className="h-full rounded-full transition-all" className="h-full rounded-full transition-all"
style={{ width: `${m.progress}%`, backgroundColor: si.color }} style={{ width: `${m.progress}%`, backgroundColor: si.color }}
/> />
</div> </div>
<span className="text-[10px] text-[#6a6a75]">{m.progress}%</span> <span className="text-[10px] text-muted-foreground">{m.progress}%</span>
</div> </div>
</div> </div>
</div> </div>
{expanded.includes(m.id) ? ( {expanded.includes(m.id) ? (
<ChevronUp className="h-4 w-4 text-[#6a6a75]" /> <ChevronUp className="h-4 w-4 text-muted-foreground" />
) : ( ) : (
<ChevronDown className="h-4 w-4 text-[#6a6a75]" /> <ChevronDown className="h-4 w-4 text-muted-foreground" />
)} )}
</button> </button>
{expanded.includes(m.id) && ( {expanded.includes(m.id) && (
<div className="px-4 pb-4 border-t border-[#1a1a24] pt-3"> <div className="px-4 pb-4 border-t border-border pt-3">
{m.description && ( {m.description && (
<p className="text-sm text-[#8a8a95] mb-3">{m.description}</p> <p className="text-sm text-muted-foreground mb-3">{m.description}</p>
)} )}
{/* Deliverables */} {/* Deliverables */}
{milestoneDeliverables.length > 0 && ( {milestoneDeliverables.length > 0 && (
<div className="space-y-2 mb-3"> <div className="space-y-2 mb-3">
<span className="text-[11px] text-[#6a6a75] uppercase tracking-wider">Deliverables</span> <span className="text-[11px] text-muted-foreground uppercase tracking-wider">Deliverables</span>
{(milestoneDeliverables || []).map((d) => ( {(milestoneDeliverables || []).map((d) => (
<div key={d.id} className="flex items-center justify-between bg-[#1a1a24] rounded-lg px-3 py-2"> <div key={d.id} className="flex items-center justify-between bg-muted rounded-lg px-3 py-2">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<FileText className="h-3.5 w-3.5 text-[#6a6a75]" /> <FileText className="h-3.5 w-3.5 text-muted-foreground" />
<span className="text-sm text-white">{d.name}</span> <span className="text-sm text-foreground">{d.name}</span>
<span className={`text-[10px] px-1.5 py-0.5 rounded ${ <span className={`text-[10px] px-1.5 py-0.5 rounded ${
d.status === "approved" ? "bg-emerald-500/10 text-emerald-400" : d.status === "approved" ? "bg-emerald-500/10 text-emerald-400" :
d.status === "rejected" ? "bg-red-500/10 text-red-400" : d.status === "rejected" ? "bg-red-500/10 text-red-400" :
@@ -228,7 +228,7 @@ export default function ProjectDetail() {
</span> </span>
</div> </div>
{d.file_url && ( {d.file_url && (
<a href={d.file_url} target="_blank" className="text-[#1BB0CE] hover:underline text-xs flex items-center gap-1"> <a href={d.file_url} target="_blank" className="text-primary hover:underline text-xs flex items-center gap-1">
<ExternalLink className="h-3 w-3" /> View <ExternalLink className="h-3 w-3" /> View
</a> </a>
)} )}
+9 -9
View File
@@ -28,13 +28,13 @@ export default function ClientProjects() {
return ( return (
<div> <div>
<h1 className="text-xl font-bold text-white mb-1">Projects</h1> <h1 className="text-xl font-bold text-foreground mb-1">Projects</h1>
<p className="text-sm text-[#6a6a75] mb-6">View your project milestones and progress</p> <p className="text-sm text-muted-foreground mb-6">View your project milestones and progress</p>
{projects.length === 0 ? ( {projects.length === 0 ? (
<div className="text-center py-16"> <div className="text-center py-16">
<FolderOpen className="h-10 w-10 text-[#2a2a35] mx-auto mb-3" /> <FolderOpen className="h-10 w-10 text-muted/50 mx-auto mb-3" />
<p className="text-sm text-[#6a6a75]">No projects yet</p> <p className="text-sm text-muted-foreground">No projects yet</p>
</div> </div>
) : ( ) : (
<div className="space-y-3"> <div className="space-y-3">
@@ -42,25 +42,25 @@ export default function ClientProjects() {
<button <button
key={p.id} key={p.id}
onClick={() => router.push(`/client-portal/projects/${p.id}`)} onClick={() => router.push(`/client-portal/projects/${p.id}`)}
className="w-full bg-[#0d1117] border border-[#1a1a24] rounded-xl p-5 text-left hover:border-[#1BB0CE]/30 transition-all group" className="w-full bg-card border border-border rounded-xl p-5 text-left hover:border-primary/30 transition-all group"
> >
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div className="flex-1"> <div className="flex-1">
<div className="flex items-center gap-3 mb-1"> <div className="flex items-center gap-3 mb-1">
<h3 className="text-white font-semibold">{p.name}</h3> <h3 className="text-foreground font-semibold">{p.name}</h3>
<span className="text-[10px] font-medium px-2 py-0.5 rounded-full" style={{ backgroundColor: `${p.status_color}15`, color: p.status_color }}> <span className="text-[10px] font-medium px-2 py-0.5 rounded-full" style={{ backgroundColor: `${p.status_color}15`, color: p.status_color }}>
{(p.status_name || "unknown").replace("_", " ")} {(p.status_name || "unknown").replace("_", " ")}
</span> </span>
</div> </div>
{p.description && ( {p.description && (
<p className="text-sm text-[#8a8a95] line-clamp-1">{p.description}</p> <p className="text-sm text-muted-foreground line-clamp-1">{p.description}</p>
)} )}
<div className="flex gap-4 mt-2 text-[11px] text-[#6a6a75]"> <div className="flex gap-4 mt-2 text-[11px] text-muted-foreground">
{p.start_date && <span>Start: {new Date(p.start_date).toLocaleDateString()}</span>} {p.start_date && <span>Start: {new Date(p.start_date).toLocaleDateString()}</span>}
{p.end_date && <span>Due: {new Date(p.end_date).toLocaleDateString()}</span>} {p.end_date && <span>Due: {new Date(p.end_date).toLocaleDateString()}</span>}
</div> </div>
</div> </div>
<ChevronRight className="h-5 w-5 text-[#2a2a35] group-hover:text-[#1BB0CE] transition-colors" /> <ChevronRight className="h-5 w-5 text-muted/50 group-hover:text-primary transition-colors" />
</div> </div>
</button> </button>
))} ))}
+14 -14
View File
@@ -25,32 +25,32 @@ export default function ClientReceipts() {
return ( return (
<div> <div>
<h1 className="text-xl font-bold text-white mb-1">Receipts</h1> <h1 className="text-xl font-bold text-foreground mb-1">Receipts</h1>
<p className="text-sm text-[#6a6a75] mb-6">View all your payment receipts in one place</p> <p className="text-sm text-muted-foreground mb-6">View all your payment receipts in one place</p>
{receipts.length === 0 ? ( {receipts.length === 0 ? (
<div className="text-center py-16"> <div className="text-center py-16">
<FileText className="h-10 w-10 text-[#2a2a35] mx-auto mb-3" /> <FileText className="h-10 w-10 text-muted-foreground mx-auto mb-3" />
<p className="text-sm text-[#6a6a75]">No receipts yet</p> <p className="text-sm text-muted-foreground">No receipts yet</p>
</div> </div>
) : ( ) : (
<div className="space-y-3"> <div className="space-y-3">
{(receipts || []).map((r) => ( {(receipts || []).map((r) => (
<div key={r.id} className="bg-[#0d1117] border border-[#1a1a24] rounded-xl p-5 flex items-center justify-between hover:border-[#1BB0CE]/30 transition-all"> <div key={r.id} className="bg-card border border-border rounded-xl p-5 flex items-center justify-between hover:border-primary/30 transition-all">
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<div className="w-10 h-10 rounded-lg bg-[#1BB0CE]/10 flex items-center justify-center"> <div className="w-10 h-10 rounded-lg bg-primary/10 flex items-center justify-center">
<FileText className="h-5 w-5 text-[#1BB0CE]" /> <FileText className="h-5 w-5 text-primary" />
</div> </div>
<div> <div>
<p className="text-white font-medium">{r.receipt_number}</p> <p className="text-foreground font-medium">{r.receipt_number}</p>
<div className="flex items-center gap-3 mt-0.5"> <div className="flex items-center gap-3 mt-0.5">
<span className="text-xs text-[#6a6a75]">{r.invoice_number}</span> <span className="text-xs text-muted-foreground">{r.invoice_number}</span>
<span className="text-xs text-[#2a2a35]">&middot;</span> <span className="text-xs text-muted-foreground">&middot;</span>
<span className="text-xs text-[#8a8a95]"> <span className="text-xs text-muted-foreground">
{r.currency} {Number(r.total_amount).toLocaleString()} {r.currency} {Number(r.total_amount).toLocaleString()}
</span> </span>
<span className="text-xs text-[#2a2a35]">&middot;</span> <span className="text-xs text-muted-foreground">&middot;</span>
<span className="text-xs text-[#6a6a75]"> <span className="text-xs text-muted-foreground">
{new Date(r.issued_at).toLocaleDateString()} {new Date(r.issued_at).toLocaleDateString()}
</span> </span>
</div> </div>
@@ -60,7 +60,7 @@ export default function ClientReceipts() {
<a <a
href={r.file_url} href={r.file_url}
target="_blank" target="_blank"
className="flex items-center gap-1.5 text-xs text-[#1BB0CE] hover:underline px-3 py-1.5 border border-[#1BB0CE]/20 rounded-lg hover:bg-[#1BB0CE]/5 transition-all" className="flex items-center gap-1.5 text-xs text-primary hover:underline px-3 py-1.5 border border-primary/20 rounded-lg hover:bg-primary/5 transition-all"
> >
<Download className="h-3.5 w-3.5" /> <Download className="h-3.5 w-3.5" />
Download Download
+156
View File
@@ -0,0 +1,156 @@
"use client"
import { useEffect, useState } from "react"
import { useTheme } from "next-themes"
import { useWebsiteTheme } from "@/providers/website-theme-provider"
import { Sun, Moon, Monitor, Shield, Palette } from "lucide-react"
const modeOptions = [
{ value: "light", icon: Sun, label: "Light" },
{ value: "dark", icon: Moon, label: "Dark" },
{ value: "system", icon: Monitor, label: "System" },
]
const COLOR_THEME_KEY = "crm-color-theme"
const colorOptions = [
{ value: "default", label: "Default", color: "bg-blue-600", ring: "ring-blue-600" },
{ value: "ocean", label: "Ocean", color: "bg-teal-500", ring: "ring-teal-500" },
{ value: "forest", label: "Forest", color: "bg-green-600", ring: "ring-green-600" },
{ value: "sunset", label: "Sunset", color: "bg-orange-500", ring: "ring-orange-500" },
{ value: "midnight", label: "Midnight", color: "bg-indigo-600", ring: "ring-indigo-600" },
{ value: "rose", label: "Rose", color: "bg-pink-600", ring: "ring-pink-600" },
{ value: "amber", label: "Amber", color: "bg-amber-500", ring: "ring-amber-500" },
{ value: "violet", label: "Violet", color: "bg-violet-600", ring: "ring-violet-600" },
{ value: "slate", label: "Slate", color: "bg-slate-500", ring: "ring-slate-500" },
{ value: "ruby", label: "Ruby", color: "bg-red-600", ring: "ring-red-600" },
]
const bgOptions = [
{ value: "default", label: "Default", color: "bg-zinc-500", ring: "ring-zinc-500", desc: "Standard CRM appearance" },
{ value: "spidey", label: "Spidey", color: "bg-red-600", ring: "ring-red-600", desc: "Dark theme with red accents" },
{ value: "cosmic", label: "Cosmic", color: "bg-gradient-to-r from-purple-500 to-green-500", ring: "ring-purple-500", desc: "Deep space with purple nebula" },
{ value: "cyberpunk", label: "Cyberpunk", color: "bg-gradient-to-r from-pink-500 to-cyan-400", ring: "ring-pink-500", desc: "Synthwave aesthetic" },
{ value: "cyber2", label: "Cyber 2.0", color: "bg-gradient-to-r from-cyan-400 to-fuchsia-500", ring: "ring-cyan-400", desc: "Neon grid with glow" },
{ value: "pumpkin", label: "Pumpkin Spice", color: "bg-gradient-to-r from-orange-600 to-amber-500", ring: "ring-orange-600", desc: "Warm autumn orange" },
{ value: "bw", label: "Black & White", color: "bg-gradient-to-r from-gray-900 to-gray-400", ring: "ring-gray-500", desc: "Clean monochrome" },
]
function getStoredColorTheme(): string {
if (typeof window === "undefined") return "default"
return localStorage.getItem(COLOR_THEME_KEY) || "default"
}
function applyColorTheme(theme: string) {
const colorThemes = ["default", "ocean", "forest", "sunset", "midnight", "rose", "amber", "violet", "slate", "ruby"]
const classes = document.documentElement.className.split(" ").filter((c) => !colorThemes.includes(c))
if (theme !== "default") classes.push(theme)
document.documentElement.className = classes.join(" ").trim()
localStorage.setItem(COLOR_THEME_KEY, theme)
}
export default function ClientSettings() {
const { theme, setTheme } = useTheme()
const { websiteTheme, setWebsiteTheme } = useWebsiteTheme()
const [colorTheme, setColorTheme] = useState("default")
const [mounted, setMounted] = useState(false)
useEffect(() => {
setMounted(true)
setColorTheme(getStoredColorTheme())
applyColorTheme(getStoredColorTheme())
}, [])
function handleColorChange(value: string) {
setColorTheme(value)
applyColorTheme(value)
}
if (!mounted) return null
return (
<div>
<h1 className="text-xl font-bold text-foreground mb-1">Settings</h1>
<p className="text-sm text-muted-foreground mb-6">Customize your portal appearance</p>
<div className="space-y-6 max-w-2xl">
{/* Theme Mode */}
<div className="bg-card border border-border rounded-xl p-6">
<h2 className="text-sm font-semibold text-foreground mb-1">Theme Mode</h2>
<p className="text-xs text-muted-foreground mb-4">Light, dark, or system default</p>
<div className="grid grid-cols-3 gap-3">
{modeOptions.map(({ value, icon: Icon, label }) => {
const active = theme === value
return (
<button
key={value}
onClick={() => setTheme(value)}
className={`flex flex-col items-center gap-2 rounded-lg border-2 p-4 transition-all ${
active
? "border-primary bg-primary/5"
: "border-border hover:border-border/80 text-muted-foreground hover:text-accent-foreground"
}`}
>
<Icon className={`h-5 w-5 ${active ? "text-primary" : ""}`} />
<span className={`text-sm font-medium ${active ? "text-primary" : ""}`}>{label}</span>
</button>
)
})}
</div>
</div>
{/* Color Accent */}
<div className="bg-card border border-border rounded-xl p-6">
<h2 className="text-sm font-semibold text-foreground mb-1">Color Accent</h2>
<p className="text-xs text-muted-foreground mb-4">Dashboard accent color</p>
<div className="grid grid-cols-5 gap-3">
{colorOptions.map(({ value, label, color, ring }) => {
const active = colorTheme === value
return (
<button
key={value}
onClick={() => handleColorChange(value)}
title={label}
className={`flex flex-col items-center gap-2 rounded-lg border-2 p-3 transition-all ${
active ? "border-primary bg-primary/5" : "border-border hover:border-border/80"
}`}
>
<div className={`h-6 w-6 rounded-full ${color} ${ring} ring-2 ring-offset-2 ring-offset-card`} />
<span className={`text-[11px] ${active ? "text-primary" : "text-muted-foreground"}`}>{label}</span>
</button>
)
})}
</div>
</div>
{/* Background Theme */}
<div className="bg-card border border-border rounded-xl p-6">
<h2 className="text-sm font-semibold text-foreground mb-1">Background Theme</h2>
<p className="text-xs text-muted-foreground mb-4">Choose a background style</p>
<div className="grid grid-cols-2 gap-3">
{bgOptions.map(({ value, label, color, ring, desc }) => {
const active = websiteTheme === value
return (
<button
key={value}
onClick={() => setWebsiteTheme(value)}
className={`flex items-center gap-3 rounded-lg border-2 p-4 transition-all text-left ${
active
? "border-primary bg-primary/5"
: "border-border hover:border-border/80"
}`}
>
<div className={`h-8 w-8 shrink-0 rounded-full ${color} ${ring} ring-2 ring-offset-2 ring-offset-card`} />
<div>
<p className={`text-sm font-medium ${active ? "text-primary" : "text-foreground"}`}>{label}</p>
<p className={`text-[11px] ${active ? "text-primary/60" : "text-muted-foreground"}`}>{desc}</p>
</div>
</button>
)
})}
</div>
</div>
</div>
</div>
)
}
+21 -21
View File
@@ -75,12 +75,12 @@ export default function ClientSupport() {
<div> <div>
<div className="flex items-center justify-between mb-6"> <div className="flex items-center justify-between mb-6">
<div> <div>
<h1 className="text-xl font-bold text-white mb-1">Support</h1> <h1 className="text-xl font-bold text-foreground mb-1">Support</h1>
<p className="text-sm text-[#6a6a75]">Submit and track support requests</p> <p className="text-sm text-muted-foreground">Submit and track support requests</p>
</div> </div>
<button <button
onClick={() => setShowForm(!showForm)} onClick={() => setShowForm(!showForm)}
className="bg-[#1BB0CE] hover:bg-[#1BB0CE]/80 text-white text-sm px-4 py-2 rounded-lg transition-all" className="bg-primary hover:bg-primary/80 text-foreground text-sm px-4 py-2 rounded-lg transition-all"
> >
{showForm ? "Cancel" : "New Ticket"} {showForm ? "Cancel" : "New Ticket"}
</button> </button>
@@ -88,34 +88,34 @@ export default function ClientSupport() {
{/* Submit form */} {/* Submit form */}
{showForm && ( {showForm && (
<form onSubmit={handleSubmit} className="bg-[#0d1117] border border-[#1a1a24] rounded-xl p-5 mb-6 space-y-4"> <form onSubmit={handleSubmit} className="bg-card border border-border rounded-xl p-5 mb-6 space-y-4">
<div> <div>
<label className="block text-sm text-[#8a8a95] mb-1">Title</label> <label className="block text-sm text-muted-foreground mb-1">Title</label>
<input <input
value={title} value={title}
onChange={(e) => setTitle(e.target.value)} onChange={(e) => setTitle(e.target.value)}
className="w-full bg-[#1a1a24] border border-[#2a2a35] rounded-lg px-3 py-2 text-sm text-white placeholder-[#6a6a75] outline-none focus:border-[#1BB0CE]/50" className="w-full bg-muted border border-border rounded-lg px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground outline-none focus:border-primary/50"
placeholder="Brief summary of the issue" placeholder="Brief summary of the issue"
required required
/> />
</div> </div>
<div> <div>
<label className="block text-sm text-[#8a8a95] mb-1">Description</label> <label className="block text-sm text-muted-foreground mb-1">Description</label>
<textarea <textarea
value={description} value={description}
onChange={(e) => setDescription(e.target.value)} onChange={(e) => setDescription(e.target.value)}
rows={4} rows={4}
className="w-full bg-[#1a1a24] border border-[#2a2a35] rounded-lg px-3 py-2 text-sm text-white placeholder-[#6a6a75] outline-none focus:border-[#1BB0CE]/50 resize-none" className="w-full bg-muted border border-border rounded-lg px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground outline-none focus:border-primary/50 resize-none"
placeholder="Detailed description of your issue" placeholder="Detailed description of your issue"
required required
/> />
</div> </div>
<div> <div>
<label className="block text-sm text-[#8a8a95] mb-1">Severity</label> <label className="block text-sm text-muted-foreground mb-1">Severity</label>
<Select value={severity} onValueChange={setSeverity}> <Select value={severity} onValueChange={setSeverity}>
<SelectTrigger className="w-full"> <SelectTrigger className="w-full bg-muted">
<SelectValue /> <SelectValue />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
@@ -128,7 +128,7 @@ export default function ClientSupport() {
</div> </div>
{submitError && ( {submitError && (
<div className="text-xs text-red-400 bg-red-400/5 border border-red-400/10 rounded-lg px-3 py-2"> <div className="text-xs text-destructive bg-destructive/5 border border-destructive/10 rounded-lg px-3 py-2">
{submitError} {submitError}
</div> </div>
)} )}
@@ -136,7 +136,7 @@ export default function ClientSupport() {
<button <button
type="submit" type="submit"
disabled={submitting} disabled={submitting}
className="flex items-center gap-2 bg-[#1BB0CE] hover:bg-[#1BB0CE]/80 text-white text-sm px-4 py-2 rounded-lg transition-all disabled:opacity-40" className="flex items-center gap-2 bg-primary hover:bg-primary/80 text-foreground text-sm px-4 py-2 rounded-lg transition-all disabled:opacity-40"
> >
<Send className="h-3.5 w-3.5" /> <Send className="h-3.5 w-3.5" />
{submitting ? "Submitting..." : "Submit Ticket"} {submitting ? "Submitting..." : "Submit Ticket"}
@@ -147,19 +147,19 @@ export default function ClientSupport() {
{/* Tickets list */} {/* Tickets list */}
{loading ? ( {loading ? (
<div className="flex items-center justify-center h-32"> <div className="flex items-center justify-center h-32">
<div className="w-6 h-6 border-2 border-[#1BB0CE] border-t-transparent rounded-full animate-spin" /> <div className="w-6 h-6 border-2 border-primary border-t-transparent rounded-full animate-spin" />
</div> </div>
) : tickets.length === 0 ? ( ) : tickets.length === 0 ? (
<div className="text-center py-16"> <div className="text-center py-16">
<LifeBuoy className="h-10 w-10 text-[#2a2a35] mx-auto mb-3" /> <LifeBuoy className="h-10 w-10 text-muted-foreground mx-auto mb-3" />
<p className="text-sm text-[#6a6a75]">No support tickets yet</p> <p className="text-sm text-muted-foreground">No support tickets yet</p>
</div> </div>
) : ( ) : (
<div className="space-y-2"> <div className="space-y-2">
{(tickets || []).map((t) => ( {(tickets || []).map((t) => (
<div key={t.id} className="bg-[#0d1117] border border-[#1a1a24] rounded-xl p-4"> <div key={t.id} className="bg-card border border-border rounded-xl p-4">
<div className="flex items-center justify-between mb-1"> <div className="flex items-center justify-between mb-1">
<h3 className="text-white text-sm font-medium">{t.title}</h3> <h3 className="text-foreground text-sm font-medium">{t.title}</h3>
<div className="flex gap-2"> <div className="flex gap-2">
<span className={`text-[10px] px-1.5 py-0.5 rounded ${ <span className={`text-[10px] px-1.5 py-0.5 rounded ${
t.severity === "critical" ? "bg-red-500/10 text-red-400" : t.severity === "critical" ? "bg-red-500/10 text-red-400" :
@@ -179,15 +179,15 @@ export default function ClientSupport() {
</span> </span>
</div> </div>
</div> </div>
<p className="text-sm text-[#8a8a95] line-clamp-2">{t.description}</p> <p className="text-sm text-muted-foreground line-clamp-2">{t.description}</p>
<div className="flex items-center justify-between mt-2"> <div className="flex items-center justify-between mt-2">
<span className="text-[10px] text-[#6a6a75]"> <span className="text-[10px] text-muted-foreground">
{new Date(t.created_at).toLocaleDateString()} {new Date(t.created_at).toLocaleDateString()}
</span> </span>
</div> </div>
{t.resolution_notes && ( {t.resolution_notes && (
<div className="mt-2 text-xs text-[#6a6a75] bg-[#1a1a24] rounded-lg px-3 py-2"> <div className="mt-2 text-xs text-muted-foreground bg-muted rounded-lg px-3 py-2">
<span className="text-[#8a8a95]">Resolution: </span>{t.resolution_notes} <span className="text-muted-foreground">Resolution: </span>{t.resolution_notes}
</div> </div>
)} )}
</div> </div>
+5
View File
@@ -6,6 +6,11 @@ import { Toaster } from "@/components/ui/sonner"
import "./globals.css" import "./globals.css"
import "../../Web_Backgrounds/default-theme.css" import "../../Web_Backgrounds/default-theme.css"
import "../../Web_Backgrounds/spidey-theme.css" import "../../Web_Backgrounds/spidey-theme.css"
import "../../Web_Backgrounds/cosmic-theme.css"
import "../../Web_Backgrounds/cyberpunk-theme.css"
import "../../Web_Backgrounds/cyber2-theme.css"
import "../../Web_Backgrounds/pumpkin-theme.css"
import "../../Web_Backgrounds/bw-theme.css"
const inter = Inter({ const inter = Inter({
variable: "--font-inter", variable: "--font-inter",