mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-09-07 01:09:54 +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';
|
} from '@affine/admin/components/ui/avatar';
|
||||||
import type { UserType } from '@affine/graphql';
|
import type { UserType } from '@affine/graphql';
|
||||||
import { FeatureType } from '@affine/graphql';
|
import { FeatureType } from '@affine/graphql';
|
||||||
|
import { AccountIcon, LockIcon, UnlockIcon } from '@blocksuite/icons/rc';
|
||||||
import type { ColumnDef } from '@tanstack/react-table';
|
import type { ColumnDef } from '@tanstack/react-table';
|
||||||
import { cssVarV2 } from '@toeverything/theme/v2';
|
import { cssVarV2 } from '@toeverything/theme/v2';
|
||||||
import clsx from 'clsx';
|
import { MailIcon } from 'lucide-react';
|
||||||
import {
|
|
||||||
LockIcon,
|
|
||||||
MailIcon,
|
|
||||||
MailWarningIcon,
|
|
||||||
UnlockIcon,
|
|
||||||
UserIcon,
|
|
||||||
} from 'lucide-react';
|
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
|
|
||||||
import { Checkbox } from '../../../components/ui/checkbox';
|
import { Checkbox } from '../../../components/ui/checkbox';
|
||||||
@@ -35,10 +29,10 @@ const StatusItem = ({
|
|||||||
textFalse: string;
|
textFalse: string;
|
||||||
}) => (
|
}) => (
|
||||||
<div
|
<div
|
||||||
className={clsx(
|
className="flex gap-1 items-center"
|
||||||
'flex gap-2 items-center',
|
style={{
|
||||||
!condition ? 'text-red-500 opacity-100' : 'opacity-25'
|
color: condition ? cssVarV2('text/secondary') : cssVarV2('status/error'),
|
||||||
)}
|
}}
|
||||||
>
|
>
|
||||||
{condition ? (
|
{condition ? (
|
||||||
<>
|
<>
|
||||||
@@ -89,7 +83,7 @@ export const columns: ColumnDef<UserType>[] = [
|
|||||||
<Avatar className="w-10 h-10">
|
<Avatar className="w-10 h-10">
|
||||||
<AvatarImage src={row.original.avatarUrl ?? undefined} />
|
<AvatarImage src={row.original.avatarUrl ?? undefined} />
|
||||||
<AvatarFallback>
|
<AvatarFallback>
|
||||||
<UserIcon size={20} />
|
<AccountIcon fontSize={20} />
|
||||||
</AvatarFallback>
|
</AvatarFallback>
|
||||||
</Avatar>
|
</Avatar>
|
||||||
<div className="flex flex-col gap-1 max-w-full overflow-hidden">
|
<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>
|
<span>{row.original.name}</span>
|
||||||
{row.original.features.includes(FeatureType.Admin) && (
|
{row.original.features.includes(FeatureType.Admin) && (
|
||||||
<span
|
<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={{
|
style={{
|
||||||
borderRadius: '4px',
|
borderRadius: '4px',
|
||||||
backgroundColor: cssVarV2('chip/label/blue'),
|
backgroundColor: cssVarV2('chip/label/blue'),
|
||||||
@@ -120,7 +114,12 @@ export const columns: ColumnDef<UserType>[] = [
|
|||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</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}
|
{row.original.email}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -141,20 +140,29 @@ export const columns: ColumnDef<UserType>[] = [
|
|||||||
cell: ({ row: { original: user } }) => (
|
cell: ({ row: { original: user } }) => (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="flex flex-col gap-2 text-xs max-md:hidden">
|
<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 justify-start">{user.id}</div>
|
||||||
<div className="flex gap-3 items-center justify-end">
|
<div className="flex gap-3 items-center justify-start">
|
||||||
<StatusItem
|
<StatusItem
|
||||||
condition={user.hasPassword}
|
condition={user.hasPassword}
|
||||||
IconTrue={<LockIcon size={10} />}
|
IconTrue={
|
||||||
IconFalse={<UnlockIcon size={10} />}
|
<LockIcon
|
||||||
|
fontSize={16}
|
||||||
|
color={cssVarV2('selfhost/icon/tertiary')}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
IconFalse={<UnlockIcon fontSize={16} />}
|
||||||
textTrue="Password Set"
|
textTrue="Password Set"
|
||||||
textFalse="No Password"
|
textFalse="No Password"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<StatusItem
|
<StatusItem
|
||||||
condition={user.emailVerified}
|
condition={user.emailVerified}
|
||||||
IconTrue={<MailIcon size={10} />}
|
IconTrue={
|
||||||
IconFalse={<MailWarningIcon size={10} />}
|
<MailIcon
|
||||||
|
size={16}
|
||||||
|
color={cssVarV2('selfhost/icon/tertiary')}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
IconFalse={<MailIcon size={16} />}
|
||||||
textTrue="Email Verified"
|
textTrue="Email Verified"
|
||||||
textFalse="Email Not Verified"
|
textFalse="Email Not Verified"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -170,25 +170,21 @@ export function DataTableRowActions({ user }: DataTableRowActionsProps) {
|
|||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="end" className="w-[214px] p-[5px] gap-2">
|
<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
|
<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}
|
onSelect={openResetPasswordDialog}
|
||||||
>
|
>
|
||||||
<LockIcon fontSize={20} /> Reset Password
|
<LockIcon fontSize={20} /> Reset Password
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onSelect={handleEdit}
|
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
|
<EditIcon fontSize={20} /> Edit
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
{user.disabled && (
|
{user.disabled && (
|
||||||
<DropdownMenuItem
|
<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}
|
onSelect={openEnableDialog}
|
||||||
>
|
>
|
||||||
<AccountBanIcon fontSize={20} /> Enable Email
|
<AccountBanIcon fontSize={20} /> Enable Email
|
||||||
@@ -197,14 +193,14 @@ export function DataTableRowActions({ user }: DataTableRowActionsProps) {
|
|||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
{!user.disabled && (
|
{!user.disabled && (
|
||||||
<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={openDisableDialog}
|
onSelect={openDisableDialog}
|
||||||
>
|
>
|
||||||
<AccountBanIcon fontSize={20} /> Disable & Delete data
|
<AccountBanIcon fontSize={20} /> Disable & Delete data
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
)}
|
)}
|
||||||
<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}
|
onSelect={openDeleteDialog}
|
||||||
>
|
>
|
||||||
<DeleteIcon fontSize={20} /> Delete
|
<DeleteIcon fontSize={20} /> Delete
|
||||||
|
|||||||
@@ -69,9 +69,9 @@ export function DataTable<TData, TValue>({
|
|||||||
}, [data]);
|
}, [data]);
|
||||||
|
|
||||||
return (
|
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} />
|
<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>
|
<Table>
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
{table.getHeaderGroups().map(headerGroup => (
|
{table.getHeaderGroups().map(headerGroup => (
|
||||||
@@ -107,49 +107,54 @@ export function DataTable<TData, TValue>({
|
|||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
))}
|
||||||
</TableHeader>
|
</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>
|
</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>
|
</div>
|
||||||
|
|
||||||
<DataTablePagination table={table} />
|
<DataTablePagination table={table} />
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ export const DeleteAccountDialog = ({
|
|||||||
className="placeholder:opacity-50"
|
className="placeholder:opacity-50"
|
||||||
/>
|
/>
|
||||||
<DialogFooter>
|
<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}>
|
<Button type="button" variant="outline" size="sm" onClick={onClose}>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ export const DisableAccountDialog = ({
|
|||||||
className="placeholder:opacity-50"
|
className="placeholder:opacity-50"
|
||||||
/>
|
/>
|
||||||
<DialogFooter>
|
<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}>
|
<Button type="button" variant="outline" size="sm" onClick={onClose}>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -23,13 +23,13 @@ export const DiscardChanges = ({
|
|||||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
<DialogContent className="sm:w-[460px]">
|
<DialogContent className="sm:w-[460px]">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle className="leading-7">Discard Changes</DialogTitle>
|
<DialogTitle className="leading-6.5">Discard Changes</DialogTitle>
|
||||||
<DialogDescription className="leading-6">
|
<DialogDescription className="leading-6 text-[15px]">
|
||||||
Changes to this user will not be saved.
|
Changes to this user will not be saved.
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<DialogFooter>
|
<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">
|
<Button type="button" onClick={onClose} variant="outline">
|
||||||
<span>Cancel</span>
|
<span>Cancel</span>
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export const EnableAccountDialog = ({
|
|||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<DialogFooter>
|
<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">
|
<Button type="button" onClick={onClose} variant="outline">
|
||||||
<span>Cancel</span>
|
<span>Cancel</span>
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { Button } from '@affine/admin/components/ui/button';
|
|||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
DialogFooter,
|
DialogFooter,
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
@@ -198,7 +199,9 @@ export function ImportUsersDialog({
|
|||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
<DialogContent
|
<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>
|
<DialogHeader>
|
||||||
<DialogTitle>
|
<DialogTitle>
|
||||||
@@ -211,35 +214,32 @@ export function ImportUsersDialog({
|
|||||||
: 'Import'}
|
: 'Import'}
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
<DialogDescription className="text-[15px]">
|
||||||
{isFormatError ? (
|
{isFormatError ? (
|
||||||
<div className="grid gap-4 py-4">
|
'You need to import the accounts by importing a CSV file in the correct format. Please download the CSV template.'
|
||||||
<p className="text-sm text-gray-500">
|
) : isPreviewMode ? (
|
||||||
You need to import the accounts by importing a CSV file in the
|
<div className="grid gap-3">
|
||||||
correct format. Please download the CSV template.
|
{isImported ? null : (
|
||||||
</p>
|
<p>
|
||||||
</div>
|
{parsedUsers.length} users detected from the CSV file. Please
|
||||||
) : isPreviewMode ? (
|
confirm the user list below and import.
|
||||||
<div className="grid gap-4 py-4">
|
</p>
|
||||||
<p className="text-sm text-gray-500">
|
)}
|
||||||
{parsedUsers.length} users detected from the CSV file. Please
|
<UserTable users={parsedUsers} />
|
||||||
confirm the user list below and import.
|
</div>
|
||||||
</p>
|
) : (
|
||||||
<UserTable users={parsedUsers} />
|
<div className="grid gap-3">
|
||||||
</div>
|
<p>
|
||||||
) : (
|
You need to import the accounts by importing a CSV file in the
|
||||||
<div className="grid gap-4 py-4">
|
correct format. Please download the CSV template.
|
||||||
<p className="text-sm text-gray-500">
|
</p>
|
||||||
You need to import the accounts by importing a CSV file in the
|
<FileUploadArea
|
||||||
correct format. Please download the CSV template.
|
ref={fileUploadRef}
|
||||||
</p>
|
onFileSelected={handleFileSelected}
|
||||||
|
/>
|
||||||
<FileUploadArea
|
</div>
|
||||||
ref={fileUploadRef}
|
)}
|
||||||
onFileSelected={handleFileSelected}
|
</DialogDescription>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<DialogFooter
|
<DialogFooter
|
||||||
className={`flex-col sm:flex-row sm:justify-between items-center ${isPreviewMode ? 'sm:justify-end' : 'sm:justify-between'}`}
|
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
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
|
||||||
onClick={cancelImport}
|
onClick={cancelImport}
|
||||||
className="mb-2 sm:mb-0"
|
className="w-full mb-2 sm:mb-0 sm:w-auto"
|
||||||
disabled={isImporting}
|
disabled={isImporting}
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export const ResetPasswordDialog = ({
|
|||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<DialogFooter>
|
<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
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
value={link}
|
value={link}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export const DiscardChanges = ({
|
|||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<DialogFooter>
|
<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">
|
<Button type="button" onClick={onClose} variant="outline">
|
||||||
<span>Cancel</span>
|
<span>Cancel</span>
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -216,7 +216,7 @@ export const LeftPanel = ({
|
|||||||
onExpand={onExpand}
|
onExpand={onExpand}
|
||||||
onCollapse={onCollapse}
|
onCollapse={onCollapse}
|
||||||
className={cn(
|
className={cn(
|
||||||
isCollapsed ? 'min-w-[56px] max-w-[56px]' : 'min-w-56 max-w-56',
|
isCollapsed ? 'min-w-[57px] max-w-[57px]' : 'min-w-56 max-w-56',
|
||||||
'border-r h-dvh'
|
'border-r h-dvh'
|
||||||
)}
|
)}
|
||||||
style={{ overflow: 'visible' }}
|
style={{ overflow: 'visible' }}
|
||||||
@@ -231,11 +231,18 @@ export const LeftPanel = ({
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex h-[56px] items-center gap-2 px-4 text-base font-medium',
|
'flex h-[56px] items-center px-4 text-base font-medium',
|
||||||
isCollapsed && 'justify-center px-2'
|
isCollapsed && 'justify-center px-2'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Logo />
|
<span
|
||||||
|
className={cn(
|
||||||
|
'flex items-center p-0.5 mr-2',
|
||||||
|
isCollapsed && 'justify-center px-2 mr-0'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Logo />
|
||||||
|
</span>
|
||||||
{!isCollapsed && 'AFFiNE'}
|
{!isCollapsed && 'AFFiNE'}
|
||||||
</div>
|
</div>
|
||||||
<Nav isCollapsed={isCollapsed} />
|
<Nav isCollapsed={isCollapsed} />
|
||||||
@@ -283,12 +290,12 @@ export const RightPanel = ({
|
|||||||
order={2}
|
order={2}
|
||||||
ref={panelRef}
|
ref={panelRef}
|
||||||
defaultSize={0}
|
defaultSize={0}
|
||||||
maxSize={30}
|
maxSize={20}
|
||||||
collapsible={true}
|
collapsible={true}
|
||||||
collapsedSize={0}
|
collapsedSize={0}
|
||||||
onExpand={onExpand}
|
onExpand={onExpand}
|
||||||
onCollapse={onCollapse}
|
onCollapse={onCollapse}
|
||||||
className="border-l"
|
className="border-l max-w-96"
|
||||||
>
|
>
|
||||||
{panelContent}
|
{panelContent}
|
||||||
</ResizablePanel>
|
</ResizablePanel>
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ const NavItem = ({ icon, label, to, isCollapsed }: NavItemProps) => {
|
|||||||
},
|
},
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{icon}
|
<span className="flex items-center p-0.5 mr-2">{icon}</span>
|
||||||
{label}
|
{label}
|
||||||
</NavLink>
|
</NavLink>
|
||||||
);
|
);
|
||||||
@@ -86,25 +86,19 @@ export function Nav({ isCollapsed = false }: NavProps) {
|
|||||||
>
|
>
|
||||||
<NavItem
|
<NavItem
|
||||||
to="/admin/config"
|
to="/admin/config"
|
||||||
icon={
|
icon={<SelfhostIcon fontSize={20} />}
|
||||||
<SelfhostIcon className={cn(!isCollapsed && 'mr-2', 'h-5 w-5')} />
|
|
||||||
}
|
|
||||||
label="Server"
|
label="Server"
|
||||||
isCollapsed={isCollapsed}
|
isCollapsed={isCollapsed}
|
||||||
/>
|
/>
|
||||||
<NavItem
|
<NavItem
|
||||||
to="/admin/accounts"
|
to="/admin/accounts"
|
||||||
icon={
|
icon={<AccountIcon fontSize={20} />}
|
||||||
<AccountIcon className={cn(!isCollapsed && 'mr-2', 'h-5 w-5')} />
|
|
||||||
}
|
|
||||||
label="Accounts"
|
label="Accounts"
|
||||||
isCollapsed={isCollapsed}
|
isCollapsed={isCollapsed}
|
||||||
/>
|
/>
|
||||||
<NavItem
|
<NavItem
|
||||||
to="/admin/ai"
|
to="/admin/ai"
|
||||||
icon={
|
icon={<AiOutlineIcon fontSize={20} />}
|
||||||
<AiOutlineIcon className={cn(!isCollapsed && 'mr-2', 'h-5 w-5')} />
|
|
||||||
}
|
|
||||||
label="AI"
|
label="AI"
|
||||||
isCollapsed={isCollapsed}
|
isCollapsed={isCollapsed}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -17,10 +17,15 @@ export const ServerVersion = () => {
|
|||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="flex items-center justify-center gap-1 text-xs p-2 font-medium"
|
className="flex items-center justify-center gap-1 text-xs p-2 font-medium w-full overflow-hidden"
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
|
title={`New Version ${availableUpgrade.version} Available`}
|
||||||
>
|
>
|
||||||
New Version <span>{availableUpgrade.version}</span>Available
|
<span className="overflow-hidden text-ellipsis">
|
||||||
|
New Version
|
||||||
|
<span>{availableUpgrade.version}</span>
|
||||||
|
Available
|
||||||
|
</span>
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ export const SettingsItem = ({ isCollapsed }: { isCollapsed: boolean }) => {
|
|||||||
: undefined,
|
: undefined,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<SettingsIcon className="h-5 w-5" />
|
<SettingsIcon fontSize={20} />
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</NavigationMenuPrimitive.Trigger>
|
</NavigationMenuPrimitive.Trigger>
|
||||||
<NavigationMenuPrimitive.Content>
|
<NavigationMenuPrimitive.Content>
|
||||||
@@ -119,7 +119,9 @@ export const SettingsItem = ({ isCollapsed }: { isCollapsed: boolean }) => {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<SettingsIcon className={cn(!isCollapsed && 'mr-2', 'h-5 w-5')} />
|
<span className="flex items-center p-0.5 mr-2">
|
||||||
|
<SettingsIcon fontSize={20} />
|
||||||
|
</span>
|
||||||
<span>Settings</span>
|
<span>Settings</span>
|
||||||
</div>
|
</div>
|
||||||
</AccordionTrigger>
|
</AccordionTrigger>
|
||||||
|
|||||||
@@ -73,15 +73,20 @@ export function UserDropdown({ isCollapsed }: UserDropdownProps) {
|
|||||||
</AvatarFallback>
|
</AvatarFallback>
|
||||||
</Avatar>
|
</Avatar>
|
||||||
{currentUser?.name ? (
|
{currentUser?.name ? (
|
||||||
<span className="text-sm text-nowrap text-ellipsis break-words overflow-hidden">
|
<span
|
||||||
|
className="text-sm text-nowrap text-ellipsis break-words overflow-hidden"
|
||||||
|
title={currentUser?.name}
|
||||||
|
>
|
||||||
{currentUser?.name}
|
{currentUser?.name}
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
// Fallback to email prefix if name is not available
|
// Fallback to email prefix if name is not available
|
||||||
<span className="text-sm">{currentUser?.email.split('@')[0]}</span>
|
<span className="text-sm" title={currentUser?.email.split('@')[0]}>
|
||||||
|
{currentUser?.email.split('@')[0]}
|
||||||
|
</span>
|
||||||
)}
|
)}
|
||||||
<span
|
<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={{
|
style={{
|
||||||
borderRadius: '4px',
|
borderRadius: '4px',
|
||||||
backgroundColor: cssVarV2('chip/label/blue'),
|
backgroundColor: cssVarV2('chip/label/blue'),
|
||||||
@@ -98,7 +103,27 @@ export function UserDropdown({ isCollapsed }: UserDropdownProps) {
|
|||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="end" side="right">
|
<DropdownMenuContent align="end" side="right">
|
||||||
<DropdownMenuLabel>{currentUser?.name}</DropdownMenuLabel>
|
<DropdownMenuLabel className="flex items-center gap-2">
|
||||||
|
<Avatar className="w-8 h-8">
|
||||||
|
<AvatarImage src={currentUser?.avatarUrl ?? undefined} />
|
||||||
|
<AvatarFallback>
|
||||||
|
<CircleUser size={32} />
|
||||||
|
</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
<div className="flex flex-col font-medium gap-1">
|
||||||
|
{currentUser?.name ?? currentUser?.email.split('@')[0]}
|
||||||
|
<span
|
||||||
|
className="w-fit 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'),
|
||||||
|
borderColor: cssVarV2('layer/insideBorder/border'),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Admin
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</DropdownMenuLabel>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
<DropdownMenuItem onSelect={handleLogout}>Logout</DropdownMenuItem>
|
<DropdownMenuItem onSelect={handleLogout}>Logout</DropdownMenuItem>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ export const ConfirmChanges = ({
|
|||||||
'There is no change.'
|
'There is no change.'
|
||||||
)}
|
)}
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<div className="flex justify-end items-center w-full space-x-4">
|
<div className="flex justify-end items-center w-full gap-2">
|
||||||
<Button type="button" onClick={onClose} variant="outline">
|
<Button type="button" onClick={onClose} variant="outline">
|
||||||
<span>Cancel</span>
|
<span>Cancel</span>
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
Reference in New Issue
Block a user