Skip to content

Commit 96fea80

Browse files
Fix dropdown, breadcrumb, and theme UX issues across the portal (#559)
1 parent 17f5c81 commit 96fea80

26 files changed

Lines changed: 289 additions & 309 deletions

File tree

web/design-tokens/colors.css

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -131,8 +131,8 @@
131131
--muted: var(--custos-gray-100);
132132
/* Pinned to gray-600 — gray-500 on bg-muted is 4.28:1 and fails WCAG AA. */
133133
--muted-foreground: var(--custos-gray-600);
134-
--accent: var(--custos-blue-50);
135-
--accent-foreground: var(--custos-blue-700);
134+
--accent: var(--custos-gray-100);
135+
--accent-foreground: var(--custos-gray-900);
136136
--destructive: var(--custos-red-600);
137137
--destructive-foreground: var(--custos-white);
138138
--border: var(--custos-gray-200);
@@ -172,14 +172,14 @@
172172
--card-foreground: var(--custos-gray-50);
173173
--popover: var(--custos-gray-800);
174174
--popover-foreground: var(--custos-gray-50);
175-
--primary: var(--custos-blue-400);
176-
--primary-foreground: var(--custos-gray-900);
175+
--primary: var(--custos-gray-700);
176+
--primary-foreground: var(--custos-gray-50);
177177
--secondary: var(--custos-gray-800);
178178
--secondary-foreground: var(--custos-gray-50);
179179
--muted: var(--custos-gray-800);
180180
--muted-foreground: var(--custos-gray-300);
181-
--accent: var(--custos-blue-800);
182-
--accent-foreground: var(--custos-blue-100);
181+
--accent: var(--custos-gray-700);
182+
--accent-foreground: var(--custos-gray-50);
183183
--destructive: var(--custos-red-500);
184184
--destructive-foreground: var(--custos-white);
185185
--border: var(--custos-gray-700);

web/src/app/(portal)/admin/users/management/UsersTable.tsx

Lines changed: 39 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,13 @@ import {
2525
} from "@/shared/hooks/useShallowSearchParams";
2626
import { DataTable, type DataTableColumn } from "@/shared/ui/DataTable";
2727
import { Input } from "@/shared/ui/input";
28+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/shared/ui/select";
2829
import { ChevronRight } from "lucide-react";
2930
import * as React from "react";
3031
import { IdentitiesCell } from "./IdentitiesCell";
3132
import { PermissionsDrawer } from "./PermissionsDrawer";
3233
import { RolesCell } from "./RolesCell";
33-
import { IDENTITY_SOURCE_LABELS } from "./identities";
34+
import { IDENTITY_SOURCE_LABELS, identitySourceLabel } from "./identities";
3435

3536
function fullNameFor(user: UserManagementRow): string {
3637
const name = [user.first_name, user.last_name].filter(Boolean).join(" ");
@@ -79,6 +80,15 @@ export function UsersTable({
7980
Boolean(search.trim()) || (canManageRoles && roleFilter !== "all") || identityFilter !== "all";
8081
const selectedUser = users.find((user) => user.id === selectedId) ?? null;
8182

83+
function roleLabelFor(value: string): string {
84+
if (value === "all") return "All roles";
85+
return rolesCatalog.find((role) => role.id === value)?.name ?? value;
86+
}
87+
88+
function identityLabelFor(value: string): string {
89+
return value === "all" ? "All external identities" : identitySourceLabel(value);
90+
}
91+
8292
function resetSelection() {
8393
setSelectedId(null);
8494
expandedRow.clear();
@@ -202,33 +212,36 @@ export function UsersTable({
202212
className="sm:w-72"
203213
/>
204214
{canManageRoles ? (
205-
<select
206-
value={roleFilter}
207-
onChange={(event) => updateFilterParam("role", event.target.value)}
208-
aria-label="Filter this page by role"
209-
className="h-9 rounded-md border bg-background px-3 text-sm"
210-
>
211-
<option value="all">All roles</option>
212-
{rolesCatalog.map((role) => (
213-
<option key={role.id} value={role.id}>
214-
{role.name}
215-
</option>
216-
))}
217-
</select>
215+
<Select value={roleFilter} onValueChange={(value) => updateFilterParam("role", value)}>
216+
<SelectTrigger aria-label="Filter this page by role" className="h-9 w-36 px-3">
217+
<SelectValue>{(value: string) => roleLabelFor(value)}</SelectValue>
218+
</SelectTrigger>
219+
<SelectContent>
220+
<SelectItem value="all">All roles</SelectItem>
221+
{rolesCatalog.map((role) => (
222+
<SelectItem key={role.id} value={role.id}>
223+
{role.name}
224+
</SelectItem>
225+
))}
226+
</SelectContent>
227+
</Select>
218228
) : null}
219-
<select
229+
<Select
220230
value={identityFilter}
221-
onChange={(event) => updateFilterParam("identity", event.target.value)}
222-
aria-label="Filter this page by external identity"
223-
className="h-9 rounded-md border bg-background px-3 text-sm"
231+
onValueChange={(value) => updateFilterParam("identity", value)}
224232
>
225-
<option value="all">All external identities</option>
226-
{Object.entries(IDENTITY_SOURCE_LABELS).map(([source, label]) => (
227-
<option key={source} value={source}>
228-
{label}
229-
</option>
230-
))}
231-
</select>
233+
<SelectTrigger aria-label="Filter this page by external identity" className="h-9 w-56 px-3">
234+
<SelectValue>{(value: string) => identityLabelFor(value)}</SelectValue>
235+
</SelectTrigger>
236+
<SelectContent>
237+
<SelectItem value="all">All external identities</SelectItem>
238+
{Object.entries(IDENTITY_SOURCE_LABELS).map(([source, label]) => (
239+
<SelectItem key={source} value={source}>
240+
{label}
241+
</SelectItem>
242+
))}
243+
</SelectContent>
244+
</Select>
232245
</div>
233246

234247
{filtersActive ? (
@@ -248,7 +261,7 @@ export function UsersTable({
248261
}}
249262
rowClassName={(row) =>
250263
isCurrentUser(row)
251-
? "bg-[color:var(--custos-blue-50)]/40 hover:bg-[color:var(--custos-blue-50)]/60"
264+
? "bg-[color:var(--brand-tint)]/40 hover:bg-[color:var(--brand-tint)]/60"
252265
: undefined
253266
}
254267
empty={

web/src/app/(portal)/admin/users/management/__tests__/UsersTable.test.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,8 @@ describe("UsersTable", () => {
7575
renderTable(true);
7676
expect(screen.getByText("Example User")).toBeInTheDocument();
7777
expect(screen.getByText("(You)")).toBeInTheDocument();
78-
expect(screen.getAllByText("Administrator")).toHaveLength(2);
79-
expect(screen.getAllByText("CILogon")).toHaveLength(2);
78+
expect(screen.getAllByText("Administrator")).toHaveLength(1);
79+
expect(screen.getAllByText("CILogon")).toHaveLength(1);
8080
});
8181

8282
it("hides all role UI without roles:manage", () => {

web/src/app/(portal)/allocations/[allocationId]/layout.tsx

Lines changed: 0 additions & 34 deletions
This file was deleted.

web/src/app/(portal)/change-requests/[changeRequestId]/layout.tsx

Lines changed: 0 additions & 37 deletions
This file was deleted.

web/src/app/(portal)/page.tsx

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,8 @@
1515
// specific language governing permissions and limitations
1616
// under the License.
1717

18+
import { redirect } from "next/navigation";
19+
1820
export default function PortalLandingPage() {
19-
return (
20-
<div className="flex flex-col gap-2">
21-
<h1 className="text-2xl font-bold tracking-tight">Welcome to Custos Portal</h1>
22-
<p className="text-sm text-muted-foreground">
23-
Features will land in subsequent phases.
24-
</p>
25-
</div>
26-
);
21+
redirect("/allocations");
2722
}

web/src/app/(portal)/projects/[projectId]/layout.tsx

Lines changed: 0 additions & 32 deletions
This file was deleted.

web/src/features/core/allocations/components/AllocationDetail.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
import { useSession } from "next-auth/react";
2121
import { useAbility } from "@/shared/casl/AbilityProvider";
22+
import { useBreadcrumbLabel } from "@/shared/layout/BreadcrumbLabelsProvider";
2223
import { ErrorState } from "@/shared/ui/ErrorState";
2324
import { CardSkeleton } from "@/shared/ui/Loading";
2425
import { TabsRouter } from "@/shared/ui/TabsRouter";
@@ -39,6 +40,7 @@ export function AllocationDetail({ allocationId }: AllocationDetailProps) {
3940
const { data: session } = useSession();
4041
const allocationQuery = useAllocation(allocationId);
4142
const membersQuery = useAllocationMembers(allocationId);
43+
useBreadcrumbLabel(allocationId, allocationQuery.data?.name);
4244

4345
if (allocationQuery.isLoading) return <CardSkeleton />;
4446
if (allocationQuery.error) {

web/src/features/core/allocations/components/AllocationDetailHeader.tsx

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
"use client";
1919

20-
import { Calendar, Server, UserSquare } from "lucide-react";
20+
import { Calendar, Gauge, Server, UserSquare } from "lucide-react";
2121
import { MetaItem, MetaRow } from "@/shared/ui/MetaRow";
2222
import type { ComputeAllocation } from "../schemas";
2323

@@ -38,6 +38,10 @@ function formatDate(iso: string): string {
3838
}
3939
}
4040

41+
function formatNumber(n: number): string {
42+
return new Intl.NumberFormat().format(n);
43+
}
44+
4145
function isExpired(endTime: string, now = new Date()): boolean {
4246
const t = new Date(endTime).getTime();
4347
return Number.isFinite(t) && t < now.getTime();
@@ -58,12 +62,24 @@ export function AllocationDetailHeader({ allocation, memberCount }: AllocationDe
5862
<h1 className="font-display text-[28px] font-bold leading-tight text-foreground">
5963
{allocation.name}
6064
</h1>
61-
<MetaRow>
62-
<MetaItem variant="status" tone={tone} value={label} />
63-
<MetaItem icon={Server} label="Cluster" value={allocation.compute_cluster_id} />
64-
<MetaItem icon={Calendar} label="End date" value={formatDate(allocation.end_time)} />
65-
<MetaItem icon={UserSquare} label="Members" value={memberCount} />
66-
</MetaRow>
65+
<MetaItem
66+
className="hidden"
67+
icon={Server}
68+
label="Cluster"
69+
value={allocation.compute_cluster_id}
70+
/>
71+
<div className="flex flex-wrap items-center gap-3">
72+
<MetaItem variant="status" tone={tone} value={label} className="py-1.5" />
73+
<MetaRow>
74+
<MetaItem
75+
icon={Gauge}
76+
label="Initial SUs"
77+
value={formatNumber(allocation.initial_su_amount)}
78+
/>
79+
<MetaItem icon={Calendar} label="End date" value={formatDate(allocation.end_time)} />
80+
<MetaItem icon={UserSquare} label="Members" value={memberCount} />
81+
</MetaRow>
82+
</div>
6783
</header>
6884
);
6985
}

web/src/features/core/allocations/components/AllocationOverviewTab.tsx

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -56,30 +56,30 @@ export function AllocationOverviewTab({ allocation }: AllocationOverviewTabProps
5656

5757
return (
5858
<div className="space-y-6">
59-
<dl className="grid gap-x-8 gap-y-3 sm:grid-cols-[max-content_1fr] text-sm">
59+
<dl className="grid gap-x-8 gap-y-3 rounded-lg border border-border bg-muted/40 p-4 text-sm sm:grid-cols-[max-content_1fr]">
6060
<dt className="text-muted-foreground">Allocation ID</dt>
61-
<dd className="font-mono text-foreground">{allocation.id}</dd>
61+
<dd className="font-mono text-foreground before:font-sans before:content-[':_']">{allocation.id}</dd>
6262

6363
<dt className="text-muted-foreground">Project</dt>
64-
<dd className="font-mono text-foreground">{allocation.project_id}</dd>
64+
<dd className="font-mono text-foreground before:font-sans before:content-[':_']">{allocation.project_id}</dd>
6565

6666
<dt className="text-muted-foreground">Name</dt>
67-
<dd className="text-foreground">{allocation.name}</dd>
67+
<dd className="text-foreground before:content-[':_']">{allocation.name}</dd>
6868

6969
<dt className="text-muted-foreground">Status</dt>
70-
<dd className="text-foreground">{allocation.status}</dd>
70+
<dd className="text-foreground before:content-[':_']">{allocation.status}</dd>
7171

7272
<dt className="text-muted-foreground">Cluster</dt>
73-
<dd className="font-mono text-foreground">{allocation.compute_cluster_id}</dd>
73+
<dd className="font-mono text-foreground before:font-sans before:content-[':_']">{allocation.compute_cluster_id}</dd>
7474

7575
<dt className="text-muted-foreground">Initial SUs</dt>
76-
<dd className="tabular-nums text-foreground">{formatNumber(allocation.initial_su_amount)}</dd>
76+
<dd className="tabular-nums text-foreground before:content-[':_']">{formatNumber(allocation.initial_su_amount)}</dd>
7777

7878
<dt className="text-muted-foreground">Start</dt>
79-
<dd className="text-foreground">{allocation.start_time}</dd>
79+
<dd className="text-foreground before:content-[':_']">{allocation.start_time}</dd>
8080

8181
<dt className="text-muted-foreground">End</dt>
82-
<dd className="text-foreground">{allocation.end_time}</dd>
82+
<dd className="text-foreground before:content-[':_']">{allocation.end_time}</dd>
8383
</dl>
8484

8585
<section className="space-y-2">

0 commit comments

Comments
 (0)