Skip to content

Commit 1d9cfe1

Browse files
committed
feat: Add client-side user management, authentication utilities, and new UI pages for content and file operations.
1 parent 53b874d commit 1d9cfe1

File tree

1 file changed

+112
-0
lines changed

1 file changed

+112
-0
lines changed

src/client/utils/auth.ts

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
export type UserRole = 'admin' | 'user' | 'viewer' | string;
2+
3+
export interface User {
4+
id: string;
5+
username: string;
6+
email?: string | null;
7+
name: string;
8+
role: UserRole;
9+
avatar?: string | null;
10+
}
11+
12+
type AuthListener = (user: User | null) => void;
13+
14+
class AuthProvider {
15+
private user: User | null = null;
16+
private listeners: Set<AuthListener> = new Set();
17+
18+
constructor() {
19+
this.loadUser();
20+
}
21+
22+
private loadUser() {
23+
const storedUser = localStorage.getItem('user');
24+
if (storedUser) {
25+
try {
26+
this.user = JSON.parse(storedUser);
27+
} catch (e) {
28+
console.error('Failed to parse stored user', e);
29+
this.user = null;
30+
}
31+
}
32+
}
33+
34+
public getUser(): User | null {
35+
return this.user;
36+
}
37+
38+
public getAccessToken(): string {
39+
return localStorage.getItem('accessToken') || this.readCookie('accessToken') || '';
40+
}
41+
42+
private readCookie(name: string): string | undefined {
43+
return document.cookie
44+
.split(';')
45+
.map(cookie => cookie.trim())
46+
.find(cookie => cookie.startsWith(`${name}=`))
47+
?.split('=')[1];
48+
}
49+
50+
public getAuthHeaders(): Record<string, string> {
51+
const token = this.getAccessToken();
52+
return token ? { 'Authorization': `Bearer ${token}` } : {};
53+
}
54+
55+
public subscribe(listener: AuthListener) {
56+
this.listeners.add(listener);
57+
listener(this.user);
58+
return () => this.listeners.delete(listener);
59+
}
60+
61+
private notify() {
62+
this.listeners.forEach(listener => listener(this.user));
63+
}
64+
65+
public updateUser(userData: Partial<User>) {
66+
if (!this.user) return;
67+
this.user = { ...this.user, ...userData };
68+
localStorage.setItem('user', JSON.stringify(this.user));
69+
this.notify();
70+
}
71+
72+
public async uploadAvatar(file: File): Promise<string> {
73+
const formData = new FormData();
74+
formData.append('avatar', file);
75+
76+
const response = await fetch('/api/auth/me/avatar', {
77+
method: 'POST',
78+
headers: this.getAuthHeaders(),
79+
body: formData
80+
});
81+
82+
if (!response.ok) {
83+
const error = await response.json();
84+
throw new Error(error.message || 'Failed to upload avatar');
85+
}
86+
87+
const result = await response.json();
88+
const avatarUrl = result.result.avatarUrl;
89+
90+
this.updateUser({ avatar: avatarUrl });
91+
return avatarUrl;
92+
}
93+
94+
public logout() {
95+
localStorage.removeItem('accessToken');
96+
localStorage.removeItem('refreshToken');
97+
localStorage.removeItem('user');
98+
// Clear cookies if any
99+
document.cookie = "accessToken=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;";
100+
this.user = null;
101+
this.notify();
102+
window.location.href = '/login';
103+
}
104+
105+
public isAuthenticated(): boolean {
106+
return !!this.getAccessToken() && !!this.user;
107+
}
108+
}
109+
110+
export const authProvider = new AuthProvider();
111+
// @ts-ignore - expose to window for easier access in inline scripts if needed
112+
window.authProvider = authProvider;

0 commit comments

Comments
 (0)