Fixed Resources, Added Error Logging/Messages, Fixed Pumkin Spice Background
Build & Auto-Repair / build (push) Has been cancelled

This commit is contained in:
JCBSComputer
2026-07-06 09:13:45 +02:00
parent dbec6c0851
commit 4035f5fad4
11 changed files with 372 additions and 264 deletions
+231 -198
View File
@@ -1,70 +1,71 @@
.dark.theme-pumpkin {
--background: 0 30% 4%;
--foreground: 20 50% 95%;
--card: 0 25% 8%;
--card-foreground: 20 50% 95%;
--popover: 0 25% 8%;
--popover-foreground: 20 50% 95%;
--primary: 10 100% 50%;
--primary-foreground: 0 0% 100%;
--secondary: 30 100% 50%;
--secondary-foreground: 0 0% 0%;
--muted: 0 12% 15%;
--muted-foreground: 20 10% 55%;
--accent: 10 100% 50%;
--background: 30 40% 3%;
--foreground: 30 30% 92%;
--card: 25 30% 7%;
--card-foreground: 30 30% 92%;
--popover: 25 30% 7%;
--popover-foreground: 30 30% 92%;
--primary: 25 95% 50%;
--primary-foreground: 30 20% 5%;
--secondary: 35 100% 45%;
--secondary-foreground: 30 20% 5%;
--muted: 25 15% 12%;
--muted-foreground: 30 12% 60%;
--accent: 15 100% 45%;
--accent-foreground: 0 0% 100%;
--destructive: 0 70% 50%;
--destructive-foreground: 210 40% 98%;
--border: 10 30% 18%;
--input: 10 30% 18%;
--ring: 10 100% 50%;
--destructive-foreground: 0 0% 100%;
--border: 25 20% 16%;
--input: 25 20% 16%;
--ring: 25 95% 50%;
--radius: 0.5rem;
--sidebar: 0 30% 4%;
--sidebar-foreground: 20 50% 95%;
--sidebar-primary: 10 100% 50%;
--sidebar-primary-foreground: 0 0% 100%;
--sidebar-accent: 10 100% 50%;
--sidebar: 25 35% 4%;
--sidebar-foreground: 30 30% 92%;
--sidebar-primary: 25 95% 50%;
--sidebar-primary-foreground: 30 20% 5%;
--sidebar-accent: 15 100% 45%;
--sidebar-accent-foreground: 0 0% 100%;
--sidebar-border: 10 30% 15%;
--sidebar-ring: 10 100% 50%;
--sidebar-border: 25 18% 14%;
--sidebar-ring: 25 95% 50%;
}
.light.theme-pumpkin {
--background: 20 30% 96%;
--foreground: 10 50% 10%;
--background: 30 30% 94%;
--foreground: 25 40% 12%;
--card: 0 0% 100%;
--card-foreground: 10 50% 10%;
--card-foreground: 25 40% 12%;
--popover: 0 0% 100%;
--popover-foreground: 10 50% 10%;
--primary: 10 90% 45%;
--popover-foreground: 25 40% 12%;
--primary: 25 90% 42%;
--primary-foreground: 0 0% 100%;
--secondary: 30 90% 45%;
--secondary: 35 90% 42%;
--secondary-foreground: 0 0% 100%;
--muted: 20 15% 88%;
--muted-foreground: 10 10% 45%;
--accent: 10 90% 45%;
--muted: 30 15% 86%;
--muted-foreground: 25 10% 42%;
--accent: 15 90% 42%;
--accent-foreground: 0 0% 100%;
--destructive: 0 80% 50%;
--destructive-foreground: 0 0% 100%;
--border: 20 15% 82%;
--input: 20 15% 82%;
--ring: 10 90% 45%;
--border: 25 15% 80%;
--input: 25 15% 80%;
--ring: 25 90% 42%;
--radius: 0.5rem;
--sidebar: 0 0% 100%;
--sidebar-foreground: 10 50% 10%;
--sidebar-primary: 10 90% 45%;
--sidebar-foreground: 25 40% 12%;
--sidebar-primary: 25 90% 42%;
--sidebar-primary-foreground: 0 0% 100%;
--sidebar-accent: 10 90% 45%;
--sidebar-accent: 15 90% 42%;
--sidebar-accent-foreground: 0 0% 100%;
--sidebar-border: 20 15% 82%;
--sidebar-ring: 10 90% 45%;
--sidebar-border: 25 15% 80%;
--sidebar-ring: 25 90% 42%;
}
/* Warm ambient glow */
.dark.theme-pumpkin body {
background: transparent
radial-gradient(ellipse 120% 60% at 20% 30%, rgba(200, 0, 0, 0.2) 0%, transparent 60%),
radial-gradient(ellipse 80% 50% at 80% 70%, rgba(255, 100, 0, 0.15) 0%, transparent 50%),
radial-gradient(ellipse 60% 40% at 50% 50%, rgba(150, 0, 0, 0.1) 0%, transparent 50%);
radial-gradient(ellipse 100% 55% at 20% 25%, rgba(200, 80, 0, 0.18) 0%, transparent 55%),
radial-gradient(ellipse 80% 45% at 80% 75%, rgba(255, 140, 0, 0.12) 0%, transparent 45%),
radial-gradient(ellipse 60% 35% at 50% 50%, rgba(180, 60, 0, 0.08) 0%, transparent 40%);
}
.dark.theme-pumpkin body > div {
@@ -78,195 +79,209 @@
z-index: -1;
pointer-events: none;
background:
radial-gradient(ellipse 80% 50% at 15% 20%, rgba(200, 50, 0, 0.04) 0%, transparent 60%),
radial-gradient(ellipse 80% 50% at 85% 80%, rgba(255, 100, 0, 0.03) 0%, transparent 60%);
radial-gradient(ellipse 70% 45% at 15% 20%, rgba(200, 80, 0, 0.05) 0%, transparent 55%),
radial-gradient(ellipse 70% 45% at 85% 80%, rgba(255, 140, 0, 0.04) 0%, transparent 55%);
}
/* Pumpkin Spice text glow */
.dark.theme-pumpkin .text-glow {
text-shadow: 0 0 10px hsl(10, 100%, 50%), 0 0 30px hsl(10, 100%, 50%), 0 0 60px rgba(255, 0, 0, 0.5);
text-shadow: 0 0 10px hsl(25, 95%, 50%), 0 0 30px hsla(25, 95%, 50%, 0.6), 0 0 60px rgba(200, 80, 0, 0.3);
}
.light.theme-pumpkin .text-glow {
text-shadow: 0 0 8px hsl(10, 90%, 45%), 0 0 20px hsl(10, 90%, 45%), 0 0 40px rgba(200, 50, 0, 0.3);
text-shadow: 0 0 8px hsl(25, 90%, 42%), 0 0 20px hsla(25, 90%, 42%, 0.4), 0 0 40px rgba(200, 80, 0, 0.15);
}
.dark.theme-pumpkin h1, .dark.theme-pumpkin h2, .dark.theme-pumpkin h3 {
background: linear-gradient(135deg, hsl(10, 100%, 50%), hsl(30, 100%, 50%));
/* Gradient headings — warm orange to amber */
.dark.theme-pumpkin h1:not(.text-primary):not(.text-foreground),
.dark.theme-pumpkin h2:not(.text-primary):not(.text-foreground),
.dark.theme-pumpkin h3:not(.text-primary):not(.text-foreground) {
background: linear-gradient(135deg, hsl(25, 95%, 55%), hsl(35, 100%, 50%));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.light.theme-pumpkin h1, .light.theme-pumpkin h2, .light.theme-pumpkin h3 {
background: linear-gradient(135deg, hsl(10, 90%, 40%), hsl(30, 90%, 40%));
.light.theme-pumpkin h1:not(.text-primary):not(.text-foreground),
.light.theme-pumpkin h2:not(.text-primary):not(.text-foreground),
.light.theme-pumpkin h3:not(.text-primary):not(.text-foreground) {
background: linear-gradient(135deg, hsl(25, 90%, 40%), hsl(35, 90%, 40%));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.dark.theme-pumpkin a {
color: hsl(10, 100%, 55%);
}
.light.theme-pumpkin a {
color: hsl(10, 90%, 40%);
}
.dark.theme-pumpkin a:hover {
color: hsl(30, 100%, 55%);
text-shadow: 0 0 8px rgba(255, 100, 0, 0.4);
}
.light.theme-pumpkin a:hover {
color: hsl(30, 90%, 45%);
}
/* Links */
.dark.theme-pumpkin a { color: hsl(25, 95%, 55%); }
.light.theme-pumpkin a { color: hsl(25, 90%, 40%); }
.dark.theme-pumpkin a:hover { color: hsl(35, 100%, 55%); text-shadow: 0 0 8px rgba(255, 140, 0, 0.35); }
.light.theme-pumpkin a:hover { color: hsl(35, 90%, 45%); }
/* Blockquotes */
.dark.theme-pumpkin blockquote {
border-left: 3px solid hsl(10, 100%, 50%);
color: hsl(20, 15%, 70%);
border-left: 3px solid hsl(25, 95%, 50%);
color: hsl(30, 15%, 70%);
font-style: italic;
}
.light.theme-pumpkin blockquote {
border-left: 3px solid hsl(10, 90%, 45%);
color: hsl(10, 10%, 45%);
border-left: 3px solid hsl(25, 90%, 42%);
color: hsl(25, 10%, 42%);
font-style: italic;
}
/* Code */
.dark.theme-pumpkin code {
color: hsl(10, 100%, 55%);
background: rgba(200, 0, 0, 0.12);
border: 1px solid rgba(200, 0, 0, 0.15);
color: hsl(25, 95%, 55%);
background: rgba(200, 80, 0, 0.1);
border: 1px solid rgba(200, 80, 0, 0.15);
}
.light.theme-pumpkin code {
color: hsl(10, 90%, 40%);
background: rgba(200, 50, 0, 0.05);
border: 1px solid rgba(200, 50, 0, 0.1);
color: hsl(25, 90%, 40%);
background: rgba(200, 80, 0, 0.04);
border: 1px solid rgba(200, 80, 0, 0.1);
}
.dark.theme-pumpkin pre { background: rgba(25, 12, 0, 0.9); border: 1px solid rgba(200, 80, 0, 0.1); }
.light.theme-pumpkin pre { background: rgba(200, 80, 0, 0.03); border: 1px solid rgba(200, 80, 0, 0.08); }
.dark.theme-pumpkin pre {
background: rgba(20, 0, 0, 0.9);
border: 1px solid rgba(200, 0, 0, 0.1);
}
/* HR */
.dark.theme-pumpkin hr { border-color: rgba(200, 80, 0, 0.12); }
.light.theme-pumpkin hr { border-color: rgba(200, 80, 0, 0.08); }
.light.theme-pumpkin pre {
background: rgba(200, 50, 0, 0.03);
border: 1px solid rgba(200, 50, 0, 0.08);
}
/* Tables */
.dark.theme-pumpkin table th { color: hsl(25, 95%, 55%); }
.light.theme-pumpkin table th { color: hsl(25, 90%, 40%); }
.dark.theme-pumpkin table td { border-color: rgba(200, 80, 0, 0.1); }
.light.theme-pumpkin table td { border-color: rgba(200, 80, 0, 0.08); }
.dark.theme-pumpkin hr {
border-color: rgba(200, 0, 0, 0.15);
}
/* Selection */
.dark.theme-pumpkin ::selection { background: rgba(200, 80, 0, 0.2); color: hsl(0, 0%, 100%); }
.light.theme-pumpkin ::selection { background: rgba(200, 80, 0, 0.1); color: hsl(0, 0%, 0%); }
.light.theme-pumpkin hr {
border-color: rgba(200, 50, 0, 0.1);
}
/* Placeholders */
.dark.theme-pumpkin input::placeholder, .dark.theme-pumpkin textarea::placeholder { color: hsl(25, 10%, 40%); }
.light.theme-pumpkin input::placeholder, .light.theme-pumpkin textarea::placeholder { color: hsl(25, 10%, 62%); }
.dark.theme-pumpkin table th {
color: hsl(10, 100%, 55%);
}
.light.theme-pumpkin table th {
color: hsl(10, 90%, 40%);
}
.dark.theme-pumpkin table td {
border-color: rgba(200, 0, 0, 0.12);
}
.light.theme-pumpkin table td {
border-color: rgba(200, 50, 0, 0.1);
}
.dark.theme-pumpkin ::selection {
background: rgba(200, 0, 0, 0.2);
color: hsl(0, 0%, 100%);
}
.light.theme-pumpkin ::selection {
background: rgba(200, 50, 0, 0.12);
color: hsl(0, 0%, 0%);
}
.dark.theme-pumpkin input::placeholder,
.dark.theme-pumpkin textarea::placeholder {
color: hsl(20, 10%, 40%);
}
.light.theme-pumpkin input::placeholder,
.light.theme-pumpkin textarea::placeholder {
color: hsl(10, 10%, 65%);
}
.dark.theme-pumpkin .page-header-title {
color: hsl(10, 100%, 55%);
}
.light.theme-pumpkin .page-header-title {
color: hsl(10, 90%, 40%);
}
.dark.theme-pumpkin .stat-card-value {
color: hsl(10, 100%, 55%);
}
.light.theme-pumpkin .stat-card-value {
color: hsl(10, 90%, 40%);
}
.dark.theme-pumpkin .badge {
color: hsl(10, 100%, 55%);
}
.light.theme-pumpkin .badge {
color: hsl(10, 90%, 40%);
}
/* Page header / stat card / badge */
.dark.theme-pumpkin .page-header-title, .dark.theme-pumpkin .stat-card-value, .dark.theme-pumpkin .badge { color: hsl(25, 95%, 55%); }
.light.theme-pumpkin .page-header-title, .light.theme-pumpkin .stat-card-value, .light.theme-pumpkin .badge { color: hsl(25, 90%, 40%); }
/* ============================================================
Dashboard stat card pipeline bars — Pumpkin Spice palette
============================================================ */
.dark.theme-pumpkin .sm\:grid-cols-2.lg\:grid-cols-3.gap-4.mt-4 > .relative:nth-child(odd) .group .h-1.w-full {
background: linear-gradient(to right, hsl(10, 100%, 50%), hsl(10, 100%, 65%), hsl(10, 100%, 50%)) !important;
background: linear-gradient(to right, hsl(25, 95%, 50%), hsl(25, 95%, 65%), hsl(25, 95%, 50%)) !important;
}
.dark.theme-pumpkin .sm\:grid-cols-2.lg\:grid-cols-3.gap-4.mt-4 > .relative:nth-child(even) .group .h-1.w-full {
background: linear-gradient(to right, hsl(30, 100%, 50%), hsl(30, 100%, 65%), hsl(30, 100%, 50%)) !important;
background: linear-gradient(to right, hsl(35, 100%, 45%), hsl(35, 100%, 60%), hsl(35, 100%, 45%)) !important;
}
.dark.theme-pumpkin .sm\:grid-cols-2.lg\:grid-cols-3.gap-4.mt-4 > .relative:nth-child(odd) .group .rounded-xl.shrink-0 {
background: hsla(10, 100%, 50%, 0.1) !important;
background: hsla(25, 95%, 50%, 0.12) !important;
}
.dark.theme-pumpkin .sm\:grid-cols-2.lg\:grid-cols-3.gap-4.mt-4 > .relative:nth-child(even) .group .rounded-xl.shrink-0 {
background: hsla(30, 100%, 50%, 0.1) !important;
background: hsla(35, 100%, 45%, 0.12) !important;
}
.dark.theme-pumpkin .sm\:grid-cols-2.lg\:grid-cols-3.gap-4.mt-4 > .relative:nth-child(odd) .group .rounded-xl.shrink-0 svg {
color: hsl(10, 100%, 50%) !important;
color: hsl(25, 95%, 50%) !important;
}
.dark.theme-pumpkin .sm\:grid-cols-2.lg\:grid-cols-3.gap-4.mt-4 > .relative:nth-child(even) .group .rounded-xl.shrink-0 svg {
color: hsl(30, 100%, 50%) !important;
color: hsl(35, 100%, 45%) !important;
}
.dark.theme-pumpkin .sm\:grid-cols-2.lg\:grid-cols-3.gap-4.mt-4 > .relative:nth-child(odd) .group .absolute.bottom-0.h-1 {
background: linear-gradient(to right, transparent, hsla(10, 100%, 50%, 0.25), transparent) !important;
background: linear-gradient(to right, transparent, hsla(25, 95%, 50%, 0.25), transparent) !important;
}
.dark.theme-pumpkin .sm\:grid-cols-2.lg\:grid-cols-3.gap-4.mt-4 > .relative:nth-child(even) .group .absolute.bottom-0.h-1 {
background: linear-gradient(to right, transparent, hsla(30, 100%, 50%, 0.25), transparent) !important;
background: linear-gradient(to right, transparent, hsla(35, 100%, 45%, 0.25), transparent) !important;
}
.dark.theme-pumpkin .sm\:grid-cols-2.lg\:grid-cols-3.gap-4.mt-4 .rounded-full.bg-gradient-to-r {
background: linear-gradient(to right, hsl(10, 100%, 50%), hsl(30, 100%, 50%)) !important;
background: linear-gradient(to right, hsl(25, 95%, 50%), hsl(35, 100%, 45%)) !important;
}
.light.theme-pumpkin .sm\:grid-cols-2.lg\:grid-cols-3.gap-4.mt-4 > .relative:nth-child(odd) .group .h-1.w-full {
background: linear-gradient(to right, hsl(10, 90%, 45%), hsl(10, 90%, 55%), hsl(10, 90%, 45%)) !important;
background: linear-gradient(to right, hsl(25, 90%, 42%), hsl(25, 90%, 55%), hsl(25, 90%, 42%)) !important;
}
.light.theme-pumpkin .sm\:grid-cols-2.lg\:grid-cols-3.gap-4.mt-4 > .relative:nth-child(even) .group .h-1.w-full {
background: linear-gradient(to right, hsl(30, 90%, 45%), hsl(30, 90%, 55%), hsl(30, 90%, 45%)) !important;
background: linear-gradient(to right, hsl(35, 90%, 42%), hsl(35, 90%, 55%), hsl(35, 90%, 42%)) !important;
}
/* ============================================================
Chart color overrides — Pumpkin Spice palette
============================================================ */
.dark.theme-pumpkin [class*="recharts-"] text,
.light.theme-pumpkin [class*="recharts-"] text {
fill: hsl(30, 12%, 60%) !important;
}
/* Donut chart gradient stops */
.dark.theme-pumpkin .recharts-pie-sector:nth-child(1) path,
.light.theme-pumpkin .recharts-pie-sector:nth-child(1) path {
fill: hsl(25, 95%, 50%) !important;
}
.dark.theme-pumpkin .recharts-pie-sector:nth-child(2) path,
.light.theme-pumpkin .recharts-pie-sector:nth-child(2) path {
fill: hsl(35, 100%, 45%) !important;
}
.dark.theme-pumpkin .recharts-pie-sector:nth-child(3) path,
.light.theme-pumpkin .recharts-pie-sector:nth-child(3) path {
fill: hsl(15, 100%, 42%) !important;
}
/* Stat card icon colors */
.dark.theme-pumpkin .grid.grid-cols-1.sm\:grid-cols-2.lg\:grid-cols-4.gap-4 > div:nth-child(1) .h-8.w-8,
.light.theme-pumpkin .grid.grid-cols-1.sm\:grid-cols-2.lg\:grid-cols-4.gap-4 > div:nth-child(1) .h-8.w-8 {
background: hsla(25, 95%, 50%, 0.12) !important;
color: hsl(25, 95%, 50%) !important;
}
.dark.theme-pumpkin .grid.grid-cols-1.sm\:grid-cols-2.lg\:grid-cols-4.gap-4 > div:nth-child(2) .h-8.w-8,
.light.theme-pumpkin .grid.grid-cols-1.sm\:grid-cols-2.lg\:grid-cols-4.gap-4 > div:nth-child(2) .h-8.w-8 {
background: hsla(35, 100%, 45%, 0.12) !important;
color: hsl(35, 100%, 45%) !important;
}
.dark.theme-pumpkin .grid.grid-cols-1.sm\:grid-cols-2.lg\:grid-cols-4.gap-4 > div:nth-child(3) .h-8.w-8,
.light.theme-pumpkin .grid.grid-cols-1.sm\:grid-cols-2.lg\:grid-cols-4.gap-4 > div:nth-child(3) .h-8.w-8 {
background: hsla(15, 100%, 42%, 0.12) !important;
color: hsl(15, 100%, 42%) !important;
}
/* Stat card value colors */
.dark.theme-pumpkin .grid.grid-cols-1.sm\:grid-cols-2.lg\:grid-cols-4.gap-4 > div:nth-child(1) .text-2xl,
.light.theme-pumpkin .grid.grid-cols-1.sm\:grid-cols-2.lg\:grid-cols-4.gap-4 > div:nth-child(1) .text-2xl {
color: hsl(25, 95%, 50%) !important;
}
.dark.theme-pumpkin .grid.grid-cols-1.sm\:grid-cols-2.lg\:grid-cols-4.gap-4 > div:nth-child(2) .text-2xl,
.light.theme-pumpkin .grid.grid-cols-1.sm\:grid-cols-2.lg\:grid-cols-4.gap-4 > div:nth-child(2) .text-2xl {
color: hsl(35, 100%, 45%) !important;
}
.dark.theme-pumpkin .grid.grid-cols-1.sm\:grid-cols-2.lg\:grid-cols-4.gap-4 > div:nth-child(3) .text-2xl,
.light.theme-pumpkin .grid.grid-cols-1.sm\:grid-cols-2.lg\:grid-cols-4.gap-4 > div:nth-child(3) .text-2xl {
color: hsl(15, 100%, 42%) !important;
}
/* Bar chart bar fills */
.dark.theme-pumpkin .recharts-bar-rectangle:nth-child(1) rect,
.light.theme-pumpkin .recharts-bar-rectangle:nth-child(1) rect {
fill: hsl(25, 95%, 50%) !important;
}
.dark.theme-pumpkin .recharts-bar-rectangle:nth-child(2) rect,
.light.theme-pumpkin .recharts-bar-rectangle:nth-child(2) rect {
fill: hsl(35, 100%, 45%) !important;
}
/* ============================================================
Card gradient accent — subtle pumpkin glow on hover
============================================================ */
.dark.theme-pumpkin .rounded-xl.border.border-border {
border-color: hsla(25, 20%, 18%, 0.8) !important;
}
.dark.theme-pumpkin .rounded-xl.border.border-border:hover {
border-color: hsla(25, 95%, 50%, 0.25) !important;
}
/* Growth word on login page — keep brand blue */
.dark.theme-pumpkin .growth-word,
.light.theme-pumpkin .growth-word {
-webkit-text-fill-color: #1BB0CE !important;
@@ -274,70 +289,88 @@
background: none !important;
}
/* ============================================================================
Client Portal Overrides — Background for portal pages
============================================================================ */
/* Make the outer portal container transparent so body::before background shows */
/* ============================================================
Client Portal Overrides
============================================================ */
.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;
background: hsl(25, 35%, 4%) !important;
border-right-color: hsl(25, 20%, 14%) !important;
}
/* Portal sidebar nav items — muted text */
.dark.theme-pumpkin aside .text-muted-foreground {
color: hsl(240, 8%, 50%) !important;
color: hsl(30, 10%, 50%) !important;
}
.dark.theme-pumpkin aside button:hover {
background: hsla(330, 100%, 50%, 0.08) !important;
background: hsla(25, 95%, 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;
background: hsl(25, 95%, 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;
box-shadow: 0 0 10px hsla(25, 95%, 50%, 0.3), 0 0 20px hsla(25, 95%, 50%, 0.1);
border-color: hsl(25, 95%, 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;
background: hsl(25, 35%, 4%) !important;
border-bottom-color: hsl(25, 20%, 14%) !important;
}
/* Portal card backgrounds — semi-transparent glass */
.dark.theme-pumpkin .bg-card {
background: hsla(240, 15%, 7%, 0.65) !important;
background: hsla(25, 30%, 7%, 0.65) !important;
backdrop-filter: blur(10px) !important;
border-color: hsla(270, 30%, 20%, 0.35) !important;
border-color: hsla(25, 20%, 16%, 0.35) !important;
}
/* Portal borders */
.dark.theme-pumpkin .border-border {
border-color: hsl(270, 25%, 12%) !important;
border-color: hsl(25, 18%, 14%) !important;
}
/* Portal input fields */
.dark.theme-pumpkin .bg-muted {
background: hsl(240, 12%, 9%) !important;
background: hsl(25, 15%, 10%) !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);
background: hsl(25, 95%, 50%) !important;
box-shadow: 0 0 10px hsla(25, 95%, 50%, 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);
background: hsl(25, 95%, 55%) !important;
box-shadow: 0 0 15px hsla(25, 95%, 50%, 0.35);
}
/* Light mode portal */
.light.theme-pumpkin .min-h-screen.bg-background {
background: transparent !important;
}
.light.theme-pumpkin aside.bg-card.border-r.border-border {
background: hsl(0, 0%, 100%) !important;
border-right-color: hsl(25, 15%, 82%) !important;
}
.light.theme-pumpkin header.bg-card\/80 {
background: hsl(0, 0%, 98%) !important;
border-bottom-color: hsl(25, 15%, 82%) !important;
}
.light.theme-pumpkin .bg-card {
background: hsla(0, 0%, 100%, 0.65) !important;
backdrop-filter: blur(10px) !important;
border-color: hsla(25, 15%, 80%, 0.5) !important;
}
.light.theme-pumpkin .bg-primary {
background: hsl(25, 90%, 42%) !important;
}
.light.theme-pumpkin .bg-primary:hover {
background: hsl(25, 90%, 48%) !important;
}
+33 -9
View File
@@ -73,9 +73,12 @@ Example format:
[{"companyName":"Acme Corp","contactName":"John Smith","email":"john@acme.com","phone":"555-0100","description":"Brief description"}]
Use realistic company names, names, and emails for the ${job.industry} industry.`
aiChatRef.current?.addAssistantMessage(`🔍 Finding leads for **${job.job_title}**...`)
const addMsg = (text: string) => aiChatRef.current?.addAssistantMessage(text)
try {
addMsg(`🔍 Step 1/3: Asking AI to generate leads for **${job.job_title}**...`)
const res = await fetch("/api/ai/chat", {
method: "POST",
headers: { "Content-Type": "application/json" },
@@ -84,11 +87,22 @@ Use realistic company names, names, and emails for the ${job.industry} industry.
if (!res.ok) {
const data = await res.json().catch(() => ({}))
throw new Error(data.error || `Error ${res.status}`)
const statusInfo = `HTTP ${res.status}: ${data.error || res.statusText}`
addMsg(`❌ Step 1/3 failed — AI chat returned ${statusInfo}. Make sure Ollama is running (port 11434) and the AI service on port 3001 is up.`)
setSearching(false)
return
}
const data = await res.json()
const raw = data.response || ""
if (!raw) {
addMsg("❌ Step 1/3 failed — AI returned an empty response.")
setSearching(false)
return
}
addMsg(`✅ Step 1/3 complete. Parsing AI response...`)
let leads: any[]
try {
let text = raw.trim()
@@ -97,11 +111,19 @@ Use realistic company names, names, and emails for the ${job.industry} industry.
leads = JSON.parse(text)
if (!Array.isArray(leads)) leads = [leads]
} catch (e) {
aiChatRef.current?.addAssistantMessage(`⚠️ AI response wasn't valid JSON. Showing raw output:\n\n${raw.slice(0, 1500)}`)
addMsg(`⚠️ Step 2/3 failed — AI response wasn't valid JSON. Raw output:\n\n${raw.slice(0, 1500)}`)
setSearching(false)
return
}
if (leads.length === 0) {
addMsg("⚠️ Step 2/3 — AI returned an empty array. No leads to create.")
setSearching(false)
return
}
addMsg(`✅ Step 2/3 — Parsed **${leads.length}** lead${leads.length > 1 ? "s" : ""} from AI response. Step 3/3: Saving to database...`)
let created = 0
let lastError = ""
for (const lead of leads.slice(0, 5)) {
@@ -119,18 +141,20 @@ Use realistic company names, names, and emails for the ${job.industry} industry.
}),
})
if (r.ok) created++
else { const e = await r.json().catch(() => ({})); lastError = e.error || r.statusText; }
} catch (e) { lastError = "Network error" }
else { const e = await r.json().catch(() => ({})); lastError = `API ${res.status} ${e.error || r.statusText}`; }
} catch (e) {
lastError = `Network error: ${e instanceof Error ? e.message : "Failed to reach server"}`
}
}
if (created > 0) {
aiChatRef.current?.addAssistantMessage(`✅ Created **${created}** lead${created > 1 ? "s" : ""} for **${job.job_title}**! Go to the Leads tab to see them.`)
addMsg(`✅ Created **${created}** lead${created > 1 ? "s" : ""} for **${job.job_title}**! Go to the **Leads** tab to view and manage them.${lastError ? `\n\n⚠️ ${leads.length - created} lead${leads.length - created > 1 ? "s" : ""} failed: ${lastError}` : ""}`)
} else {
aiChatRef.current?.addAssistantMessage(`Failed to create leads. Last error: ${lastError || "Unknown"}. Make sure you're logged in with an account that has permission to create leads.`)
addMsg(`Step 3/3 failed — Could not save any leads.\n\nLast error: ${lastError || "Unknown"}\n\nPossible causes:\n• Your session expired — try logging out and back in\n• You don't have permission to create leads\n• The database connection is down`)
}
} catch (err) {
const msg = err instanceof Error ? err.message : "Search failed"
aiChatRef.current?.addAssistantMessage(`Error: ${msg}. Make sure Ollama is running with the model loaded.`)
const msg = err instanceof Error ? err.message : "Unknown error"
addMsg(`Unexpected error: ${msg}\n\nThis might be a network issue or the AI service (port 3001) is not responding. Try again.`)
} finally {
setSearching(false)
}
+5 -5
View File
@@ -126,11 +126,11 @@ export default function CalendarPage() {
fetch("/api/event-users")
.then((r) => r.json())
.then((data) => setUsers(data.users || []))
.catch(() => {})
.catch((err) => console.error("Failed to fetch event users:", err))
fetch("/api/leads?limit=200")
.then((r) => r.json())
.then((data) => setLeads((Array.isArray(data) ? data : data?.leads || []).map((l: any) => ({ id: l.id, contactName: l.contactName, companyName: l.companyName }))))
.catch(() => {})
.catch((err) => console.error("Failed to fetch leads:", err))
}, [user, router])
useEffect(() => {
@@ -139,13 +139,13 @@ export default function CalendarPage() {
}, [fetchMonthEvents])
useEffect(() => {
fetchUpcomingEvents().catch(() => {})
fetchUpcomingEvents().catch((err) => console.error("Failed to fetch upcoming events:", err))
}, [fetchUpcomingEvents])
useEffect(() => {
const interval = setInterval(() => {
fetchMonthEvents().catch(() => {})
fetchUpcomingEvents().catch(() => {})
fetchMonthEvents().catch((err) => console.error("Failed to fetch month events:", err))
fetchUpcomingEvents().catch((err) => console.error("Failed to fetch upcoming events:", err))
}, 15000)
return () => clearInterval(interval)
}, [fetchMonthEvents, fetchUpcomingEvents])
+7 -1
View File
@@ -39,8 +39,11 @@ export default function CampaignDetailPage({ params }: { params: Promise<{ id: s
const searchLeads = async (q: string) => {
setLeadSearch(q)
if (q.length < 2) { setSearchResults([]); return }
try {
const res = await fetch(`/api/leads?search=${encodeURIComponent(q)}&limit=10`)
if (res.ok) setSearchResults(await res.json())
else console.error("Failed to search leads:", res.status, res.statusText)
} catch (err) { console.error("Failed to search leads:", err) }
}
const addStep = async () => {
@@ -84,12 +87,15 @@ export default function CampaignDetailPage({ params }: { params: Promise<{ id: s
}
const updateStatus = async (status: string) => {
await fetch(`/api/campaigns/${id}`, {
try {
const res = await fetch(`/api/campaigns/${id}`, {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ status }),
})
if (!res.ok) console.error("Failed to update campaign status:", res.status, res.statusText)
fetchCampaign()
} catch (err) { console.error("Failed to update campaign status:", err) }
}
if (loading) return <div className="p-8 text-center text-muted-foreground">Loading...</div>
+9 -9
View File
@@ -235,8 +235,8 @@ const formatPreviewContent = (content: string) => {
setUnreadMap(new Map(convs.map((c: any) => [c.id, c.unread])))
if (convs.length > 0) setActiveChat(convs[0].id)
}
} catch {
console.warn("Failed to fetch conversations in chats page")
} catch (err) {
console.error("Failed to fetch conversations:", err)
} finally {
setLoadingChats(false)
}
@@ -297,11 +297,11 @@ const formatPreviewContent = (content: string) => {
return next
})
}
} catch {
// ignore
} catch (err) {
console.error("Failed to fetch messages:", err)
}
// Mark conversation as read
fetch(`/api/conversations/${activeChat}/read`, { method: "POST" }).catch(() => {})
fetch(`/api/conversations/${activeChat}/read`, { method: "POST" }).catch((err) => console.error("Failed to mark read:", err))
setUnreadMap((prev) => { const m = new Map(prev); m.set(activeChat, 0); return m })
}
fetchMsgs()
@@ -320,7 +320,7 @@ const formatPreviewContent = (content: string) => {
const existingIds = new Set(conversations.map((c) => otherParticipant(c).id))
setSearchResults(data.users.filter((u: any) => !existingIds.has(u.id)))
}
} catch { console.warn("Failed to search users in chats page") }
} catch (err) { console.error("Failed to search users:", err) }
setSearchingUsers(false)
}, 300)
return () => clearTimeout(timer)
@@ -799,8 +799,8 @@ const formatPreviewContent = (content: string) => {
prev.map((c) => (c.id === convId ? { ...c, pinned: !pinned } : c))
)
}
} catch {
console.warn("Failed to toggle pin")
} catch (err) {
console.error("Failed to toggle pin:", err)
}
}
@@ -912,7 +912,7 @@ const formatPreviewContent = (content: string) => {
setSearchQuery("")
}
}
} catch { console.warn("Failed to create conversation in chats page") }
} catch (err) { console.error("Failed to create conversation:", err) }
}}
className="w-full flex items-center gap-3 p-4 text-left transition-colors hover:bg-muted/50"
>
+2 -2
View File
@@ -39,8 +39,8 @@ export default function DashboardPage() {
const data = await res.json()
setStats(data)
}
} catch {
console.warn("Failed to fetch dashboard stats")
} catch (err) {
console.error("Failed to fetch dashboard stats:", err)
}
}
+1 -1
View File
@@ -13,7 +13,7 @@ export default function EmailsPage() {
useEffect(() => {
if (!user) { router.push("/login"); return }
if (user.role !== "admin" && user.role !== "super_admin") { router.push("/dashboard"); return }
fetch("/api/emails").then((r) => r.ok ? r.json() : { emails: [] }).then((d) => setEmails(d.emails || []))
fetch("/api/emails").then((r) => r.ok ? r.json() : { emails: [] }).then((d) => setEmails(d.emails || [])).catch((err) => console.error("Failed to fetch emails:", err))
}, [user, router])
if (!user || (user.role !== "admin" && user.role !== "super_admin")) return null
+2 -2
View File
@@ -39,8 +39,8 @@ export default function LeadDetailsPage({ params }: { params: Promise<{ id: stri
fetchNotes(),
])
if (leadRes.ok) setLead(await leadRes.json())
} catch {
console.warn("Failed to fetch lead details")
} catch (err) {
console.error("Failed to fetch lead details:", err)
}
setLoading(false)
}
+19 -28
View File
@@ -42,33 +42,24 @@ interface Totals {
teamUtilization: number
}
function utilizationColor(pct: number): string {
if (pct > 100) return "bg-red-500"
if (pct > 90) return "bg-orange-500"
if (pct > 70) return "bg-yellow-500"
return "bg-emerald-500"
}
function utilizationTextColor(pct: number): string {
if (pct > 100) return "text-red-600"
if (pct > 90) return "text-orange-600"
if (pct > 70) return "text-yellow-600"
return "text-emerald-600"
}
function utilizationBgColor(pct: number): string {
if (pct > 100) return "bg-red-50 border-red-200"
if (pct > 90) return "bg-orange-50 border-orange-200"
if (pct > 70) return "bg-yellow-50 border-yellow-200"
return "bg-emerald-50 border-emerald-200"
function statusKey(pct: number): string {
if (pct > 100) return "overloaded"
if (pct > 90) return "near-capacity"
if (pct > 70) return "busy"
if (pct > 50) return "available"
return "underutilized"
}
function statusLabel(pct: number): { label: string; color: string } {
if (pct > 100) return { label: "Overloaded", color: "bg-red-100 text-red-700 border-red-300" }
if (pct > 90) return { label: "Near Capacity", color: "bg-orange-100 text-orange-700 border-orange-300" }
if (pct > 70) return { label: "Busy", color: "bg-yellow-100 text-yellow-700 border-yellow-300" }
if (pct > 50) return { label: "Available", color: "bg-emerald-100 text-emerald-700 border-emerald-300" }
return { label: "Underutilized", color: "bg-slate-100 text-slate-600 border-slate-300" }
const key = statusKey(pct)
const labels: Record<string, string> = {
overloaded: "Overloaded",
"near-capacity": "Near Capacity",
busy: "Busy",
available: "Available",
underutilized: "Underutilized",
}
return { label: labels[key], color: `util-badge-${key}` }
}
export default function ResourcePlanningPage() {
@@ -120,7 +111,7 @@ export default function ResourcePlanningPage() {
<div className="flex items-center gap-2 text-sm text-muted-foreground">
<TrendingUp className="h-4 w-4" /> Utilisation
</div>
<p className={`text-2xl font-bold ${utilizationTextColor(totals?.teamUtilization ?? 0)}`}>
<p className={`text-2xl font-bold util-text-${statusKey(totals?.teamUtilization ?? 0)}`}>
{totals?.teamUtilization ?? 0}%
</p>
</Card>
@@ -157,7 +148,7 @@ export default function ResourcePlanningPage() {
{members.map((m) => {
const status = statusLabel(m.utilization)
return (
<Card key={m.id} className={`p-4 space-y-3 border ${utilizationBgColor(m.utilization)}`}>
<Card key={m.id} className={`p-4 space-y-3 border util-card-${statusKey(m.utilization)}`}>
<div className="flex items-start justify-between">
<div className="flex items-center gap-3">
<Avatar className="h-10 w-10">
@@ -179,11 +170,11 @@ export default function ResourcePlanningPage() {
<div className="flex justify-between text-xs text-muted-foreground">
<span>Capacity: {m.capacityHours}h</span>
<span>Logged: {m.totalHours}h</span>
<span className={utilizationTextColor(m.utilization)}>{m.utilization}%</span>
<span className={`util-text-${statusKey(m.utilization)}`}>{m.utilization}%</span>
</div>
<div className="h-2.5 w-full overflow-hidden rounded-full bg-muted">
<div
className={`h-full rounded-full transition-all ${utilizationColor(m.utilization)}`}
className={`h-full rounded-full transition-all util-bar-${statusKey(m.utilization)}`}
style={{ width: `${Math.min(m.utilization, 100)}%` }}
/>
</div>
+2 -2
View File
@@ -24,8 +24,8 @@ export default function UsersPage() {
const data = await res.json()
setUsers(data.users || [])
}
} catch {
console.warn("Failed to fetch users in users page")
} catch (err) {
console.error("Failed to fetch users:", err)
} finally {
setLoading(false)
}
+54
View File
@@ -758,4 +758,58 @@ main {
opacity: 1 !important;
}
/* ============================================================
Utilisation status card backgrounds (theme-aware)
============================================================ */
.util-card-overloaded {
background-color: color-mix(in srgb, hsl(0 80% 50% / 0.1), hsl(var(--card))) !important;
border-color: hsl(0 70% 50% / 0.3) !important;
}
.util-card-near-capacity {
background-color: color-mix(in srgb, hsl(25 95% 50% / 0.1), hsl(var(--card))) !important;
border-color: hsl(25 85% 45% / 0.3) !important;
}
.util-card-busy {
background-color: color-mix(in srgb, hsl(45 90% 50% / 0.08), hsl(var(--card))) !important;
border-color: hsl(45 80% 45% / 0.25) !important;
}
.util-card-available {
background-color: color-mix(in srgb, hsl(150 65% 40% / 0.08), hsl(var(--card))) !important;
border-color: hsl(150 60% 40% / 0.25) !important;
}
.util-card-underutilized {
/* Default card styling — no tint needed */
}
/* Utilisation status text */
.util-text-overloaded { @apply text-red-600 dark:text-red-400; }
.util-text-near-capacity { @apply text-orange-600 dark:text-orange-400; }
.util-text-busy { @apply text-yellow-600 dark:text-yellow-400; }
.util-text-available { @apply text-emerald-600 dark:text-emerald-400; }
.util-text-underutilized { @apply text-slate-600 dark:text-slate-400; }
/* Utilisation status badge */
.util-badge-overloaded {
@apply bg-red-100 text-red-700 border-red-300 dark:bg-red-900/30 dark:text-red-300 dark:border-red-800;
}
.util-badge-near-capacity {
@apply bg-orange-100 text-orange-700 border-orange-300 dark:bg-orange-900/30 dark:text-orange-300 dark:border-orange-800;
}
.util-badge-busy {
@apply bg-yellow-100 text-yellow-700 border-yellow-300 dark:bg-yellow-900/25 dark:text-yellow-300 dark:border-yellow-800;
}
.util-badge-available {
@apply bg-emerald-100 text-emerald-700 border-emerald-300 dark:bg-emerald-900/25 dark:text-emerald-300 dark:border-emerald-800;
}
.util-badge-underutilized {
@apply bg-slate-100 text-slate-600 border-slate-300 dark:bg-slate-800/30 dark:text-slate-400 dark:border-slate-700;
}
/* Utilisation bar fill */
.util-bar-overloaded { @apply bg-red-500; }
.util-bar-near-capacity { @apply bg-orange-500; }
.util-bar-busy { @apply bg-yellow-500; }
.util-bar-available { @apply bg-emerald-500; }
.util-bar-underutilized { @apply bg-slate-400 dark:bg-slate-500; }