my own indieAuth provider!
indiko.dunkirk.sh/docs
indieauth
oauth2-server
1<!doctype html>
2<html lang="en">
3
4<head>
5 <meta charset="UTF-8" />
6 <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7 <title>dashboard • indiko</title>
8 <meta name="description" content="Your Indiko dashboard - manage your profile, apps, and passkeys" />
9 <link rel="icon" href="../../public/favicon.svg" type="image/svg+xml" />
10
11 <!-- Open Graph / Facebook -->
12 <meta property="og:type" content="website" />
13 <meta property="og:title" content="Dashboard • Indiko" />
14 <meta property="og:description" content="Your Indiko dashboard - manage your profile, apps, and passkeys" />
15
16 <!-- Twitter -->
17 <meta name="twitter:card" content="summary" />
18 <meta name="twitter:title" content="Dashboard • Indiko" />
19 <meta name="twitter:description" content="Your Indiko dashboard - manage your profile, apps, and passkeys" />
20 <link rel="preconnect" href="https://fonts.googleapis.com">
21 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
22 <link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300..700&display=swap" rel="stylesheet">
23 <link rel="stylesheet" href="../styles.css">
24 <style>
25 /* Dashboard-specific styles */
26 .profile-section {
27 background: rgba(188, 141, 160, 0.05);
28 border: 1px solid var(--old-rose);
29 padding: 2rem;
30 margin-bottom: 2rem;
31 }
32
33 .section-title {
34 font-size: 1.25rem;
35 font-weight: 600;
36 color: var(--lavender);
37 margin-bottom: 1.5rem;
38 }
39
40 .avatar-upload {
41 display: flex;
42 align-items: center;
43 gap: 1.5rem;
44 margin-bottom: 2rem;
45 }
46
47 .profile-avatar {
48 width: 80px;
49 height: 80px;
50 border: 3px solid var(--berry-crush);
51 font-size: 2rem;
52 }
53
54 .avatar-controls {
55 display: flex;
56 flex-direction: column;
57 gap: 0.5rem;
58 }
59
60 .profile-info {
61 flex: 1;
62 }
63
64 .profile-name {
65 font-size: 1.5rem;
66 font-weight: 700;
67 color: var(--lavender);
68 margin-bottom: 0.25rem;
69 }
70
71 .profile-username {
72 font-size: 1rem;
73 color: var(--old-rose);
74 margin-bottom: 0.5rem;
75 }
76
77 .profile-links {
78 display: flex;
79 gap: 1rem;
80 font-size: 0.875rem;
81 }
82
83 .profile-links a {
84 color: var(--berry-crush);
85 text-decoration: none;
86 }
87
88 .profile-links a:hover {
89 text-decoration: underline;
90 }
91
92 h1 {
93 margin-bottom: 0.5rem;
94 }
95
96 .dashboard-grid {
97 display: grid;
98 grid-template-columns: 1fr;
99 gap: 1.5rem;
100 margin-bottom: 2rem;
101 }
102
103 @media (min-width: 768px) {
104 .dashboard-grid {
105 grid-template-columns: 1fr 1fr;
106 }
107 }
108
109 input[type="text"],
110 input[type="email"],
111 input[type="url"] {
112 margin-bottom: 1.5rem;
113 }
114
115 .button-group {
116 display: flex;
117 gap: 1rem;
118 margin-top: 1.5rem;
119 }
120
121 .button-group button {
122 flex: 1;
123 }
124
125 .message {
126 padding: 0.75rem;
127 margin-top: 1rem;
128 font-size: 0.875rem;
129 }
130
131 .apps-preview {
132 display: flex;
133 flex-direction: column;
134 gap: 0.75rem;
135 }
136
137 .app-item {
138 padding: 0.75rem;
139 background: rgba(12, 23, 19, 0.6);
140 border: 1px solid var(--rosewood);
141 display: flex;
142 justify-content: space-between;
143 align-items: center;
144 }
145
146 .app-name {
147 font-weight: 500;
148 color: var(--lavender);
149 }
150
151 .app-date {
152 font-size: 0.875rem;
153 color: var(--old-rose);
154 }
155
156 .view-all {
157 display: block;
158 text-align: center;
159 margin-top: 1rem;
160 color: var(--berry-crush);
161 text-decoration: none;
162 font-size: 0.875rem;
163 }
164
165 .view-all:hover {
166 text-decoration: underline;
167 }
168
169 .toast {
170 position: fixed;
171 bottom: 2rem;
172 right: 2rem;
173 background: var(--mahogany);
174 border: 2px solid var(--berry-crush);
175 padding: 1rem 1.5rem;
176 color: var(--lavender);
177 font-size: 0.875rem;
178 font-weight: 500;
179 z-index: 2000;
180 opacity: 0;
181 transform: translateY(1rem);
182 transition: opacity 0.3s, transform 0.3s;
183 max-width: 25rem;
184 box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.5);
185 }
186
187 .toast.show {
188 opacity: 1;
189 transform: translateY(0);
190 }
191
192 .toast.error {
193 border-color: var(--rosewood);
194 }
195
196 .toast.success {
197 border-color: var(--berry-crush);
198 }
199
200 .passkey-item {
201 padding: 0.75rem;
202 background: rgba(12, 23, 19, 0.6);
203 border: 1px solid var(--rosewood);
204 display: flex;
205 justify-content: space-between;
206 align-items: center;
207 gap: 1rem;
208 }
209
210 .passkey-info {
211 flex: 1;
212 }
213
214 .passkey-name {
215 font-weight: 500;
216 color: var(--lavender);
217 margin-bottom: 0.25rem;
218 }
219
220 .passkey-date {
221 font-size: 0.75rem;
222 color: var(--old-rose);
223 }
224
225 .passkey-actions {
226 display: flex;
227 gap: 0.5rem;
228 }
229
230 .passkey-actions button {
231 padding: 0.375rem 0.75rem;
232 font-size: 0.75rem;
233 margin: 0;
234 }
235
236 .rename-form {
237 display: flex;
238 gap: 0.5rem;
239 width: 100%;
240 }
241
242 .rename-form input {
243 flex: 1;
244 margin: 0;
245 padding: 0.375rem 0.75rem;
246 font-size: 0.875rem;
247 }
248
249 .rename-form button {
250 padding: 0.375rem 0.75rem;
251 font-size: 0.75rem;
252 margin: 0;
253 }
254 </style>
255</head>
256
257<body>
258 <header>
259 <h1 id="welcome">welcome</h1>
260 <p class="subtitle" id="subtitle">loading...</p>
261 </header>
262
263 <main>
264 <div class="profile-section">
265 <h2 class="section-title">recent apps</h2>
266 <div id="recentApps" class="apps-preview">
267 <div class="loading">loading...</div>
268 </div>
269 </div>
270
271 <div class="profile-section">
272 <h2 class="section-title">passkeys</h2>
273 <p style="color: var(--old-rose); margin-bottom: 1.5rem; line-height: 1.6;">
274 Manage your passkeys for secure, password-free authentication
275 </p>
276 <div id="passkeysList" class="apps-preview">
277 <div class="loading">loading...</div>
278 </div>
279 <button type="button" id="addPasskeyBtn" style="margin-top: 1rem;">
280 add new passkey
281 </button>
282 </div>
283
284 <div class="profile-section">
285 <h2 class="section-title">profile settings</h2>
286
287 <form id="profileForm">
288 <div class="avatar-upload">
289 <div class="profile-avatar" id="avatarPreview">
290 <!-- Avatar will be rendered here -->
291 </div>
292 <div class="avatar-controls">
293 <label for="photo">avatar url</label>
294 <input type="url" id="photo" name="photo" placeholder="https://example.com/avatar.jpg" />
295 <small style="color: var(--old-rose); font-size: 0.75rem;">Enter a URL to an image</small>
296 </div>
297 </div>
298
299 <label for="username">username</label>
300 <input type="text" id="username" name="username" required disabled />
301
302 <label for="name">display name</label>
303 <input type="text" id="name" name="name" required />
304
305 <label for="email">email</label>
306 <input type="email" id="email" name="email" />
307
308 <label for="url">website</label>
309 <input type="url" id="url" name="url" placeholder="https://example.com" />
310
311 <button type="submit" id="saveBtn">save changes</button>
312 </form>
313 </div>
314
315 <div class="profile-section" id="dangerZone" style="border-color: var(--rosewood); display: none;">
316 <h2 class="section-title" style="color: var(--rosewood);">danger zone</h2>
317 <p style="color: var(--old-rose); margin-bottom: 1.5rem; line-height: 1.6;">
318 Permanently delete your account and all associated data. This action cannot be undone.
319 </p>
320 <button type="button" id="deleteAccountBtn" style="width: 100%;">
321 delete my account
322 </button>
323 </div>
324 </main>
325
326 <footer id="footer">
327 loading...
328 </footer>
329
330 <div id="toast" class="toast"></div>
331
332 <script type="module" src="../client/index.ts"></script>
333</body>
334
335</html>