- Copy admin-dashboard-web → dashboards/admin-web - Copy tracker-dashboard-web → dashboards/tracker-web - Update pnpm-workspace.yaml to include dashboards/* - Replace file: refs with workspace:* for @bytelyst/* packages - Replace all hardcoded LysnrAI/lysnn.com branding with generic platform refs - Make telemetry use NEXT_PUBLIC_PRODUCT_ID / PRODUCT_ID env vars - Update mock credentials, seed data, invitation codes, placeholders - Update READMEs, e2e tests, unit tests for product-agnostic content - Both dashboards pass tsc --noEmit clean
84 lines
2.1 KiB
TypeScript
84 lines
2.1 KiB
TypeScript
'use client';
|
|
|
|
import { createContext, useContext, useState, useEffect, useCallback, type ReactNode } from 'react';
|
|
|
|
interface User {
|
|
id: string;
|
|
email: string;
|
|
role: string;
|
|
displayName: string;
|
|
}
|
|
|
|
interface AuthState {
|
|
user: User | null;
|
|
token: string | null;
|
|
loading: boolean;
|
|
login: (email: string, password: string) => Promise<void>;
|
|
logout: () => void;
|
|
}
|
|
|
|
const AuthContext = createContext<AuthState>({
|
|
user: null,
|
|
token: null,
|
|
loading: true,
|
|
login: async () => {},
|
|
logout: () => {},
|
|
});
|
|
|
|
export function useAuth() {
|
|
return useContext(AuthContext);
|
|
}
|
|
|
|
export function AuthProvider({ children }: { children: ReactNode }) {
|
|
const [user, setUser] = useState<User | null>(null);
|
|
const [token, setToken] = useState<string | null>(null);
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
useEffect(() => {
|
|
const stored = localStorage.getItem('tracker_token');
|
|
if (stored) {
|
|
setToken(stored);
|
|
fetch('/api/auth/me', {
|
|
headers: { Authorization: `Bearer ${stored}` },
|
|
})
|
|
.then(res => (res.ok ? res.json() : Promise.reject()))
|
|
.then(data => setUser(data))
|
|
.catch(() => {
|
|
localStorage.removeItem('tracker_token');
|
|
setToken(null);
|
|
})
|
|
.finally(() => setLoading(false));
|
|
} else {
|
|
setLoading(false);
|
|
}
|
|
}, []);
|
|
|
|
const login = useCallback(async (email: string, password: string) => {
|
|
const res = await fetch('/api/auth/login', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ email, password }),
|
|
});
|
|
if (!res.ok) {
|
|
const body = await res.json().catch(() => ({}));
|
|
throw new Error(body.error || 'Login failed');
|
|
}
|
|
const data = await res.json();
|
|
localStorage.setItem('tracker_token', data.accessToken);
|
|
setToken(data.accessToken);
|
|
setUser(data.user);
|
|
}, []);
|
|
|
|
const logout = useCallback(() => {
|
|
localStorage.removeItem('tracker_token');
|
|
setToken(null);
|
|
setUser(null);
|
|
}, []);
|
|
|
|
return (
|
|
<AuthContext.Provider value={{ user, token, loading, login, logout }}>
|
|
{children}
|
|
</AuthContext.Provider>
|
|
);
|
|
}
|