my own indieAuth provider! indiko.dunkirk.sh/docs
indieauth oauth2-server
at main 335 lines 8.1 kB view raw
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>