Settings as panels, the way the portal does it
Four tabs over three cards was navigation for its own sake, and the two shells disagreed about what a settings screen looks like. It is one grid of cards now, matching the portal: the account card carries changing a password and deleting the account in its footer, appearance is a card with one row, and remote access — an operator's concern, not a personal preference — is a card of its own for a superuser. SettingRow, alert-dialog and UserAvatar come across from the portal, so an account renders the same face in both shells. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,25 +1,11 @@
|
||||
import { createFileRoute } from "@tanstack/react-router"
|
||||
import { motion } from "motion/react"
|
||||
|
||||
import Appearance from "@/components/UserSettings/Appearance"
|
||||
import ChangePassword from "@/components/UserSettings/ChangePassword"
|
||||
import DeleteAccount from "@/components/UserSettings/DeleteAccount"
|
||||
import RemoteAccess from "@/components/UserSettings/RemoteAccess"
|
||||
import UserInformation from "@/components/UserSettings/UserInformation"
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
||||
import useAuth from "@/hooks/useAuth"
|
||||
|
||||
const tabsConfig = [
|
||||
{ value: "my-profile", title: "My profile", component: UserInformation },
|
||||
{ value: "password", title: "Password", component: ChangePassword },
|
||||
{ value: "appearance", title: "Appearance", component: Appearance },
|
||||
{ value: "danger-zone", title: "Danger zone", component: DeleteAccount },
|
||||
]
|
||||
|
||||
// Whether this whole installation can be reached from outside is not a
|
||||
// personal preference, so the tab only exists for an operator.
|
||||
const superuserTabs = [
|
||||
{ value: "remote-access", title: "Remote access", component: RemoteAccess },
|
||||
]
|
||||
import { listStagger, slideUp } from "@/lib/motion"
|
||||
|
||||
export const Route = createFileRoute("/_layout/settings")({
|
||||
component: UserSettings,
|
||||
@@ -34,13 +20,6 @@ export const Route = createFileRoute("/_layout/settings")({
|
||||
|
||||
function UserSettings() {
|
||||
const { user: currentUser } = useAuth()
|
||||
// A superuser deleting its own account would lock everyone out.
|
||||
const finalTabs = currentUser?.is_superuser
|
||||
? [
|
||||
...tabsConfig.filter((tab) => tab.value !== "danger-zone"),
|
||||
...superuserTabs,
|
||||
]
|
||||
: tabsConfig
|
||||
|
||||
if (!currentUser) {
|
||||
return null
|
||||
@@ -49,26 +28,36 @@ function UserSettings() {
|
||||
return (
|
||||
<div className="flex flex-col gap-6">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold tracking-tight">User Settings</h1>
|
||||
<p className="text-muted-foreground">
|
||||
<h1 className="font-display text-3xl font-semibold">Settings</h1>
|
||||
<p className="max-w-2xl text-sm text-muted-foreground">
|
||||
Manage your account settings and preferences
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Tabs defaultValue="my-profile">
|
||||
<TabsList>
|
||||
{finalTabs.map((tab) => (
|
||||
<TabsTrigger key={tab.value} value={tab.value}>
|
||||
{tab.title}
|
||||
</TabsTrigger>
|
||||
))}
|
||||
</TabsList>
|
||||
{finalTabs.map((tab) => (
|
||||
<TabsContent key={tab.value} value={tab.value}>
|
||||
<tab.component />
|
||||
</TabsContent>
|
||||
))}
|
||||
</Tabs>
|
||||
{/* Panels rather than tabs, the way the portal does it: there are few
|
||||
enough of these to read at once, and hiding four behind a tab bar was
|
||||
navigation over three cards. Changing a password and deleting an
|
||||
account belong to the account, so they live in that card's footer. */}
|
||||
<motion.div
|
||||
className="grid items-start gap-6 lg:grid-cols-2"
|
||||
variants={listStagger}
|
||||
initial="hidden"
|
||||
animate="visible"
|
||||
>
|
||||
<motion.div variants={slideUp}>
|
||||
<UserInformation />
|
||||
</motion.div>
|
||||
<motion.div variants={slideUp}>
|
||||
<Appearance />
|
||||
</motion.div>
|
||||
{/* Whether this whole installation can be reached from outside is not
|
||||
a personal preference, so it only exists for an operator. */}
|
||||
{currentUser.is_superuser && (
|
||||
<motion.div variants={slideUp}>
|
||||
<RemoteAccess />
|
||||
</motion.div>
|
||||
)}
|
||||
</motion.div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user