113 lines · 4.3 KB
1 ---
2 import Base from '../layouts/Base.astro';
3 import { apiGet, apiPost } from '../lib/api';
4
5 const cookie = Astro.request.headers.get('cookie') || '';
6
7 let user: any = null;
8 let tokens: any[] = [];
9 let error = '';
10 let newToken = '';
11
12 try {
13 [user, tokens] = await Promise.all([
14 apiGet('/api/user', cookie),
15 apiGet('/api/user/tokens', cookie),
16 ]);
17 } catch (e: any) {
18 error = e.message;
19 }
20
21 if (Astro.request.method === 'POST') {
22 try {
23 const formData = await Astro.request.formData();
24 const action = formData.get('action');
25
26 if (action === 'create_token') {
27 const name = formData.get('name') as string;
28 const { data } = await apiPost('/api/tokens', { name }, cookie);
29 newToken = data.token;
30 // Refresh token list
31 tokens = await apiGet('/api/user/tokens', cookie);
32 }
33 } catch (e: any) {
34 error = e.message;
35 }
36 }
37 ---
38
39 <Base title="Settings">
40 <div class="container" style="max-width: 700px; padding-top: 24px;">
41 <h1 style="font-size: 1.5rem; margin-bottom: 24px;">Settings</h1>
42
43 {error && <div class="flash-error">{error}</div>}
44
45 {user && (
46 <>
47 {/* Profile */}
48 <div class="card" style="margin-bottom: 24px;">
49 <h2 style="font-size: 1.125rem; margin-bottom: 12px;">Profile</h2>
50 <div style="font-size: 0.875rem; display: grid; grid-template-columns: 120px 1fr; gap: 8px;">
51 <span style="color: var(--text-muted);">Username</span>
52 <span>{user.username}</span>
53 <span style="color: var(--text-muted);">Email</span>
54 <span>{user.email}</span>
55 <span style="color: var(--text-muted);">Member since</span>
56 <span>{user.created_at}</span>
57 </div>
58 </div>
59
60 {/* Personal Access Tokens */}
61 <div class="card" style="margin-bottom: 24px;">
62 <h2 style="font-size: 1.125rem; margin-bottom: 12px;">Personal Access Tokens</h2>
63 <p style="font-size: 0.8125rem; color: var(--text-muted); margin-bottom: 16px;">
64 Tokens are used for git push/pull authentication. Use them as the password in git credential helpers.
65 </p>
66
67 {newToken && (
68 <div style="background: var(--success-bg); border: 1px solid var(--success-border); padding: 12px; border-radius: var(--radius); margin-bottom: 16px;">
69 <div style="font-size: 0.8125rem; font-weight: 600; margin-bottom: 4px;">New token created — copy it now, it won't be shown again:</div>
70 <code style="background: var(--bg); padding: 4px 8px; border-radius: 4px; font-size: 0.875rem; word-break: break-all;">{newToken}</code>
71 </div>
72 )}
73
74 {/* Token list */}
75 {tokens.length > 0 && (
76 <div style="margin-bottom: 16px;">
77 {tokens.map((token: any) => (
78 <div style="display: flex; justify-content: space-between; align-items: center; padding: 8px 0; border-bottom: 1px solid var(--border); font-size: 0.875rem;">
79 <div>
80 <strong>{token.name}</strong>
81 <span style="color: var(--text-muted); margin-left: 8px;">
82 <code>{token.token_prefix}...</code>
83 </span>
84 {token.last_used_at && (
85 <span style="color: var(--text-muted); font-size: 0.75rem; margin-left: 8px;">
86 Last used: {token.last_used_at}
87 </span>
88 )}
89 </div>
90 </div>
91 ))}
92 </div>
93 )}
94
95 {/* Create token form */}
96 <form method="POST" style="display: flex; gap: 8px; align-items: end;">
97 <input type="hidden" name="action" value="create_token" />
98 <div class="form-group" style="margin-bottom: 0; flex: 1;">
99 <label for="token_name" style="font-size: 0.8125rem;">Token name</label>
100 <input type="text" id="token_name" name="name" required placeholder="e.g., laptop" />
101 </div>
102 <button type="submit" class="btn btn-primary" style="white-space: nowrap;">Create token</button>
103 </form>
104 </div>
105 </>
106 )}
107
108 {!user && !error && (
109 <p style="color: var(--text-muted);">Please <a href="/login">sign in</a> to access settings.</p>
110 )}
111 </div>
112 </Base>
113