feat: improve admin panel design (#14464)

This commit is contained in:
DarkSky
2026-02-17 17:40:29 +08:00
committed by GitHub
parent 850e646ab9
commit 8f833388eb
86 changed files with 2633 additions and 1431 deletions
@@ -5,7 +5,6 @@ import {
} from '@affine/admin/components/ui/avatar';
import { AccountIcon, LinkIcon } from '@blocksuite/icons/rc';
import type { ColumnDef } from '@tanstack/react-table';
import { cssVarV2 } from '@toeverything/theme/v2';
import { useMemo } from 'react';
import type { WorkspaceListItem } from '../schema';
@@ -27,42 +26,27 @@ export const useColumns = () => {
{workspace.name || workspace.id}
</span>
{workspace.public ? (
<span
className="inline-flex items-center gap-1 px-2 py-0.5 text-[11px] rounded border"
style={{
backgroundColor: cssVarV2('chip/label/white'),
borderColor: cssVarV2('layer/insideBorder/border'),
}}
>
<span className="inline-flex items-center gap-1 rounded-md border border-border/60 bg-chip-white px-2 py-0.5 text-xxs">
<LinkIcon fontSize={14} />
Public
</span>
) : null}
</div>
<div
className="text-xs font-mono truncate w-full"
style={{ color: cssVarV2('text/secondary') }}
>
<div className="w-full truncate font-mono text-xs text-muted-foreground">
{workspace.id}
</div>
<div className="flex flex-wrap gap-2 text-[11px]">
<div className="flex flex-wrap gap-2 text-xxs">
{workspace.features.length ? (
workspace.features.map(feature => (
<span
key={feature}
className="px-2 py-0.5 rounded border"
style={{
backgroundColor: cssVarV2('chip/label/white'),
borderColor: cssVarV2('layer/insideBorder/border'),
}}
className="rounded-md border border-border/60 bg-chip-white px-2 py-0.5"
>
{feature}
</span>
))
) : (
<span style={{ color: cssVarV2('text/secondary') }}>
No features
</span>
<span className="text-muted-foreground">No features</span>
)}
</div>
</div>
@@ -75,17 +59,10 @@ export const useColumns = () => {
cell: ({ row }) => {
const owner = row.original.owner;
if (!owner) {
return (
<div
className="text-xs"
style={{ color: cssVarV2('text/secondary') }}
>
Unknown
</div>
);
return <div className="text-xs text-muted-foreground">Unknown</div>;
}
return (
<div className="flex items-center gap-3 min-w-[180px] min-w-0">
<div className="flex items-center gap-3 min-w-0">
<Avatar className="w-9 h-9">
<AvatarImage src={owner.avatarUrl ?? undefined} />
<AvatarFallback>
@@ -94,10 +71,7 @@ export const useColumns = () => {
</Avatar>
<div className="flex flex-col overflow-hidden min-w-0">
<div className="text-sm font-medium truncate">{owner.name}</div>
<div
className="text-xs truncate"
style={{ color: cssVarV2('text/secondary') }}
>
<div className="truncate text-xs text-muted-foreground">
{owner.email}
</div>
</div>
@@ -114,15 +88,13 @@ export const useColumns = () => {
<div className="flex flex-col gap-1 text-xs">
<div className="flex gap-3">
<span>Snapshot {formatBytes(ws.snapshotSize)}</span>
<span style={{ color: cssVarV2('text/secondary') }}>
<span className="text-muted-foreground">
({ws.snapshotCount})
</span>
</div>
<div className="flex gap-3">
<span>Blobs {formatBytes(ws.blobSize)}</span>
<span style={{ color: cssVarV2('text/secondary') }}>
({ws.blobCount})
</span>
<span className="text-muted-foreground">({ws.blobCount})</span>
</div>
</div>
);
@@ -137,15 +109,11 @@ export const useColumns = () => {
<div className="flex flex-col text-xs gap-1">
<div className="flex gap-2">
<span className="font-medium">{ws.memberCount}</span>
<span style={{ color: cssVarV2('text/secondary') }}>
members
</span>
<span className="text-muted-foreground">members</span>
</div>
<div className="flex gap-2">
<span className="font-medium">{ws.publicPageCount}</span>
<span style={{ color: cssVarV2('text/secondary') }}>
shared pages
</span>
<span className="text-muted-foreground">shared pages</span>
</div>
</div>
);
@@ -14,7 +14,6 @@ import {
adminWorkspacesQuery,
} from '@affine/graphql';
import { AccountIcon } from '@blocksuite/icons/rc';
import { cssVarV2 } from '@toeverything/theme/v2';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { toast } from 'sonner';
@@ -53,10 +52,7 @@ export function WorkspacePanel({
handleConfirm={onClose}
canSave={false}
/>
<div
className="p-6 text-sm"
style={{ color: cssVarV2('text/secondary') }}
>
<div className="p-6 text-sm text-muted-foreground">
Workspace not found.
</div>
</div>
@@ -173,29 +169,19 @@ function WorkspacePanelContent({
const memberList = workspace.members ?? [];
return (
<div className="flex flex-col h-full">
<div className="flex h-full flex-col bg-background">
<RightPanelHeader
title="Update Workspace"
handleClose={onClose}
handleConfirm={handleSave}
canSave={hasChanges && !isMutating}
/>
<div className="p-4 flex flex-col gap-4 overflow-y-auto">
<div className="border rounded-md p-3 space-y-2">
<div
className="text-xs"
style={{ color: cssVarV2('text/secondary') }}
>
Workspace ID
</div>
<div className="flex flex-col gap-4 overflow-y-auto p-4">
<div className="space-y-2 rounded-xl border border-border/60 bg-card p-3 shadow-sm">
<div className="text-xs text-muted-foreground">Workspace ID</div>
<div className="text-sm font-mono break-all">{workspace.id}</div>
<div className="flex flex-col gap-1">
<Label
className="text-xs"
style={{ color: cssVarV2('text/secondary') }}
>
Name
</Label>
<Label className="text-xs text-muted-foreground">Name</Label>
<Input
value={flags.name}
onChange={e =>
@@ -206,7 +192,7 @@ function WorkspacePanelContent({
</div>
</div>
<div className="border rounded-md">
<div className="rounded-xl border border-border/60 bg-card shadow-sm">
<FlagItem
label="Public"
description="Allow public access to workspace pages"
@@ -253,7 +239,7 @@ function WorkspacePanelContent({
/>
</div>
<div className="border rounded-md p-3 space-y-3">
<div className="space-y-3 rounded-xl border border-border/60 bg-card p-3 shadow-sm">
<div className="text-sm font-medium">Features</div>
<FeatureToggleList
features={serverConfig.availableWorkspaceFeatures ?? []}
@@ -286,15 +272,12 @@ function WorkspacePanelContent({
/>
</div>
<div className="border rounded-md">
<div className="rounded-xl border border-border/60 bg-card shadow-sm">
<div className="px-3 py-2 text-sm font-medium">Members</div>
<Separator />
<div className="flex flex-col divide-y">
{memberList.length === 0 ? (
<div
className="px-3 py-3 text-xs"
style={{ color: cssVarV2('text/secondary') }}
>
<div className="px-3 py-3 text-xs text-muted-foreground">
No members.
</div>
) : (
@@ -313,10 +296,7 @@ function WorkspacePanelContent({
<div className="text-sm font-medium truncate">
{member.name || member.email}
</div>
<div
className="text-xs truncate"
style={{ color: cssVarV2('text/secondary') }}
>
<div className="truncate text-xs text-muted-foreground">
{member.email}
</div>
</div>
@@ -348,9 +328,7 @@ function FlagItem({
<div className="flex items-start justify-between gap-2 p-3">
<div className="flex flex-col">
<div className="text-sm font-medium">{label}</div>
<div className="text-xs" style={{ color: cssVarV2('text/secondary') }}>
{description}
</div>
<div className="text-xs text-muted-foreground">{description}</div>
</div>
<Switch checked={checked} onCheckedChange={onCheckedChange} />
</div>
@@ -359,10 +337,8 @@ function FlagItem({
function MetricCard({ label, value }: { label: string; value: string }) {
return (
<div className="border rounded-md p-3 flex flex-col gap-1">
<div className="text-xs" style={{ color: cssVarV2('text/secondary') }}>
{label}
</div>
<div className="flex flex-col gap-1 rounded-xl border border-border/60 bg-card p-3 shadow-sm">
<div className="text-xs text-muted-foreground">{label}</div>
<div className="text-sm font-semibold">{value}</div>
</div>
);
@@ -1,6 +1,5 @@
import { Separator } from '@affine/admin/components/ui/separator';
import { adminWorkspaceQuery } from '@affine/graphql';
import { cssVarV2 } from '@toeverything/theme/v2';
import { useMemo } from 'react';
import { useQuery } from '../../../use-query';
@@ -44,10 +43,7 @@ export function WorkspaceSharedLinksPanel({
handleConfirm={onClose}
canSave={false}
/>
<div
className="p-6 text-sm"
style={{ color: cssVarV2('text/secondary') }}
>
<div className="p-6 text-sm text-muted-foreground">
Workspace not found.
</div>
</div>
@@ -55,23 +51,18 @@ export function WorkspaceSharedLinksPanel({
}
return (
<div className="flex flex-col h-full">
<div className="flex h-full flex-col bg-background">
<RightPanelHeader
title="Shared Links"
handleClose={onClose}
handleConfirm={onClose}
canSave={false}
/>
<div className="p-4 flex flex-col gap-3 overflow-y-auto">
<div className="flex flex-col gap-3 overflow-y-auto p-4">
{sharedLinks.length === 0 ? (
<div
className="text-sm"
style={{ color: cssVarV2('text/secondary') }}
>
No shared links.
</div>
<div className="text-sm text-muted-foreground">No shared links.</div>
) : (
<div className="flex flex-col divide-y rounded-md border">
<div className="flex flex-col divide-y rounded-xl border border-border/60 bg-card shadow-sm">
{sharedLinks.map(link => (
<SharedLinkItem key={link.docId} link={link} />
))}
@@ -91,9 +82,7 @@ function SharedLinkItem({ link }: { link: WorkspaceSharedLink }) {
<div className="text-sm font-medium truncate">{title}</div>
<div className="flex items-center gap-2 text-xs">
<Separator className="h-3" orientation="vertical" />
<span style={{ color: cssVarV2('text/secondary') }}>
Shared on {sharedDate}
</span>
<span className="text-muted-foreground">Shared on {sharedDate}</span>
</div>
</div>
);
@@ -27,7 +27,7 @@ export function WorkspacePage() {
const columns = useColumns();
return (
<div className="h-screen flex-1 flex-col flex">
<div className="h-dvh flex-1 flex-col flex">
<Header title="Workspaces" />
<DataTable