mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-20 19:46:32 +08:00
fix(admin): adjust admin panel style (#11065)
close AF-2353 AF-2354 AF-2355 AF-2356 AF-2358 AF-2360 AF-2362 AF-2363 AF-2364 AF-2389 Fixed some UI issues.
This commit is contained in:
@@ -5,16 +5,10 @@ import {
|
||||
} from '@affine/admin/components/ui/avatar';
|
||||
import type { UserType } from '@affine/graphql';
|
||||
import { FeatureType } from '@affine/graphql';
|
||||
import { AccountIcon, LockIcon, UnlockIcon } from '@blocksuite/icons/rc';
|
||||
import type { ColumnDef } from '@tanstack/react-table';
|
||||
import { cssVarV2 } from '@toeverything/theme/v2';
|
||||
import clsx from 'clsx';
|
||||
import {
|
||||
LockIcon,
|
||||
MailIcon,
|
||||
MailWarningIcon,
|
||||
UnlockIcon,
|
||||
UserIcon,
|
||||
} from 'lucide-react';
|
||||
import { MailIcon } from 'lucide-react';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
import { Checkbox } from '../../../components/ui/checkbox';
|
||||
@@ -35,10 +29,10 @@ const StatusItem = ({
|
||||
textFalse: string;
|
||||
}) => (
|
||||
<div
|
||||
className={clsx(
|
||||
'flex gap-2 items-center',
|
||||
!condition ? 'text-red-500 opacity-100' : 'opacity-25'
|
||||
)}
|
||||
className="flex gap-1 items-center"
|
||||
style={{
|
||||
color: condition ? cssVarV2('text/secondary') : cssVarV2('status/error'),
|
||||
}}
|
||||
>
|
||||
{condition ? (
|
||||
<>
|
||||
@@ -89,7 +83,7 @@ export const columns: ColumnDef<UserType>[] = [
|
||||
<Avatar className="w-10 h-10">
|
||||
<AvatarImage src={row.original.avatarUrl ?? undefined} />
|
||||
<AvatarFallback>
|
||||
<UserIcon size={20} />
|
||||
<AccountIcon fontSize={20} />
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="flex flex-col gap-1 max-w-full overflow-hidden">
|
||||
@@ -97,7 +91,7 @@ export const columns: ColumnDef<UserType>[] = [
|
||||
<span>{row.original.name}</span>
|
||||
{row.original.features.includes(FeatureType.Admin) && (
|
||||
<span
|
||||
className="ml-2 rounded px-2 py-0.5 text-xs h-5 border"
|
||||
className="ml-2 rounded px-2 py-0.5 text-xs h-5 border text-center inline-flex items-center font-normal"
|
||||
style={{
|
||||
borderRadius: '4px',
|
||||
backgroundColor: cssVarV2('chip/label/blue'),
|
||||
@@ -120,7 +114,12 @@ export const columns: ColumnDef<UserType>[] = [
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="text-xs font-medium opacity-50 max-w-full overflow-hidden">
|
||||
<div
|
||||
className="text-xs font-medium max-w-full overflow-hidden"
|
||||
style={{
|
||||
color: cssVarV2('text/secondary'),
|
||||
}}
|
||||
>
|
||||
{row.original.email}
|
||||
</div>
|
||||
</div>
|
||||
@@ -141,20 +140,29 @@ export const columns: ColumnDef<UserType>[] = [
|
||||
cell: ({ row: { original: user } }) => (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex flex-col gap-2 text-xs max-md:hidden">
|
||||
<div className="flex justify-end opacity-25">{user.id}</div>
|
||||
<div className="flex gap-3 items-center justify-end">
|
||||
<div className="flex justify-start">{user.id}</div>
|
||||
<div className="flex gap-3 items-center justify-start">
|
||||
<StatusItem
|
||||
condition={user.hasPassword}
|
||||
IconTrue={<LockIcon size={10} />}
|
||||
IconFalse={<UnlockIcon size={10} />}
|
||||
IconTrue={
|
||||
<LockIcon
|
||||
fontSize={16}
|
||||
color={cssVarV2('selfhost/icon/tertiary')}
|
||||
/>
|
||||
}
|
||||
IconFalse={<UnlockIcon fontSize={16} />}
|
||||
textTrue="Password Set"
|
||||
textFalse="No Password"
|
||||
/>
|
||||
|
||||
<StatusItem
|
||||
condition={user.emailVerified}
|
||||
IconTrue={<MailIcon size={10} />}
|
||||
IconFalse={<MailWarningIcon size={10} />}
|
||||
IconTrue={
|
||||
<MailIcon
|
||||
size={16}
|
||||
color={cssVarV2('selfhost/icon/tertiary')}
|
||||
/>
|
||||
}
|
||||
IconFalse={<MailIcon size={16} />}
|
||||
textTrue="Email Verified"
|
||||
textFalse="Email Not Verified"
|
||||
/>
|
||||
|
||||
@@ -170,25 +170,21 @@ export function DataTableRowActions({ user }: DataTableRowActionsProps) {
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-[214px] p-[5px] gap-2">
|
||||
<div className="px-2 py-[6px] text-sm font-semibold overflow-hidden text-ellipsis text-nowrap">
|
||||
{user.name}
|
||||
</div>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem
|
||||
className="px-2 py-[6px] text-sm font-medium gap-2 cursor-pointer"
|
||||
className="px-2 py-[6px] text-sm font-normal gap-2 cursor-pointer"
|
||||
onSelect={openResetPasswordDialog}
|
||||
>
|
||||
<LockIcon fontSize={20} /> Reset Password
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onSelect={handleEdit}
|
||||
className="px-2 py-[6px] text-sm font-medium gap-2 cursor-pointer"
|
||||
className="px-2 py-[6px] text-sm font-normal gap-2 cursor-pointer"
|
||||
>
|
||||
<EditIcon fontSize={20} /> Edit
|
||||
</DropdownMenuItem>
|
||||
{user.disabled && (
|
||||
<DropdownMenuItem
|
||||
className="px-2 py-[6px] text-sm font-medium gap-2 cursor-pointer"
|
||||
className="px-2 py-[6px] text-sm font-normal gap-2 cursor-pointer"
|
||||
onSelect={openEnableDialog}
|
||||
>
|
||||
<AccountBanIcon fontSize={20} /> Enable Email
|
||||
@@ -197,14 +193,14 @@ export function DataTableRowActions({ user }: DataTableRowActionsProps) {
|
||||
<DropdownMenuSeparator />
|
||||
{!user.disabled && (
|
||||
<DropdownMenuItem
|
||||
className="px-2 py-[6px] text-sm font-medium gap-2 text-red-500 cursor-pointer focus:text-red-500"
|
||||
className="px-2 py-[6px] text-sm font-normal gap-2 text-red-500 cursor-pointer focus:text-red-500"
|
||||
onSelect={openDisableDialog}
|
||||
>
|
||||
<AccountBanIcon fontSize={20} /> Disable & Delete data
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
<DropdownMenuItem
|
||||
className="px-2 py-[6px] text-sm font-medium gap-2 text-red-500 cursor-pointer focus:text-red-500"
|
||||
className="px-2 py-[6px] text-sm font-normal gap-2 text-red-500 cursor-pointer focus:text-red-500"
|
||||
onSelect={openDeleteDialog}
|
||||
>
|
||||
<DeleteIcon fontSize={20} /> Delete
|
||||
|
||||
@@ -69,9 +69,9 @@ export function DataTable<TData, TValue>({
|
||||
}, [data]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4 py-5 px-6 h-full">
|
||||
<div className="flex flex-col gap-4 py-5 px-6 h-full overflow-auto">
|
||||
<DataTableToolbar setDataTable={setTableData} data={data} table={table} />
|
||||
<div className="rounded-md border max-h-[75vh] h-full overflow-auto">
|
||||
<div className="rounded-md border h-full flex flex-col overflow-auto">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
{table.getHeaderGroups().map(headerGroup => (
|
||||
@@ -107,49 +107,54 @@ export function DataTable<TData, TValue>({
|
||||
</TableRow>
|
||||
))}
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{table.getRowModel().rows?.length ? (
|
||||
table.getRowModel().rows.map(row => (
|
||||
<TableRow key={row.id} className="flex items-center">
|
||||
{row.getVisibleCells().map(cell => {
|
||||
let columnClassName = '';
|
||||
if (cell.column.id === 'select') {
|
||||
columnClassName = 'w-[40px] flex-shrink-0';
|
||||
} else if (cell.column.id === 'info') {
|
||||
columnClassName = 'flex-1';
|
||||
} else if (cell.column.id === 'property') {
|
||||
columnClassName = 'flex-1';
|
||||
} else if (cell.column.id === 'actions') {
|
||||
columnClassName =
|
||||
'w-[40px] flex-shrink-0 justify-center mr-6';
|
||||
}
|
||||
|
||||
return (
|
||||
<TableCell
|
||||
key={cell.id}
|
||||
className={`${columnClassName} flex items-center`}
|
||||
>
|
||||
{flexRender(
|
||||
cell.column.columnDef.cell,
|
||||
cell.getContext()
|
||||
)}
|
||||
</TableCell>
|
||||
);
|
||||
})}
|
||||
</TableRow>
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell
|
||||
colSpan={columns.length}
|
||||
className="h-24 text-center"
|
||||
>
|
||||
No results.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
|
||||
<div className="overflow-auto flex-1">
|
||||
<Table>
|
||||
<TableBody>
|
||||
{table.getRowModel().rows?.length ? (
|
||||
table.getRowModel().rows.map(row => (
|
||||
<TableRow key={row.id} className="flex items-center">
|
||||
{row.getVisibleCells().map(cell => {
|
||||
let columnClassName = '';
|
||||
if (cell.column.id === 'select') {
|
||||
columnClassName = 'w-[40px] flex-shrink-0';
|
||||
} else if (cell.column.id === 'info') {
|
||||
columnClassName = 'flex-1';
|
||||
} else if (cell.column.id === 'property') {
|
||||
columnClassName = 'flex-1';
|
||||
} else if (cell.column.id === 'actions') {
|
||||
columnClassName =
|
||||
'w-[40px] flex-shrink-0 justify-center mr-6';
|
||||
}
|
||||
|
||||
return (
|
||||
<TableCell
|
||||
key={cell.id}
|
||||
className={`${columnClassName} flex items-center`}
|
||||
>
|
||||
{flexRender(
|
||||
cell.column.columnDef.cell,
|
||||
cell.getContext()
|
||||
)}
|
||||
</TableCell>
|
||||
);
|
||||
})}
|
||||
</TableRow>
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell
|
||||
colSpan={columns.length}
|
||||
className="h-24 text-center"
|
||||
>
|
||||
No results.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DataTablePagination table={table} />
|
||||
|
||||
@@ -56,7 +56,7 @@ export const DeleteAccountDialog = ({
|
||||
className="placeholder:opacity-50"
|
||||
/>
|
||||
<DialogFooter>
|
||||
<div className="flex justify-between items-center w-full">
|
||||
<div className="flex justify-end gap-2 items-center w-full">
|
||||
<Button type="button" variant="outline" size="sm" onClick={onClose}>
|
||||
Cancel
|
||||
</Button>
|
||||
|
||||
@@ -56,7 +56,7 @@ export const DisableAccountDialog = ({
|
||||
className="placeholder:opacity-50"
|
||||
/>
|
||||
<DialogFooter>
|
||||
<div className="flex justify-between items-center w-full">
|
||||
<div className="flex justify-end gap-2 items-center w-full">
|
||||
<Button type="button" variant="outline" size="sm" onClick={onClose}>
|
||||
Cancel
|
||||
</Button>
|
||||
|
||||
@@ -23,13 +23,13 @@ export const DiscardChanges = ({
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="sm:w-[460px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="leading-7">Discard Changes</DialogTitle>
|
||||
<DialogDescription className="leading-6">
|
||||
<DialogTitle className="leading-6.5">Discard Changes</DialogTitle>
|
||||
<DialogDescription className="leading-6 text-[15px]">
|
||||
Changes to this user will not be saved.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter>
|
||||
<div className="flex justify-end items-center w-full space-x-4">
|
||||
<div className="flex justify-end gap-2 items-center w-full">
|
||||
<Button type="button" onClick={onClose} variant="outline">
|
||||
<span>Cancel</span>
|
||||
</Button>
|
||||
|
||||
@@ -33,7 +33,7 @@ export const EnableAccountDialog = ({
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter>
|
||||
<div className="flex justify-end items-center w-full space-x-4">
|
||||
<div className="flex justify-end gap-2 items-center w-full">
|
||||
<Button type="button" onClick={onClose} variant="outline">
|
||||
<span>Cancel</span>
|
||||
</Button>
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Button } from '@affine/admin/components/ui/button';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
@@ -198,7 +199,9 @@ export function ImportUsersDialog({
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent
|
||||
className={isPreviewMode ? 'sm:max-w-[600px]' : 'sm:max-w-[425px]'}
|
||||
className={
|
||||
isPreviewMode ? 'sm:max-w-[600px] flex-col gap-3' : 'sm:max-w-[425px]'
|
||||
}
|
||||
>
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
@@ -211,35 +214,32 @@ export function ImportUsersDialog({
|
||||
: 'Import'}
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
{isFormatError ? (
|
||||
<div className="grid gap-4 py-4">
|
||||
<p className="text-sm text-gray-500">
|
||||
You need to import the accounts by importing a CSV file in the
|
||||
correct format. Please download the CSV template.
|
||||
</p>
|
||||
</div>
|
||||
) : isPreviewMode ? (
|
||||
<div className="grid gap-4 py-4">
|
||||
<p className="text-sm text-gray-500">
|
||||
{parsedUsers.length} users detected from the CSV file. Please
|
||||
confirm the user list below and import.
|
||||
</p>
|
||||
<UserTable users={parsedUsers} />
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid gap-4 py-4">
|
||||
<p className="text-sm text-gray-500">
|
||||
You need to import the accounts by importing a CSV file in the
|
||||
correct format. Please download the CSV template.
|
||||
</p>
|
||||
|
||||
<FileUploadArea
|
||||
ref={fileUploadRef}
|
||||
onFileSelected={handleFileSelected}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<DialogDescription className="text-[15px]">
|
||||
{isFormatError ? (
|
||||
'You need to import the accounts by importing a CSV file in the correct format. Please download the CSV template.'
|
||||
) : isPreviewMode ? (
|
||||
<div className="grid gap-3">
|
||||
{isImported ? null : (
|
||||
<p>
|
||||
{parsedUsers.length} users detected from the CSV file. Please
|
||||
confirm the user list below and import.
|
||||
</p>
|
||||
)}
|
||||
<UserTable users={parsedUsers} />
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid gap-3">
|
||||
<p>
|
||||
You need to import the accounts by importing a CSV file in the
|
||||
correct format. Please download the CSV template.
|
||||
</p>
|
||||
<FileUploadArea
|
||||
ref={fileUploadRef}
|
||||
onFileSelected={handleFileSelected}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</DialogDescription>
|
||||
|
||||
<DialogFooter
|
||||
className={`flex-col sm:flex-row sm:justify-between items-center ${isPreviewMode ? 'sm:justify-end' : 'sm:justify-between'}`}
|
||||
@@ -265,9 +265,8 @@ export function ImportUsersDialog({
|
||||
<>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={cancelImport}
|
||||
className="mb-2 sm:mb-0"
|
||||
className="w-full mb-2 sm:mb-0 sm:w-auto"
|
||||
disabled={isImporting}
|
||||
>
|
||||
Cancel
|
||||
|
||||
@@ -32,7 +32,7 @@ export const ResetPasswordDialog = ({
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter>
|
||||
<div className="flex justify-between items-center w-full space-x-4">
|
||||
<div className="flex justify-end gap-2 items-center w-full">
|
||||
<Input
|
||||
type="text"
|
||||
value={link}
|
||||
|
||||
Reference in New Issue
Block a user