Fuckups fixed

This commit is contained in:
Ace
2026-06-26 16:30:48 +02:00
parent dfba947aa4
commit bd581739f9
11 changed files with 256 additions and 48 deletions
+34 -1
View File
@@ -3,7 +3,6 @@
This is the current website appearance captured as a reusable CSS theme. This is the current website appearance captured as a reusable CSS theme.
============================================================================ */ ============================================================================ */
:root,
.theme-spidey { .theme-spidey {
--background: 222.2 84% 4.9%; --background: 222.2 84% 4.9%;
--foreground: 210 40% 98%; --foreground: 210 40% 98%;
@@ -34,3 +33,37 @@
--sidebar-border: 217.2 32.6% 17.5%; --sidebar-border: 217.2 32.6% 17.5%;
--sidebar-ring: 0 100% 53%; --sidebar-ring: 0 100% 53%;
} }
.theme-spidey body::before {
content: "";
position: fixed;
inset: 0;
z-index: -1;
pointer-events: none;
background-image:
repeating-conic-gradient(
from 10deg,
transparent 0deg 10deg,
rgba(220, 38, 38, 0.04) 10deg 12deg
),
repeating-radial-gradient(
circle at 50% 50%,
transparent 0,
transparent 30px,
rgba(37, 99, 235, 0.03) 30px,
transparent 32px
),
radial-gradient(2px 2px at 10% 20%, rgba(220,38,38,0.35) 0%, transparent 100%),
radial-gradient(2px 2px at 30% 80%, rgba(37,99,235,0.35) 0%, transparent 100%),
radial-gradient(2px 2px at 50% 30%, rgba(220,38,38,0.3) 0%, transparent 100%),
radial-gradient(2px 2px at 70% 60%, rgba(37,99,235,0.3) 0%, transparent 100%),
radial-gradient(2px 2px at 85% 15%, rgba(220,38,38,0.35) 0%, transparent 100%),
radial-gradient(2px 2px at 20% 50%, rgba(37,99,235,0.25) 0%, transparent 100%),
radial-gradient(2px 2px at 65% 85%, rgba(220,38,38,0.25) 0%, transparent 100%),
radial-gradient(2px 2px at 40% 10%, rgba(37,99,235,0.3) 0%, transparent 100%),
radial-gradient(2px 2px at 90% 75%, rgba(220,38,38,0.25) 0%, transparent 100%),
radial-gradient(2px 2px at 5% 60%, rgba(37,99,235,0.3) 0%, transparent 100%),
radial-gradient(2px 2px at 55% 55%, rgba(220,38,38,0.2) 0%, transparent 100%);
background-size: 200% 200%;
animation: drift 60s linear infinite;
}
+64
View File
@@ -0,0 +1,64 @@
/* ============================================================================
Starforce Theme — Dark cosmic with crimson accents and neon green glow
Captures the current starfield + drift + glow effects used live.
============================================================================ */
.theme-starforce {
--background: 0 0% 10%;
--foreground: 210 40% 98%;
--card: 0 0% 9%;
--card-foreground: 210 40% 98%;
--popover: 222.2 84% 11%;
--popover-foreground: 210 40% 98%;
--primary: 0 100% 53%;
--primary-foreground: 222.2 47.4% 11.2%;
--secondary: 217.2 32.6% 17.5%;
--secondary-foreground: 210 40% 98%;
--muted: 217.2 32.6% 17.5%;
--muted-foreground: 215 20.2% 65.1%;
--accent: 217.2 32.6% 17.5%;
--accent-foreground: 210 40% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 40% 98%;
--border: 217.2 32.6% 17.5%;
--input: 217.2 32.6% 17.5%;
--ring: 0 100% 53%;
--radius: 0.5rem;
--sidebar: 0 0% 11%;
--sidebar-foreground: 210 40% 98%;
--sidebar-primary: 145 65% 50%;
--sidebar-primary-foreground: 0 0% 100%;
--sidebar-accent: 217.2 32.6% 17.5%;
--sidebar-accent-foreground: 210 40% 98%;
--sidebar-border: 217.2 32.6% 17.5%;
--sidebar-ring: 0 100% 53%;
}
.theme-starforce body::before {
content: "";
position: fixed;
inset: 0;
z-index: -1;
pointer-events: none;
background-image:
radial-gradient(1px 1px at 10% 20%, rgba(100,100,120,0.4) 0%, transparent 100%),
radial-gradient(1px 1px at 20% 40%, rgba(100,100,120,0.3) 0%, transparent 100%),
radial-gradient(1.5px 1.5px at 30% 10%, rgba(80,80,100,0.5) 0%, transparent 100%),
radial-gradient(1px 1px at 40% 60%, rgba(100,100,120,0.3) 0%, transparent 100%),
radial-gradient(1px 1px at 50% 80%, rgba(90,90,110,0.4) 0%, transparent 100%),
radial-gradient(1.5px 1.5px at 60% 30%, rgba(80,80,100,0.5) 0%, transparent 100%),
radial-gradient(1px 1px at 70% 50%, rgba(100,100,120,0.3) 0%, transparent 100%),
radial-gradient(1px 1px at 80% 90%, rgba(90,90,110,0.4) 0%, transparent 100%),
radial-gradient(1.5px 1.5px at 90% 15%, rgba(80,80,100,0.5) 0%, transparent 100%),
radial-gradient(1px 1px at 15% 70%, rgba(100,100,120,0.3) 0%, transparent 100%),
radial-gradient(1px 1px at 25% 90%, rgba(90,90,110,0.4) 0%, transparent 100%),
radial-gradient(1.5px 1.5px at 35% 35%, rgba(80,80,100,0.5) 0%, transparent 100%),
radial-gradient(1px 1px at 45% 15%, rgba(100,100,120,0.3) 0%, transparent 100%),
radial-gradient(1px 1px at 55% 75%, rgba(90,90,110,0.4) 0%, transparent 100%),
radial-gradient(1.5px 1.5px at 65% 55%, rgba(80,80,100,0.5) 0%, transparent 100%),
radial-gradient(1px 1px at 75% 25%, rgba(100,100,120,0.3) 0%, transparent 100%),
radial-gradient(1px 1px at 85% 65%, rgba(90,90,110,0.4) 0%, transparent 100%),
radial-gradient(1.5px 1.5px at 95% 45%, rgba(80,80,100,0.5) 0%, transparent 100%);
background-size: 200% 200%;
animation: drift 60s linear infinite;
}
+59
View File
@@ -0,0 +1,59 @@
{
"theme": {
"id": "starforce",
"name": "Starforce",
"description": "Dark cosmic theme with crimson accents, starfield background, and neon green glow effects",
"type": "dark",
"default": true
},
"colors": {
"background": "hsl(0, 0%, 10%)",
"foreground": "hsl(210, 40%, 98%)",
"card": "hsl(0, 0%, 9%)",
"cardForeground": "hsl(210, 40%, 98%)",
"popover": "hsl(222.2, 84%, 11%)",
"popoverForeground": "hsl(210, 40%, 98%)",
"primary": "hsl(0, 100%, 53%)",
"primaryForeground": "hsl(222.2, 47.4%, 11.2%)",
"secondary": "hsl(217.2, 32.6%, 17.5%)",
"secondaryForeground": "hsl(210, 40%, 98%)",
"muted": "hsl(217.2, 32.6%, 17.5%)",
"mutedForeground": "hsl(215, 20.2%, 65.1%)",
"accent": "hsl(217.2, 32.6%, 17.5%)",
"accentForeground": "hsl(210, 40%, 98%)",
"destructive": "hsl(0, 62.8%, 30.6%)",
"destructiveForeground": "hsl(210, 40%, 98%)",
"border": "hsl(217.2, 32.6%, 17.5%)",
"input": "hsl(217.2, 32.6%, 17.5%)",
"ring": "hsl(0, 100%, 53%)",
"sidebar": "hsl(0, 0%, 11%)",
"sidebarForeground": "hsl(210, 40%, 98%)",
"sidebarPrimary": "hsl(145, 65%, 50%)",
"sidebarPrimaryForeground": "hsl(0, 0%, 100%)",
"sidebarAccent": "hsl(217.2, 32.6%, 17.5%)",
"sidebarAccentForeground": "hsl(210, 40%, 98%)",
"sidebarBorder": "hsl(217.2, 32.6%, 17.5%)",
"sidebarRing": "hsl(0, 100%, 53%)"
},
"borderRadius": "0.5rem",
"typography": {
"fontFamily": "Inter, sans-serif",
"headingFont": "Inter, sans-serif"
},
"keyColors": {
"primary": "#FF0000",
"background": "#1a1a1a",
"card": "#171717",
"sidebar": "#1c1c1c",
"border": "#2a2a35",
"text": "#e8e8ef",
"mutedText": "#888888",
"glow": "#39ff14"
},
"effects": {
"starfield": true,
"starTwinkle": true,
"starDrift": true,
"textGlow": "#39ff14"
}
}
+6 -2
View File
@@ -26,8 +26,10 @@ import {
import { DashboardStats } from "@/types" import { DashboardStats } from "@/types"
import { CrossedLightsabers } from "@/components/dashboard/crossed-lightsabers" import { CrossedLightsabers } from "@/components/dashboard/crossed-lightsabers"
import { StarField } from "@/components/dashboard/star-field" import { StarField } from "@/components/dashboard/star-field"
import { useWebsiteTheme } from "@/providers/website-theme-provider"
export default function DashboardPage() { export default function DashboardPage() {
const { websiteTheme: theme } = useWebsiteTheme()
const [period, setPeriod] = useState("6months") const [period, setPeriod] = useState("6months")
const [stats, setStats] = useState<DashboardStats | null>(null) const [stats, setStats] = useState<DashboardStats | null>(null)
const pollingRef = useRef<NodeJS.Timeout | null>(null) const pollingRef = useRef<NodeJS.Timeout | null>(null)
@@ -65,20 +67,22 @@ export default function DashboardPage() {
return ( return (
<div className="space-y-6 relative"> <div className="space-y-6 relative">
<StarField /> {theme === "starforce" && <StarField />}
<div className="relative z-[1] space-y-6"> <div className="relative z-[1] space-y-6">
<div> <div>
<PageHeader <PageHeader
title={<span style={{fontFamily:"'Audiowide',sans-serif"}}>Dashboard</span>} title={<span style={{fontFamily:"'Audiowide',sans-serif"}}>Dashboard</span>}
description="Overview of your sales pipeline" description="Overview of your sales pipeline"
> >
{theme === "starforce" && (
<span <span
className="pointer-events-none select-none text-5xl text-gray-600 dark:text-[#b0f0c0] tracking-wider font-bold text-glow whitespace-nowrap" className="pointer-events-none select-none text-5xl text-gray-600 dark:text-[#b0f0c0] tracking-wider font-bold text-glow whitespace-nowrap"
style={{ fontFamily: "'Cormorant Garamond', serif" }} style={{ fontFamily: "'Cormorant Garamond', serif" }}
> >
Trust the Force, Find your Path Trust the Force, Find your Path
</span> </span>
<CrossedLightsabers /> )}
{theme === "starforce" && <CrossedLightsabers />}
<Select value={period} onValueChange={setPeriod}> <Select value={period} onValueChange={setPeriod}>
<SelectTrigger className="h-9 w-[160px]"> <SelectTrigger className="h-9 w-[160px]">
<ListFilter className="mr-2 h-4 w-4" /> <ListFilter className="mr-2 h-4 w-4" />
+2 -2
View File
@@ -12,7 +12,7 @@ export async function GET() {
[user.id], [user.id],
) )
const websiteTheme = result.rows[0]?.website_theme || "spidey" const websiteTheme = result.rows[0]?.website_theme || "starforce"
return NextResponse.json({ websiteTheme }) return NextResponse.json({ websiteTheme })
} catch (error) { } catch (error) {
console.error("Website theme GET error:", error) console.error("Website theme GET error:", error)
@@ -26,7 +26,7 @@ export async function PUT(request: NextRequest) {
if (!user) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }) if (!user) return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
const body = await request.json() const body = await request.json()
const theme = body.websiteTheme || "spidey" const theme = body.websiteTheme || "starforce"
await query( await query(
`UPDATE users SET preferences = preferences || $2::jsonb WHERE id = $1`, `UPDATE users SET preferences = preferences || $2::jsonb WHERE id = $1`,
-29
View File
@@ -406,35 +406,6 @@ main {
z-index: 0; z-index: 0;
} }
body::before {
content: "";
position: fixed;
inset: 0;
z-index: -1;
pointer-events: none;
background-image:
radial-gradient(1px 1px at 10% 20%, rgba(100,100,120,0.4) 0%, transparent 100%),
radial-gradient(1px 1px at 20% 40%, rgba(100,100,120,0.3) 0%, transparent 100%),
radial-gradient(1.5px 1.5px at 30% 10%, rgba(80,80,100,0.5) 0%, transparent 100%),
radial-gradient(1px 1px at 40% 60%, rgba(100,100,120,0.3) 0%, transparent 100%),
radial-gradient(1px 1px at 50% 80%, rgba(90,90,110,0.4) 0%, transparent 100%),
radial-gradient(1.5px 1.5px at 60% 30%, rgba(80,80,100,0.5) 0%, transparent 100%),
radial-gradient(1px 1px at 70% 50%, rgba(100,100,120,0.3) 0%, transparent 100%),
radial-gradient(1px 1px at 80% 90%, rgba(90,90,110,0.4) 0%, transparent 100%),
radial-gradient(1.5px 1.5px at 90% 15%, rgba(80,80,100,0.5) 0%, transparent 100%),
radial-gradient(1px 1px at 15% 70%, rgba(100,100,120,0.3) 0%, transparent 100%),
radial-gradient(1px 1px at 25% 90%, rgba(90,90,110,0.4) 0%, transparent 100%),
radial-gradient(1.5px 1.5px at 35% 35%, rgba(80,80,100,0.5) 0%, transparent 100%),
radial-gradient(1px 1px at 45% 15%, rgba(100,100,120,0.3) 0%, transparent 100%),
radial-gradient(1px 1px at 55% 75%, rgba(90,90,110,0.4) 0%, transparent 100%),
radial-gradient(1.5px 1.5px at 65% 55%, rgba(80,80,100,0.5) 0%, transparent 100%),
radial-gradient(1px 1px at 75% 25%, rgba(100,100,120,0.3) 0%, transparent 100%),
radial-gradient(1px 1px at 85% 65%, rgba(90,90,110,0.4) 0%, transparent 100%),
radial-gradient(1.5px 1.5px at 95% 45%, rgba(80,80,100,0.5) 0%, transparent 100%);
background-size: 200% 200%;
animation: drift 60s linear infinite;
}
@keyframes drift { @keyframes drift {
0% { background-position: 0% 0%; } 0% { background-position: 0% 0%; }
100% { background-position: 100% 100%; } 100% { background-position: 100% 100%; }
+5
View File
@@ -1,8 +1,11 @@
import type { Metadata, Viewport } from "next" import type { Metadata, Viewport } from "next"
import { Inter } from "next/font/google" import { Inter } from "next/font/google"
import { ThemeProvider } from "@/providers/theme-provider" import { ThemeProvider } from "@/providers/theme-provider"
import { WebsiteThemeProvider } from "@/providers/website-theme-provider"
import { Toaster } from "@/components/ui/sonner" import { Toaster } from "@/components/ui/sonner"
import "./globals.css" import "./globals.css"
import "../../Web_Backgrounds/spidey-theme.css"
import "../../Web_Backgrounds/starforce-theme.css"
const inter = Inter({ const inter = Inter({
variable: "--font-inter", variable: "--font-inter",
@@ -31,8 +34,10 @@ export default function RootLayout({
<html lang="en" suppressHydrationWarning> <html lang="en" suppressHydrationWarning>
<body className={`${inter.variable} min-h-screen antialiased`}> <body className={`${inter.variable} min-h-screen antialiased`}>
<ThemeProvider attribute="class" defaultTheme="dark" disableTransitionOnChange> <ThemeProvider attribute="class" defaultTheme="dark" disableTransitionOnChange>
<WebsiteThemeProvider>
{children} {children}
<Toaster /> <Toaster />
</WebsiteThemeProvider>
</ThemeProvider> </ThemeProvider>
</body> </body>
</html> </html>
+4 -1
View File
@@ -31,6 +31,8 @@ import {
Trash2, Trash2,
} from "lucide-react"; } from "lucide-react";
import { CrmIcon } from "./crm-icon"; import { CrmIcon } from "./crm-icon";
import { CaptainAmericaShield } from "@/components/shared/captain-america-shield";
import { useWebsiteTheme } from "@/providers/website-theme-provider";
interface TopbarProps { interface TopbarProps {
onMenuClick: () => void; onMenuClick: () => void;
@@ -39,6 +41,7 @@ interface TopbarProps {
export function Topbar({ onMenuClick }: TopbarProps) { export function Topbar({ onMenuClick }: TopbarProps) {
const router = useRouter(); const router = useRouter();
const { theme, setTheme } = useTheme(); const { theme, setTheme } = useTheme();
const { websiteTheme } = useWebsiteTheme();
const { user, logout } = useUser(); const { user, logout } = useUser();
const { notifications, unreadCount, markAsRead, markAllAsRead, dismiss } = const { notifications, unreadCount, markAsRead, markAllAsRead, dismiss } =
useNotifications(); useNotifications();
@@ -71,7 +74,7 @@ export function Topbar({ onMenuClick }: TopbarProps) {
<header className="sticky top-0 z-20 flex h-16 items-center gap-4 border-b bg-card dark:bg-[#141414] px-4 lg:px-6 relative overflow-hidden"> <header className="sticky top-0 z-20 flex h-16 items-center gap-4 border-b bg-card dark:bg-[#141414] px-4 lg:px-6 relative overflow-hidden">
{/* Logo */} {/* Logo */}
<div className="flex items-center gap-2 mr-2 shrink-0"> <div className="flex items-center gap-2 mr-2 shrink-0">
<CrmIcon /> {websiteTheme === "spidey" ? <CaptainAmericaShield /> : <CrmIcon />}
<span className="text-[#CC0000] dark:text-[#FF1111] font-bold text-sm tracking-wide"> <span className="text-[#CC0000] dark:text-[#FF1111] font-bold text-sm tracking-wide">
CRM CRM
</span> </span>
+38 -1
View File
@@ -6,7 +6,8 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/com
import { Label } from "@/components/ui/label" import { Label } from "@/components/ui/label"
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group" import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
import { Sun, Moon, Monitor } from "lucide-react" import { Sun, Moon, Monitor, Sparkles, Shield } from "lucide-react"
import { useWebsiteTheme } from "@/providers/website-theme-provider"
const COLOR_THEME_KEY = "crm-color-theme" const COLOR_THEME_KEY = "crm-color-theme"
@@ -29,6 +30,11 @@ const themeOptions = [
{ value: "ruby", label: "Ruby", color: "bg-red-600", ring: "ring-red-600" }, { value: "ruby", label: "Ruby", color: "bg-red-600", ring: "ring-red-600" },
] ]
const backgroundOptions = [
{ value: "spidey", label: "Spidey", icon: Shield, color: "bg-red-600", ring: "ring-red-600", desc: "Dark theme with red accents" },
{ value: "starforce", label: "StarForce", icon: Sparkles, color: "bg-red-700", ring: "ring-red-700", desc: "Cosmic dark with starfield & green glow" },
]
function getStoredColorTheme(): string { function getStoredColorTheme(): string {
if (typeof window === "undefined") return "default" if (typeof window === "undefined") return "default"
return localStorage.getItem(COLOR_THEME_KEY) || "default" return localStorage.getItem(COLOR_THEME_KEY) || "default"
@@ -46,6 +52,7 @@ function applyColorTheme(theme: string) {
export function ThemeSettings() { export function ThemeSettings() {
const { theme, setTheme } = useTheme() const { theme, setTheme } = useTheme()
const { websiteTheme, setWebsiteTheme } = useWebsiteTheme()
const [colorTheme, setColorTheme] = useState("default") const [colorTheme, setColorTheme] = useState("default")
const [mounted, setMounted] = useState(false) const [mounted, setMounted] = useState(false)
@@ -119,6 +126,36 @@ export function ThemeSettings() {
</RadioGroup> </RadioGroup>
</CardContent> </CardContent>
</Card> </Card>
<Card>
<CardHeader>
<CardTitle>Color Theme for Background</CardTitle>
<CardDescription>
Choose the website background theme.
</CardDescription>
</CardHeader>
<CardContent>
<RadioGroup value={websiteTheme} onValueChange={setWebsiteTheme} className="grid grid-cols-2 gap-4">
{backgroundOptions.map(({ value, label, icon: Icon, color, ring, desc }) => (
<div key={value}>
<RadioGroupItem value={value} id={`bg-${value}`} className="peer sr-only" />
<Label
htmlFor={`bg-${value}`}
className={cn(
"flex flex-col items-center gap-3 rounded-lg border-2 p-4 hover:bg-accent cursor-pointer transition-all",
"peer-data-[state=checked]:border-primary peer-data-[state=checked]:bg-primary/5"
)}
>
<Icon className="h-6 w-6" />
<div className={cn("h-8 w-8 rounded-full", color, ring, "ring-2 ring-offset-2 ring-offset-background")} />
<span className="text-sm font-medium">{label}</span>
<span className="text-xs text-muted-foreground">{desc}</span>
</Label>
</div>
))}
</RadioGroup>
</CardContent>
</Card>
</div> </div>
) )
} }
@@ -0,0 +1,31 @@
"use client"
export function CaptainAmericaShield() {
return (
<svg
width="28"
height="28"
viewBox="0 0 100 100"
aria-hidden="true"
>
<defs>
<clipPath id="shield-clip">
<polygon points="50,95 5,60 5,30 50,5 95,30 95,60" />
</clipPath>
</defs>
<g clip-path="url(#shield-clip)">
<circle cx="50" cy="45" r="45" fill="#c62828" />
<circle cx="50" cy="45" r="35" fill="#efefef" />
<circle cx="50" cy="45" r="25" fill="#c62828" />
<circle cx="50" cy="45" r="15" fill="#1565c0" />
<polygon points="50,32 56,47 72,48 60,58 64,75 50,64 36,75 40,58 28,48 44,47" fill="#efefef" />
</g>
<polygon
points="50,95 5,60 5,30 50,5 95,30 95,60"
fill="none"
stroke="#c62828"
stroke-width="1.5"
/>
</svg>
)
}
+5 -4
View File
@@ -5,7 +5,8 @@ import { createContext, useContext, useState, useEffect, ReactNode, useCallback
const WEBSITE_THEME_KEY = "crm-website-theme" const WEBSITE_THEME_KEY = "crm-website-theme"
const themeClasses: Record<string, string> = { const themeClasses: Record<string, string> = {
spidey: "", spidey: "theme-spidey",
starforce: "theme-starforce",
} }
interface WebsiteThemeContextValue { interface WebsiteThemeContextValue {
@@ -38,7 +39,7 @@ function storeTheme(theme: string) {
} }
export function WebsiteThemeProvider({ children }: { children: ReactNode }) { export function WebsiteThemeProvider({ children }: { children: ReactNode }) {
const [websiteTheme, setWebsiteThemeState] = useState<string>("spidey") const [websiteTheme, setWebsiteThemeState] = useState<string>("starforce")
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
useEffect(() => { useEffect(() => {
@@ -52,13 +53,13 @@ export function WebsiteThemeProvider({ children }: { children: ReactNode }) {
fetch("/api/settings/website-theme") fetch("/api/settings/website-theme")
.then((res) => (res.ok ? res.json() : null)) .then((res) => (res.ok ? res.json() : null))
.then((data) => { .then((data) => {
const theme = data?.websiteTheme || "spidey" const theme = data?.websiteTheme || "starforce"
setWebsiteThemeState(theme) setWebsiteThemeState(theme)
storeTheme(theme) storeTheme(theme)
applyWebsiteTheme(theme) applyWebsiteTheme(theme)
}) })
.catch(() => { .catch(() => {
applyWebsiteTheme("spidey") applyWebsiteTheme("starforce")
}) })
.finally(() => setLoading(false)) .finally(() => setLoading(false))
}, []) }, [])