yuuki-deidad

This commit is contained in:
aguitauwu
2026-02-09 19:36:27 -06:00
commit efdbd95700
34 changed files with 5639 additions and 0 deletions

96
lib/auth-context.tsx Normal file
View File

@@ -0,0 +1,96 @@
"use client";
import React, {
createContext,
useContext,
useState,
useCallback,
useEffect,
} from "react";
export type TokenSource = "yuuki-api" | "huggingface" | "demo";
interface AuthContextValue {
token: string | null;
tokenSource: TokenSource | null;
setAuth: (token: string, source: TokenSource) => void;
logout: () => void;
isAuthenticated: boolean;
}
const AuthContext = createContext<AuthContextValue | undefined>(undefined);
export function AuthProvider({ children }: { children: React.ReactNode }) {
const [token, setToken] = useState<string | null>(null);
const [tokenSource, setTokenSource] = useState<TokenSource | null>(null);
const [mounted, setMounted] = useState(false);
useEffect(() => {
const stored = localStorage.getItem("yuuki-token");
const storedSource = localStorage.getItem(
"yuuki-token-source"
) as TokenSource | null;
if (stored && storedSource) {
setToken(stored);
setTokenSource(storedSource);
}
setMounted(true);
}, []);
const setAuth = useCallback((t: string, source: TokenSource) => {
setToken(t);
setTokenSource(source);
if (source === "demo") {
// Don't persist demo sessions for security
localStorage.setItem("yuuki-token", "__demo__");
localStorage.setItem("yuuki-token-source", "demo");
} else {
localStorage.setItem("yuuki-token", t);
localStorage.setItem("yuuki-token-source", source);
}
}, []);
const logout = useCallback(() => {
setToken(null);
setTokenSource(null);
localStorage.removeItem("yuuki-token");
localStorage.removeItem("yuuki-token-source");
}, []);
// Prevent flash of token screen before localStorage is read
if (!mounted) {
return (
<AuthContext.Provider
value={{
token: null,
tokenSource: null,
setAuth,
logout,
isAuthenticated: false,
}}
>
{null}
</AuthContext.Provider>
);
}
return (
<AuthContext.Provider
value={{
token,
tokenSource,
setAuth,
logout,
isAuthenticated: !!token,
}}
>
{children}
</AuthContext.Provider>
);
}
export function useAuth() {
const ctx = useContext(AuthContext);
if (!ctx) throw new Error("useAuth must be used within AuthProvider");
return ctx;
}

67
lib/theme-context.tsx Normal file
View File

@@ -0,0 +1,67 @@
"use client";
import React, { createContext, useContext, useState, useEffect, useCallback } from "react";
export type ThemeMode = "light" | "dark" | "pastel";
interface ThemeContextValue {
mode: ThemeMode;
setMode: (mode: ThemeMode) => void;
accentColor: string;
setAccentColor: (hex: string) => void;
}
const ThemeContext = createContext<ThemeContextValue | undefined>(undefined);
export function YuukiThemeProvider({ children }: { children: React.ReactNode }) {
const [mode, setModeState] = useState<ThemeMode>("dark");
const [accentColor, setAccentColorState] = useState("#fafafa");
useEffect(() => {
const stored = localStorage.getItem("yuuki-theme-mode") as ThemeMode | null;
const storedAccent = localStorage.getItem("yuuki-accent-color");
if (stored) setModeState(stored);
if (storedAccent) setAccentColorState(storedAccent);
}, []);
const applyMode = useCallback((m: ThemeMode) => {
const html = document.documentElement;
html.classList.remove("dark", "pastel");
if (m === "dark") html.classList.add("dark");
if (m === "pastel") html.classList.add("pastel");
}, []);
const applyAccent = useCallback((hex: string) => {
document.documentElement.style.setProperty("--user-accent", hex);
}, []);
useEffect(() => {
applyMode(mode);
}, [mode, applyMode]);
useEffect(() => {
applyAccent(accentColor);
}, [accentColor, applyAccent]);
const setMode = useCallback((m: ThemeMode) => {
setModeState(m);
localStorage.setItem("yuuki-theme-mode", m);
}, []);
const setAccentColor = useCallback((hex: string) => {
setAccentColorState(hex);
localStorage.setItem("yuuki-accent-color", hex);
}, []);
return (
<ThemeContext.Provider value={{ mode, setMode, accentColor, setAccentColor }}>
{children}
</ThemeContext.Provider>
);
}
export function useYuukiTheme() {
const ctx = useContext(ThemeContext);
if (!ctx) throw new Error("useYuukiTheme must be used within YuukiThemeProvider");
return ctx;
}

6
lib/utils.ts Normal file
View File

@@ -0,0 +1,6 @@
import { clsx, type ClassValue } from 'clsx'
import { twMerge } from 'tailwind-merge'
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}