mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-09-01 22:29:44 +08:00
feat: improve admin panel design (#14464)
This commit is contained in:
@@ -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>
|
||||
);
|
||||
|
||||
+6
-17
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user