110 lines
3.2 KiB
TypeScript
110 lines
3.2 KiB
TypeScript
import { signal, computed } from '@preact/signals';
|
|
import { endpoints } from '../api/client';
|
|
import type { AuthLoginRequest, AuthLoginResponse, UserProfile } from '../api/types';
|
|
|
|
// Types
|
|
export interface UserPreferences {
|
|
theme: 'light' | 'dark' | 'auto';
|
|
sidebarCollapsed: boolean;
|
|
panelHeight: number;
|
|
defaultTeam: string;
|
|
keyboardShortcutsEnabled: boolean;
|
|
notificationsEnabled: boolean;
|
|
}
|
|
|
|
// Default preferences
|
|
const DEFAULT_PREFERENCES: UserPreferences = {
|
|
theme: 'auto',
|
|
sidebarCollapsed: false,
|
|
panelHeight: 280,
|
|
defaultTeam: 'ui',
|
|
keyboardShortcutsEnabled: true,
|
|
notificationsEnabled: true
|
|
};
|
|
|
|
// User State Signals
|
|
export const user = signal<UserProfile | null>(null);
|
|
export const preferences = signal<UserPreferences>(DEFAULT_PREFERENCES);
|
|
|
|
// Computed Values
|
|
export const isAuthenticated = computed(() => user.value !== null);
|
|
export const userId = computed(() => user.value?.id ?? 1);
|
|
export const userName = computed(() => user.value?.display_name ?? user.value?.email ?? 'Guest');
|
|
export const userInitials = computed(() => {
|
|
const name = user.value?.display_name ?? user.value?.email ?? 'Guest';
|
|
return name.split(' ').filter(Boolean).map(n => n[0]).join('').toUpperCase().slice(0, 2);
|
|
});
|
|
|
|
// Actions
|
|
export function loadUserPreferences(): void {
|
|
if (typeof window === 'undefined') return;
|
|
|
|
const saved = localStorage.getItem('dss-preferences');
|
|
if (saved) {
|
|
try {
|
|
const parsed = JSON.parse(saved);
|
|
preferences.value = { ...DEFAULT_PREFERENCES, ...parsed };
|
|
} catch {
|
|
preferences.value = DEFAULT_PREFERENCES;
|
|
}
|
|
}
|
|
}
|
|
|
|
export function saveUserPreferences(): void {
|
|
if (typeof window === 'undefined') return;
|
|
localStorage.setItem('dss-preferences', JSON.stringify(preferences.value));
|
|
}
|
|
|
|
export function updatePreferences(updates: Partial<UserPreferences>): void {
|
|
preferences.value = { ...preferences.value, ...updates };
|
|
saveUserPreferences();
|
|
}
|
|
|
|
export function setUser(userData: UserProfile | null): void {
|
|
user.value = userData;
|
|
}
|
|
|
|
export function logout(): void {
|
|
user.value = null;
|
|
localStorage.removeItem('dss-token');
|
|
}
|
|
|
|
export async function refreshUser(): Promise<void> {
|
|
if (typeof window === 'undefined') return;
|
|
|
|
const token = localStorage.getItem('dss-token');
|
|
if (!token) {
|
|
user.value = null;
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const profile = await endpoints.auth.me();
|
|
user.value = profile;
|
|
} catch {
|
|
// Token is invalid/expired; clear it.
|
|
localStorage.removeItem('dss-token');
|
|
user.value = null;
|
|
}
|
|
}
|
|
|
|
export async function loginWithAtlassian(credentials: AuthLoginRequest): Promise<AuthLoginResponse> {
|
|
const response = await endpoints.auth.login(credentials);
|
|
localStorage.setItem('dss-token', response.token);
|
|
// Prefer server truth; also supports older login response shapes.
|
|
user.value = {
|
|
id: response.user.id,
|
|
email: response.user.email,
|
|
display_name: response.user.display_name,
|
|
atlassian_url: response.user.atlassian_url,
|
|
atlassian_service: response.user.service,
|
|
last_login: new Date().toISOString(),
|
|
};
|
|
return response;
|
|
}
|
|
|
|
// Keyboard shortcuts state
|
|
export const keyboardShortcutsEnabled = computed(() =>
|
|
preferences.value.keyboardShortcutsEnabled
|
|
);
|