Account settings in the same shape as a workspace's #4
Mergedsyntaqx merged account-settings into main
2 files+54−110/2 viewed
| 285 | 285 | ); | |
| 286 | 286 | } | |
| 287 | 287 | ||
| 288 | + | /** Your own settings, as the sidebar shows them on the settings page. */ | |
| 289 | + | function AccountSettingsMenu({ open }: { open: boolean }) { | |
| 290 | + | const { hash } = useLocation(); | |
| 291 | + | const item = (id: string, icon: ReactNode, label: string) => ( | |
| 292 | + | <Link | |
| 293 | + | to={`/settings#${id}`} | |
| 294 | + | className={`group flex h-8 items-center gap-2.5 rounded-md px-2 text-[0.8125rem] transition-colors ${ | |
| 295 | + | hash === `#${id}` ? "bg-raised font-medium text-fg" : "text-muted hover:bg-raised/60 hover:text-fg" | |
| 296 | + | }`} | |
| 297 | + | > | |
| 298 | + | <span className="shrink-0 text-faint group-hover:text-muted">{icon}</span> | |
| 299 | + | {label} | |
| 300 | + | </Link> | |
| 301 | + | ); | |
| 302 | + | return ( | |
| 303 | + | <nav | |
| 304 | + | aria-label="Your settings" | |
| 305 | + | inert={!open} | |
| 306 | + | className={`${SLIDE} ${open ? "translate-x-0 opacity-100" : "pointer-events-none translate-x-full opacity-0"}`} | |
| 307 | + | > | |
| 308 | + | <Link | |
| 309 | + | to="/" | |
| 310 | + | className="group mt-3 flex h-8 items-center gap-2 rounded-md px-2 text-[0.8125rem] text-muted transition-colors hover:bg-raised/60 hover:text-fg" | |
| 311 | + | > | |
| 312 | + | <ArrowLeft size={15} className="text-faint transition-transform group-hover:-translate-x-0.5 group-hover:text-muted" /> | |
| 313 | + | Mission control | |
| 314 | + | </Link> | |
| 315 | + | <SidebarGroup title="Account"> | |
| 316 | + | {item("ssh-keys", <KeyRound size={15} />, "SSH keys")} | |
| 317 | + | {item("tokens", <Lock size={15} />, "Access tokens")} | |
| 318 | + | {item("applications", <Plug size={15} />, "Connected applications")} | |
| 319 | + | </SidebarGroup> | |
| 320 | + | </nav> | |
| 321 | + | ); | |
| 322 | + | } | |
| 323 | + | ||
| 288 | 324 | function Sidebar({ user, shell, onFind }: { user: User; shell: ShellData; onFind: () => void }) { | |
| 289 | 325 | const ws = shell.workspace; | |
| 290 | 326 | const { pathname } = useLocation(); | |
| 292 | 328 | // On a workspace's settings page, or on the way to one, its settings take | |
| 293 | 329 | // the sidebar over. | |
| 294 | 330 | const inSettings = ws != null && SETTINGS_PAGE.exec(going ?? pathname)?.[1]?.toLowerCase() === ws.slug; | |
| 331 | + | const inAccount = (going ?? pathname) === "/settings"; | |
| 332 | + | const away = inSettings || inAccount; | |
| 295 | 333 | const active = shell.repo; | |
| 296 | 334 | const repoBase = active ? `/${active.namespace}/${active.name}` : null; | |
| 297 | 335 | // The repository being looked at is listed even when it is someone else's. | |
| 325 | 363 | <div className="relative min-h-0 grow overflow-hidden"> | |
| 326 | 364 | <nav | |
| 327 | 365 | aria-label="g1t" | |
| 328 | − | inert={inSettings} | |
| 329 | − | className={`${SLIDE} ${inSettings ? "pointer-events-none -translate-x-1/3 opacity-0" : "translate-x-0 opacity-100"}`} | |
| 366 | + | inert={away} | |
| 367 | + | className={`${SLIDE} ${away ? "pointer-events-none -translate-x-1/3 opacity-0" : "translate-x-0 opacity-100"}`} | |
| 330 | 368 | > | |
| 331 | 369 | <div className="mt-3 space-y-px"> | |
| 332 | 370 | <SidebarLink to="/" end icon={<LayoutDashboard size={15} />}> | |
| 438 | 476 | </SidebarGroup> | |
| 439 | 477 | </nav> | |
| 440 | 478 | {ws && <SettingsMenu slug={ws.slug} owner={ws.role === "owner"} open={inSettings} />} | |
| 479 | + | <AccountSettingsMenu open={inAccount} /> | |
| 441 | 480 | </div> | |
| 442 | 481 | ||
| 443 | 482 | <div className="space-y-2 p-2"> | |
| 472 | 511 | if (parts.length === 0) return <span className="text-sm font-medium">Mission control</span>; | |
| 473 | 512 | if (reserved.includes(parts[0]!)) { | |
| 474 | 513 | const words: Record<string, string> = { | |
| 475 | − | settings: "Your settings", | |
| 514 | + | settings: "Account", | |
| 476 | 515 | explore: "Explore", | |
| 477 | 516 | new: "New repository", | |
| 478 | 517 | search: "Search", |
| 80 | 80 | }: Route.ComponentProps) { | |
| 81 | 81 | const { user, keys, tokens, applications } = loaderData; | |
| 82 | 82 | return ( | |
| 83 | − | <main className="mx-auto max-w-2xl space-y-12 px-4 py-12"> | |
| 84 | − | <h1 className="text-xl font-semibold"> | |
| 85 | − | Settings <span className="font-mono text-muted">{user.username}</span> | |
| 86 | − | </h1> | |
| 87 | − | ||
| 88 | − | <section> | |
| 83 | + | <main className="mx-auto max-w-5xl px-4 py-10 sm:px-8"> | |
| 84 | + | <header className="mb-8 border-b border-line pb-6"> | |
| 85 | + | <h1 className="text-2xl font-semibold tracking-tight">Account</h1> | |
| 86 | + | <p className="mt-1.5 text-sm text-muted"> | |
| 87 | + | How <span className="font-mono text-fg">{user.username}</span> signs in from git, tools and agents. | |
| 88 | + | </p> | |
| 89 | + | </header> | |
| 90 | + | <div className="max-w-2xl space-y-12"> | |
| 91 | + | <section id="ssh-keys" className="scroll-mt-20"> | |
| 89 | 92 | <h2 className="font-medium">SSH keys</h2> | |
| 90 | 93 | <ul className="mt-4 divide-y divide-line rounded-md border border-line empty:hidden"> | |
| 91 | 94 | {keys.map((key) => ( | |
| 113 | 116 | </Form> | |
| 114 | 117 | </section> | |
| 115 | 118 | ||
| 116 | − | <section> | |
| 119 | + | <section id="tokens" className="scroll-mt-20"> | |
| 117 | 120 | <h2 className="font-medium">Access tokens</h2> | |
| 118 | 121 | <p className="mt-1 text-sm text-muted"> | |
| 119 | 122 | Use a token as the password when git asks for one over HTTPS, and to | |
| 176 | 179 | </Form> | |
| 177 | 180 | </section> | |
| 178 | 181 | ||
| 179 | − | <section> | |
| 182 | + | <section id="applications" className="scroll-mt-20"> | |
| 180 | 183 | <h2 className="font-medium">Connected applications</h2> | |
| 181 | 184 | <p className="mt-1 text-sm text-muted"> | |
| 182 | 185 | Applications you signed in to through your browser, such as an agent | |
| 206 | 209 | </ul> | |
| 207 | 210 | )} | |
| 208 | 211 | </section> | |
| 212 | + | </div> | |
| 209 | 213 | </main> | |
| 210 | 214 | ); | |
| 211 | 215 | } |