mirror of
https://github.com/YuuKi-OS/Yuuki-chat.git
synced 2026-02-18 22:01:09 +00:00
yuuki-deidad
This commit is contained in:
96
lib/auth-context.tsx
Normal file
96
lib/auth-context.tsx
Normal 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
67
lib/theme-context.tsx
Normal 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
6
lib/utils.ts
Normal 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))
|
||||
}
|
||||
Reference in New Issue
Block a user