refactor(teams): team settings surface becomes the team detail page

Align with the house detail-page convention (issueDetail/projectDetail/...):
- route: /team/KEY/settings -> /team/KEY (paths.teamDetail), web page moved
  up a segment, desktop route + TeamDetailRoute renamed
- component: TeamSettingsPage -> TeamDetailPage
- sidebar: settingsHref prop -> detailHref, drop the /settings-suffix strip
- layout: the two-column Linear-style shell collapses to a single max-w-xl
  column (identity -> members -> goto -> danger) — content is too sparse for
  an aside

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Naiyuan Qing
2026-07-06 16:17:16 +08:00
committed by Jiayuan Zhang
parent 5b91457c10
commit e7f81d930d
10 changed files with 36 additions and 41 deletions

View File

@@ -3,7 +3,7 @@ import {
TeamIssuesPage,
TeamProjectsPage,
TeamAutopilotsPage,
TeamSettingsPage,
TeamDetailPage,
} from "@multica/views/teams";
// Router wrappers: resolve the :teamKey param and hand it to the shared
@@ -27,8 +27,8 @@ export function TeamAutopilotsRoute() {
return <TeamAutopilotsPage teamKey={teamKey} />;
}
export function TeamSettingsRoute() {
export function TeamDetailRoute() {
const { teamKey } = useParams<{ teamKey: string }>();
if (!teamKey) return null;
return <TeamSettingsPage teamKey={teamKey} />;
return <TeamDetailPage teamKey={teamKey} />;
}

View File

@@ -16,7 +16,7 @@ import {
TeamIssuesRoute,
TeamProjectsRoute,
TeamAutopilotsRoute,
TeamSettingsRoute,
TeamDetailRoute,
} from "./pages/team-surface-pages";
import { IssuesPage } from "@multica/views/issues/components";
import { ProjectsPage } from "@multica/views/projects/components";
@@ -174,9 +174,9 @@ export const appRoutes: RouteObject[] = [
handle: { title: "Team autopilots" },
},
{
path: "team/:teamKey/settings",
element: <TeamSettingsRoute />,
handle: { title: "Team settings" },
path: "team/:teamKey",
element: <TeamDetailRoute />,
handle: { title: "Team detail" },
},
{
path: "projects/:id",

View File

@@ -1,7 +1,7 @@
"use client";
import { use } from "react";
import { TeamSettingsPage } from "@multica/views/teams";
import { TeamDetailPage } from "@multica/views/teams";
export default function Page({
params,
@@ -9,5 +9,5 @@ export default function Page({
params: Promise<{ teamKey: string }>;
}) {
const { teamKey } = use(params);
return <TeamSettingsPage teamKey={teamKey} />;
return <TeamDetailPage teamKey={teamKey} />;
}

View File

@@ -32,7 +32,7 @@ function workspaceScoped(slug: string) {
teamIssues: (key: string) => `${ws}/team/${encode(key)}/issues`,
teamProjects: (key: string) => `${ws}/team/${encode(key)}/projects`,
teamAutopilots: (key: string) => `${ws}/team/${encode(key)}/autopilots`,
teamSettings: (key: string) => `${ws}/team/${encode(key)}/settings`,
teamDetail: (key: string) => `${ws}/team/${encode(key)}`,
projects: () => `${ws}/projects`,
projectDetail: (id: string) => `${ws}/projects/${encode(id)}`,
autopilots: () => `${ws}/autopilots`,

View File

@@ -215,13 +215,13 @@ function SortableTeamGroup({
team,
pathname,
buildHref,
settingsHref,
detailHref,
forceCollapsed,
}: {
team: Team;
pathname: string;
buildHref: (pathKey: (typeof teamChildNav)[number]["pathKey"], teamKey: string) => string;
settingsHref: string;
detailHref: string;
/** True while any team drag is in flight: every group renders collapsed so
all sortable items are the same height — variable-height blocks make
dnd-kit's rect math (and thus the whole gesture) feel broken. */
@@ -237,11 +237,10 @@ function SortableTeamGroup({
useEffect(() => {
if (isDragging) wasDragged.current = true;
}, [isDragging]);
// The row itself is the team's home (overview/settings). It stays
// highlighted anywhere inside the team (issues/projects/autopilots too) so
// the sidebar always shows which team you're in.
const teamBase = settingsHref.replace(/\/settings$/, "");
const isTeamActive = isNavActive(pathname, teamBase);
// The row itself is the team's home (detail page). It stays highlighted
// anywhere inside the team (issues/projects/autopilots too) so the sidebar
// always shows which team you're in.
const isTeamActive = isNavActive(pathname, detailHref);
return (
<div
ref={setNodeRef}
@@ -257,7 +256,7 @@ function SortableTeamGroup({
the row; the 5px activation distance keeps clicks working. */}
<div {...attributes} {...listeners}>
<SidebarGroupLabel
render={<AppLink href={settingsHref} draggable={false} />}
render={<AppLink href={detailHref} draggable={false} />}
onClick={(event) => {
if (wasDragged.current) {
wasDragged.current = false;
@@ -282,7 +281,7 @@ function SortableTeamGroup({
e.stopPropagation();
collapse.onOpenChange(!collapse.open);
}}
className="ml-1 flex size-4 shrink-0 cursor-pointer items-center justify-center rounded-sm text-muted-foreground hover:bg-sidebar-accent hover:text-foreground"
className="-ml-1 flex size-4 shrink-0 cursor-pointer items-center justify-center rounded-sm text-muted-foreground hover:bg-sidebar-accent hover:text-foreground"
>
<ChevronRight
className={cn(
@@ -1071,7 +1070,7 @@ export function AppSidebar({ topSlot, searchSlot, headerClassName, headerStyle }
team={team}
pathname={pathname}
buildHref={(pathKey, teamKey) => p[pathKey](teamKey)}
settingsHref={p.teamSettings(team.key)}
detailHref={p.teamDetail(team.key)}
/>
))}
</SortableContext>

View File

@@ -2,4 +2,4 @@ export { TeamsPage } from "./teams-page";
export { TeamPicker, TeamMultiPicker } from "./team-picker";
export { TeamIcon } from "./team-icon";
export { TeamIssuesPage, TeamProjectsPage, TeamAutopilotsPage } from "./team-surface-pages";
export { TeamSettingsPage } from "./team-settings-page";
export { TeamDetailPage } from "./team-detail-page";

View File

@@ -46,13 +46,13 @@ const underline =
"rounded-none border-0 border-b border-input bg-transparent dark:bg-transparent px-0 shadow-none focus-visible:ring-0 focus-visible:border-foreground";
/**
* Team settings — /team/:key/settings, Linear team-page shape. Left column is
* the identity itself rendered as page text (icon picker applies on pick,
* name and description commit on blur — no save buttons); the right column
* holds members (avatar stack → checkbox config; saving an empty set
* archives behind a confirm), go-to links, and archive.
* Team detail — /team/:key, the sidebar team row's landing page. A single
* narrow column: the identity rendered as page text (icon picker applies on
* pick, name and description commit on blur — no save buttons), then members
* (avatar stack → checkbox config; saving an empty set archives behind a
* confirm), go-to links, and archive.
*/
export function TeamSettingsPage({ teamKey }: { teamKey: string }) {
export function TeamDetailPage({ teamKey }: { teamKey: string }) {
const { t } = useT("teams");
const wsId = useWorkspaceId();
// Full list (not active-only): an archived team's settings stay viewable.
@@ -77,15 +77,11 @@ export function TeamSettingsPage({ teamKey }: { teamKey: string }) {
</PageHeader>
<div className="min-h-0 flex-1 overflow-y-auto">
<div className="mx-auto flex w-full max-w-3xl gap-10 px-6 py-8">
<div className="min-w-0 flex-1">
<Identity team={team} />
</div>
<aside className="flex w-52 shrink-0 flex-col gap-6">
<MembersSection team={team} />
<GotoSection team={team} />
<ArchiveSection team={team} />
</aside>
<div className="mx-auto flex w-full max-w-xl flex-col gap-8 px-6 py-8">
<Identity team={team} />
<MembersSection team={team} />
<GotoSection team={team} />
<ArchiveSection team={team} />
</div>
</div>
</div>
@@ -299,7 +295,7 @@ function MembersSection({ team }: { team: Team }) {
</span>
) : (
<span className="text-sm text-muted-foreground">
{t(($) => $.picker.empty)}
{t(($) => $.settings.members_empty)}
</span>
)}
</PopoverTrigger>
@@ -337,7 +333,7 @@ function MembersSection({ team }: { team: Team }) {
))}
{filteredMembers.length === 0 && (
<div className="px-1 py-4 text-center text-sm text-muted-foreground">
{t(($) => $.picker.empty)}
{t(($) => $.dialog.member_search_empty)}
</div>
)}
</div>

View File

@@ -47,7 +47,7 @@ export function TeamIssuesPage({ teamKey }: { teamKey: string }) {
<PageHeader className="gap-2">
{/* Team name links into the team's settings page. */}
<AppLink
href={p.teamSettings(team.key)}
href={p.teamDetail(team.key)}
className="flex items-center gap-2 rounded px-1 -mx-1 transition-colors hover:bg-accent/40"
>
<TeamIcon team={team} />

View File

@@ -103,7 +103,7 @@ export function TeamsPage() {
<ListGridRow
key={team.id}
className="cursor-pointer"
{...rowLink(p.teamSettings(team.key))}
{...rowLink(p.teamDetail(team.key))}
>
<ListGridCell className="gap-2">
<TeamIcon team={team} />

View File

@@ -6,5 +6,5 @@ export {
TeamIssuesPage,
TeamProjectsPage,
TeamAutopilotsPage,
TeamSettingsPage,
TeamDetailPage,
} from "./components";