Current state

This commit is contained in:
Chariah
2026-06-26 14:31:38 +02:00
commit 7a76841309
982 changed files with 451988 additions and 0 deletions
+146
View File
@@ -0,0 +1,146 @@
"use client"
import { createContext, useContext, useState, useCallback, useMemo, useEffect, ReactNode } from "react"
import { Notification, NotificationType } from "@/types"
interface NotificationContextValue {
notifications: Notification[]
unreadCount: number
unreadChatCount: number
addNotification: (type: NotificationType, title: string, description: string, link?: string) => void
markAsRead: (id: string) => void
markAllAsRead: () => void
dismiss: (id: string) => void
}
const NotificationContext = createContext<NotificationContextValue | null>(null)
export function NotificationProvider({ children }: { children: ReactNode }) {
const [notifications, setNotifications] = useState<Notification[]>([])
const [loading, setLoading] = useState(true)
const [unreadChatCount, setUnreadChatCount] = useState(0)
useEffect(() => {
async function fetchNotifications() {
try {
const res = await fetch("/api/notifications")
if (!res.ok) return
const data = await res.json()
if (data.notifications) {
setNotifications(data.notifications)
}
} catch {
console.warn("Failed to fetch notifications in notification provider")
} finally {
setLoading(false)
}
}
fetchNotifications()
}, [])
useEffect(() => {
let cancelled = false
async function fetchUnread() {
try {
const res = await fetch("/api/conversations")
if (!res.ok) return
const data = await res.json()
if (!cancelled) {
const list = data.conversations || []
const total = list.reduce((sum: number, c: any) => sum + (c.unread || 0), 0)
setUnreadChatCount(total)
}
} catch {
console.warn("Failed to fetch unread conversations in notification provider")
}
}
fetchUnread()
const id = setInterval(fetchUnread, 5000)
return () => { cancelled = true; clearInterval(id) }
}, [])
const unreadCount = useMemo(
() => notifications.filter((n) => !n.read).length,
[notifications]
)
const addNotification = useCallback(
async (type: NotificationType, title: string, description: string, link?: string) => {
const notif: Notification = {
id: `temp-${Date.now()}`,
type,
title,
description,
timestamp: new Date().toISOString(),
read: false,
link,
}
setNotifications((prev) => [notif, ...prev])
try {
await fetch("/api/notifications", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ type, title, description, link }),
})
} catch {
console.warn("Failed to add notification in notification provider")
}
},
[]
)
const markAsRead = useCallback(async (id: string) => {
if (id.startsWith("temp-")) return
setNotifications((prev) =>
prev.map((n) => (n.id === id ? { ...n, read: true } : n))
)
try {
await fetch(`/api/notifications/${id}`, { method: "PATCH" })
} catch {
console.warn("Failed to mark notification as read in notification provider")
}
}, [])
const markAllAsRead = useCallback(async () => {
setNotifications((prev) => prev.map((n) => ({ ...n, read: true })))
try {
await fetch("/api/notifications", { method: "PATCH" })
} catch {
console.warn("Failed to mark all notifications as read in notification provider")
}
}, [])
const dismiss = useCallback(async (id: string) => {
setNotifications((prev) => prev.filter((n) => n.id !== id))
if (id.startsWith("temp-")) return
try {
await fetch(`/api/notifications/${id}`, { method: "DELETE" })
} catch {
console.warn("Failed to dismiss notification in notification provider")
}
}, [])
return (
<NotificationContext.Provider
value={{
notifications,
unreadCount,
unreadChatCount,
addNotification,
markAsRead,
markAllAsRead,
dismiss,
}}
>
{children}
</NotificationContext.Provider>
)
}
export function useNotifications(): NotificationContextValue {
const ctx = useContext(NotificationContext)
if (!ctx) {
throw new Error("useNotifications must be used within a NotificationProvider")
}
return ctx
}
+15
View File
@@ -0,0 +1,15 @@
"use client"
import { ThemeProvider as NextThemesProvider } from "next-themes"
import { type ThemeProviderProps } from "next-themes"
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
return (
<NextThemesProvider
themes={["light", "dark", "system", "ocean", "forest", "sunset", "midnight"]}
{...props}
>
{children}
</NextThemesProvider>
)
}
+78
View File
@@ -0,0 +1,78 @@
"use client"
import { createContext, useContext, useState, useEffect, ReactNode, useCallback } from "react"
import { useRouter } from "next/navigation"
import type { User } from "@/types"
interface UserContextValue {
user: User | null
loading: boolean
error: string | null
logout: () => Promise<void>
updateAvatar: (url: string) => void
}
const UserContext = createContext<UserContextValue | null>(null)
export function UserProvider({ children }: { children: ReactNode }) {
const router = useRouter()
const [user, setUser] = useState<User | null>(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
const fetchUser = useCallback(async () => {
try {
const res = await fetch("/api/auth/me")
if (res.ok) {
const data = await res.json()
const u = data.user
setUser({
id: u.id,
name: `${u.firstName} ${u.lastName}`,
email: u.email,
role: u.role,
active: true,
avatar: u.avatar,
createdAt: new Date().toISOString(),
})
} else {
setUser(null)
}
} catch {
console.warn("Failed to fetch user in user provider")
setUser(null)
} finally {
setLoading(false)
}
}, [])
useEffect(() => {
fetchUser()
}, [fetchUser])
const logout = useCallback(async () => {
try {
await fetch("/api/auth/logout", { method: "POST" })
} catch {
console.warn("Failed to logout in user provider")
}
setUser(null)
router.push("/login")
}, [router])
const updateAvatar = useCallback((url: string) => {
setUser((prev) => (prev ? { ...prev, avatar: url } : prev))
}, [])
return (
<UserContext.Provider value={{ user, loading, error, logout, updateAvatar }}>
{children}
</UserContext.Provider>
)
}
export function useUser() {
const ctx = useContext(UserContext)
if (!ctx) throw new Error("useUser must be used within UserProvider")
return ctx
}
+92
View File
@@ -0,0 +1,92 @@
"use client"
import { createContext, useContext, useState, useEffect, ReactNode, useCallback } from "react"
const WEBSITE_THEME_KEY = "crm-website-theme"
const themeClasses: Record<string, string> = {
spidey: "",
}
interface WebsiteThemeContextValue {
websiteTheme: string
setWebsiteTheme: (theme: string) => Promise<void>
loading: boolean
}
const WebsiteThemeContext = createContext<WebsiteThemeContextValue | null>(null)
function applyWebsiteTheme(theme: string) {
const prefix = "theme-"
const root = document.documentElement
const classes = root.className.split(" ").filter((c) => !c.startsWith(prefix))
const cls = themeClasses[theme]
if (cls) {
classes.push(cls)
}
root.className = classes.join(" ").trim()
}
function getStoredTheme(): string | null {
if (typeof window === "undefined") return null
return localStorage.getItem(WEBSITE_THEME_KEY)
}
function storeTheme(theme: string) {
if (typeof window === "undefined") return
localStorage.setItem(WEBSITE_THEME_KEY, theme)
}
export function WebsiteThemeProvider({ children }: { children: ReactNode }) {
const [websiteTheme, setWebsiteThemeState] = useState<string>("spidey")
const [loading, setLoading] = useState(true)
useEffect(() => {
const stored = getStoredTheme()
if (stored) {
setWebsiteThemeState(stored)
applyWebsiteTheme(stored)
setLoading(false)
return
}
fetch("/api/settings/website-theme")
.then((res) => (res.ok ? res.json() : null))
.then((data) => {
const theme = data?.websiteTheme || "spidey"
setWebsiteThemeState(theme)
storeTheme(theme)
applyWebsiteTheme(theme)
})
.catch(() => {
applyWebsiteTheme("spidey")
})
.finally(() => setLoading(false))
}, [])
const setWebsiteTheme = useCallback(async (theme: string) => {
setWebsiteThemeState(theme)
storeTheme(theme)
applyWebsiteTheme(theme)
try {
await fetch("/api/settings/website-theme", {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ websiteTheme: theme }),
})
} catch {
console.warn("Failed to persist website theme")
}
}, [])
return (
<WebsiteThemeContext.Provider value={{ websiteTheme, setWebsiteTheme, loading }}>
{children}
</WebsiteThemeContext.Provider>
)
}
export function useWebsiteTheme() {
const ctx = useContext(WebsiteThemeContext)
if (!ctx) throw new Error("useWebsiteTheme must be used within WebsiteThemeProvider")
return ctx
}