Account settings in the same shape as a workspace's
The personal settings page gets the plain title the workspace's pages have, and the sidebar slides over to its sections (SSH keys, access tokens, connected applications) with an arrow back to mission control.
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 | } |