mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-02 09:59:55 +08:00
fix(admin): adjust admin panel settings style (#11291)
Adjusted the style of some runtime configurations to match the new parameters. Adjusted the style of dialog and right sidebar. close AF-2411 AF-2412 AF-2413 AF-2422
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import { cn } from '@affine/admin/utils';
|
import { cn } from '@affine/admin/utils';
|
||||||
|
import { CloseIcon } from '@blocksuite/icons/rc';
|
||||||
import * as DialogPrimitive from '@radix-ui/react-dialog';
|
import * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||||
import { X } from 'lucide-react';
|
import { cssVarV2 } from '@toeverything/theme/v2';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
const Dialog = DialogPrimitive.Root;
|
const Dialog = DialogPrimitive.Root;
|
||||||
@@ -35,14 +36,14 @@ const DialogContent = React.forwardRef<
|
|||||||
<DialogPrimitive.Content
|
<DialogPrimitive.Content
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg',
|
'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg',
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
|
<DialogPrimitive.Close className="cursor-pointer absolute right-7 top-7 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
|
||||||
<X className="h-4 w-4" />
|
<CloseIcon fontSize={20} color={cssVarV2('selfhost/icon/secondary')} />
|
||||||
<span className="sr-only">Close</span>
|
<span className="sr-only">Close</span>
|
||||||
</DialogPrimitive.Close>
|
</DialogPrimitive.Close>
|
||||||
</DialogPrimitive.Content>
|
</DialogPrimitive.Content>
|
||||||
@@ -56,7 +57,7 @@ const DialogHeader = ({
|
|||||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex flex-col space-y-1.5 text-center sm:text-left',
|
'flex flex-col space-y-3 text-center sm:text-left',
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -85,7 +86,7 @@ const DialogTitle = React.forwardRef<
|
|||||||
<DialogPrimitive.Title
|
<DialogPrimitive.Title
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
'text-lg font-semibold leading-none tracking-tight',
|
'text-lg font-semibold tracking-tight leading-6.5',
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -99,7 +100,7 @@ const DialogDescription = React.forwardRef<
|
|||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<DialogPrimitive.Description
|
<DialogPrimitive.Description
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn('text-sm text-muted-foreground', className)}
|
className={cn('text-[15px] text-muted-foreground leading-6', className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
));
|
));
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ const ScrollArea = React.forwardRef<
|
|||||||
className={cn('relative overflow-hidden', className)}
|
className={cn('relative overflow-hidden', className)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
|
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit] [&>*:first-child]:!block">
|
||||||
{children}
|
{children}
|
||||||
</ScrollAreaPrimitive.Viewport>
|
</ScrollAreaPrimitive.Viewport>
|
||||||
<ScrollBar />
|
<ScrollBar />
|
||||||
|
|||||||
@@ -4,10 +4,14 @@ import {
|
|||||||
AvatarImage,
|
AvatarImage,
|
||||||
} from '@affine/admin/components/ui/avatar';
|
} from '@affine/admin/components/ui/avatar';
|
||||||
import { FeatureType } from '@affine/graphql';
|
import { FeatureType } from '@affine/graphql';
|
||||||
import { AccountIcon, LockIcon, UnlockIcon } from '@blocksuite/icons/rc';
|
import {
|
||||||
|
AccountIcon,
|
||||||
|
EmailIcon,
|
||||||
|
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 { MailIcon } from 'lucide-react';
|
|
||||||
import {
|
import {
|
||||||
type Dispatch,
|
type Dispatch,
|
||||||
type ReactNode,
|
type ReactNode,
|
||||||
@@ -201,19 +205,29 @@ export const useColumns = ({
|
|||||||
color={cssVarV2('selfhost/icon/tertiary')}
|
color={cssVarV2('selfhost/icon/tertiary')}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
IconFalse={<UnlockIcon fontSize={16} />}
|
IconFalse={
|
||||||
|
<UnlockIcon
|
||||||
|
fontSize={16}
|
||||||
|
color={cssVarV2('toast/iconState/error')}
|
||||||
|
/>
|
||||||
|
}
|
||||||
textTrue="Password Set"
|
textTrue="Password Set"
|
||||||
textFalse="No Password"
|
textFalse="No Password"
|
||||||
/>
|
/>
|
||||||
<StatusItem
|
<StatusItem
|
||||||
condition={user.emailVerified}
|
condition={user.emailVerified}
|
||||||
IconTrue={
|
IconTrue={
|
||||||
<MailIcon
|
<EmailIcon
|
||||||
size={16}
|
fontSize={16}
|
||||||
color={cssVarV2('selfhost/icon/tertiary')}
|
color={cssVarV2('selfhost/icon/tertiary')}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
IconFalse={<MailIcon size={16} />}
|
IconFalse={
|
||||||
|
<EmailIcon
|
||||||
|
fontSize={16}
|
||||||
|
color={cssVarV2('toast/iconState/error')}
|
||||||
|
/>
|
||||||
|
}
|
||||||
textTrue="Email Verified"
|
textTrue="Email Verified"
|
||||||
textFalse="Email Not Verified"
|
textFalse="Email Not Verified"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -53,9 +53,9 @@ export const DeleteAccountDialog = ({
|
|||||||
value={input}
|
value={input}
|
||||||
onChange={handleInput}
|
onChange={handleInput}
|
||||||
placeholder="Please type email to confirm"
|
placeholder="Please type email to confirm"
|
||||||
className="placeholder:opacity-50"
|
className="placeholder:opacity-50 mt-4 h-9"
|
||||||
/>
|
/>
|
||||||
<DialogFooter>
|
<DialogFooter className="mt-6">
|
||||||
<div className="flex justify-end gap-2 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
|
||||||
|
|||||||
@@ -53,9 +53,9 @@ export const DisableAccountDialog = ({
|
|||||||
value={input}
|
value={input}
|
||||||
onChange={handleInput}
|
onChange={handleInput}
|
||||||
placeholder="Please type email to confirm"
|
placeholder="Please type email to confirm"
|
||||||
className="placeholder:opacity-50"
|
className="placeholder:opacity-50 mt-4 h-9"
|
||||||
/>
|
/>
|
||||||
<DialogFooter>
|
<DialogFooter className="mt-6">
|
||||||
<div className="flex justify-end gap-2 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
|
||||||
|
|||||||
@@ -23,12 +23,12 @@ 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-6.5">Discard Changes</DialogTitle>
|
<DialogTitle>Discard Changes</DialogTitle>
|
||||||
<DialogDescription className="leading-6 text-[15px]">
|
<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 className="mt-6">
|
||||||
<div className="flex justify-end gap-2 items-center w-full">
|
<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>
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export const EnableAccountDialog = ({
|
|||||||
used to log in.
|
used to log in.
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<DialogFooter>
|
<DialogFooter className="mt-6">
|
||||||
<div className="flex justify-end gap-2 items-center w-full">
|
<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>
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ export function ExportUsersDialog({
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DialogFooter>
|
<DialogFooter className="mt-6">
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={handleExport}
|
onClick={handleExport}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export const ResetPasswordDialog = ({
|
|||||||
complete it.
|
complete it.
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<DialogFooter>
|
<DialogFooter className="mt-4">
|
||||||
<div className="flex justify-end gap-2 items-center w-full">
|
<div className="flex justify-end gap-2 items-center w-full">
|
||||||
<Input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
|
|||||||
@@ -99,13 +99,14 @@ function UserForm({
|
|||||||
handleConfirm={handleConfirm}
|
handleConfirm={handleConfirm}
|
||||||
canSave={canSave}
|
canSave={canSave}
|
||||||
/>
|
/>
|
||||||
<div className="p-4 flex-grow overflow-y-auto space-y-[10px]">
|
<div className="p-4 flex-grow overflow-y-auto space-y-[8px]">
|
||||||
<div className="flex flex-col rounded-md border py-4 gap-4">
|
<div className="flex flex-col rounded-md border">
|
||||||
<InputItem
|
<InputItem
|
||||||
label="Name"
|
label="User name"
|
||||||
field="name"
|
field="name"
|
||||||
value={changes.name}
|
value={changes.name}
|
||||||
onChange={setField}
|
onChange={setField}
|
||||||
|
placeholder="Enter user name"
|
||||||
/>
|
/>
|
||||||
<Separator />
|
<Separator />
|
||||||
<InputItem
|
<InputItem
|
||||||
@@ -113,6 +114,7 @@ function UserForm({
|
|||||||
field="email"
|
field="email"
|
||||||
value={changes.email}
|
value={changes.email}
|
||||||
onChange={setField}
|
onChange={setField}
|
||||||
|
placeholder="Enter email address"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -153,8 +155,10 @@ function ToggleItem({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Label className="flex items-center justify-between px-4 py-3">
|
<Label className="flex items-center justify-between p-3 text-[15px] gap-2 font-medium leading-6 overflow-hidden">
|
||||||
<span>{name}</span>
|
<span className="overflow-hidden text-ellipsis" title={name}>
|
||||||
|
{name}
|
||||||
|
</span>
|
||||||
<Switch checked={checked} onCheckedChange={onToggle} />
|
<Switch checked={checked} onCheckedChange={onToggle} />
|
||||||
</Label>
|
</Label>
|
||||||
);
|
);
|
||||||
@@ -165,11 +169,13 @@ function InputItem({
|
|||||||
field,
|
field,
|
||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
|
placeholder,
|
||||||
}: {
|
}: {
|
||||||
label: string;
|
label: string;
|
||||||
field: keyof UserInput;
|
field: keyof UserInput;
|
||||||
value?: string;
|
value?: string;
|
||||||
onChange: (field: keyof UserInput, value: string) => void;
|
onChange: (field: keyof UserInput, value: string) => void;
|
||||||
|
placeholder?: string;
|
||||||
}) {
|
}) {
|
||||||
const onValueChange = useCallback(
|
const onValueChange = useCallback(
|
||||||
(e: ChangeEvent<HTMLInputElement>) => {
|
(e: ChangeEvent<HTMLInputElement>) => {
|
||||||
@@ -179,13 +185,16 @@ function InputItem({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="px-5 space-y-3">
|
<div className="p-3">
|
||||||
<Label className="text-sm font-medium">{label}</Label>
|
<Label className="text-[15px] leading-6 font-medium mb-1.5">
|
||||||
|
{label}
|
||||||
|
</Label>
|
||||||
<Input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
className="py-2 px-3 text-base font-normal"
|
className="py-2 px-3 text-[15px] font-normal h-9"
|
||||||
value={value}
|
value={value}
|
||||||
onChange={onValueChange}
|
onChange={onValueChange}
|
||||||
|
placeholder={placeholder}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export const DiscardChanges = ({
|
|||||||
Changes to this prompt will not be saved.
|
Changes to this prompt will not be saved.
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<DialogFooter>
|
<DialogFooter className="mt-6">
|
||||||
<div className="flex justify-end gap-2 items-center w-full">
|
<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>
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ import {
|
|||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { NavLink } from 'react-router-dom';
|
import { NavLink } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { buttonVariants } from '../../components/ui/button';
|
||||||
|
import { cn } from '../../utils';
|
||||||
|
|
||||||
export const CollapsibleItem = ({
|
export const CollapsibleItem = ({
|
||||||
title,
|
title,
|
||||||
changeModule,
|
changeModule,
|
||||||
@@ -14,22 +17,12 @@ export const CollapsibleItem = ({
|
|||||||
title: string;
|
title: string;
|
||||||
changeModule?: (module: string) => void;
|
changeModule?: (module: string) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const handleClick = useCallback(
|
const handleClick = useCallback(() => {
|
||||||
(id: string) => {
|
changeModule?.(title);
|
||||||
const targetElement = document.getElementById(id);
|
}, [changeModule, title]);
|
||||||
if (targetElement) {
|
|
||||||
targetElement.scrollIntoView({
|
|
||||||
behavior: 'smooth',
|
|
||||||
block: 'center',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
changeModule?.(title);
|
|
||||||
},
|
|
||||||
[changeModule, title]
|
|
||||||
);
|
|
||||||
return (
|
return (
|
||||||
<Accordion type="multiple" className="w-full ">
|
<Accordion type="multiple" className="w-full">
|
||||||
<AccordionItem value="item-1" className="border-b-0 ml-7 ">
|
<AccordionItem value="item-1" className="border-b-0 ml-7">
|
||||||
<NavLink
|
<NavLink
|
||||||
to={`/admin/settings/${title}`}
|
to={`/admin/settings/${title}`}
|
||||||
className={({ isActive }) => {
|
className={({ isActive }) => {
|
||||||
@@ -39,7 +32,7 @@ export const CollapsibleItem = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<AccordionTrigger
|
<AccordionTrigger
|
||||||
onClick={() => handleClick(title)}
|
onClick={handleClick}
|
||||||
className="py-2 px-2 rounded [&[data-state=closed]>svg]:rotate-270 [&[data-state=open]>svg]:rotate-360"
|
className="py-2 px-2 rounded [&[data-state=closed]>svg]:rotate-270 [&[data-state=open]>svg]:rotate-360"
|
||||||
>
|
>
|
||||||
{title}
|
{title}
|
||||||
@@ -50,6 +43,36 @@ export const CollapsibleItem = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const NormalSubItem = ({
|
||||||
|
title,
|
||||||
|
changeModule,
|
||||||
|
}: {
|
||||||
|
title: string;
|
||||||
|
changeModule?: (module: string) => void;
|
||||||
|
}) => {
|
||||||
|
const handleClick = useCallback(() => {
|
||||||
|
changeModule?.(title);
|
||||||
|
}, [changeModule, title]);
|
||||||
|
return (
|
||||||
|
<div className="w-full flex">
|
||||||
|
<NavLink
|
||||||
|
to={`/admin/settings/${title}`}
|
||||||
|
onClick={handleClick}
|
||||||
|
className={({ isActive }) => {
|
||||||
|
return cn(
|
||||||
|
buttonVariants({
|
||||||
|
variant: 'ghost',
|
||||||
|
className: `ml-8 px-2 w-full justify-start ${isActive ? 'bg-zinc-100' : ''}`,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
</NavLink>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const OtherModules = ({
|
export const OtherModules = ({
|
||||||
moduleList,
|
moduleList,
|
||||||
changeModule,
|
changeModule,
|
||||||
@@ -58,14 +81,14 @@ export const OtherModules = ({
|
|||||||
changeModule?: (module: string) => void;
|
changeModule?: (module: string) => void;
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<Accordion type="multiple" className="w-full ">
|
<Accordion type="multiple" className="w-full">
|
||||||
<AccordionItem value="item-1" className="border-b-0">
|
<AccordionItem value="item-1" className="border-b-0">
|
||||||
<AccordionTrigger className="ml-7 py-2 px-2 rounded [&[data-state=closed]>svg]:rotate-270 [&[data-state=open]>svg]:rotate-360">
|
<AccordionTrigger className="ml-8 py-2 px-2 rounded [&[data-state=closed]>svg]:rotate-270 [&[data-state=open]>svg]:rotate-360">
|
||||||
Other
|
Other
|
||||||
</AccordionTrigger>
|
</AccordionTrigger>
|
||||||
<AccordionContent className="flex flex-col gap-2 py-1">
|
<AccordionContent className="flex flex-col gap-1 py-1">
|
||||||
{moduleList.map(module => (
|
{moduleList.map(module => (
|
||||||
<CollapsibleItem
|
<NormalSubItem
|
||||||
key={module}
|
key={module}
|
||||||
title={module}
|
title={module}
|
||||||
changeModule={changeModule}
|
changeModule={changeModule}
|
||||||
|
|||||||
@@ -33,12 +33,15 @@ export const ServerVersion = () => {
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="flex items-center justify-between pt-2 border-t px-2 text-xs"
|
className="inline-flex items-center justify-between pt-2 border-t px-2 text-xs flex-nowrap gap-1"
|
||||||
style={{
|
style={{
|
||||||
color: cssVarV2('text/tertiary'),
|
color: cssVarV2('text/tertiary'),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
ServerVersion<span>v{version}</span>
|
<span>ServerVersion</span>
|
||||||
|
<span className="overflow-hidden text-ellipsis" title={version}>
|
||||||
|
v{version}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { cssVarV2 } from '@toeverything/theme/v2';
|
|||||||
import { NavLink } from 'react-router-dom';
|
import { NavLink } from 'react-router-dom';
|
||||||
|
|
||||||
import { ALL_CONFIGURABLE_MODULES } from '../settings/config';
|
import { ALL_CONFIGURABLE_MODULES } from '../settings/config';
|
||||||
import { CollapsibleItem, OtherModules } from './collapsible-item';
|
import { NormalSubItem, OtherModules } from './collapsible-item';
|
||||||
import { useNav } from './context';
|
import { useNav } from './context';
|
||||||
|
|
||||||
const authModule = ALL_CONFIGURABLE_MODULES.find(module => module === 'auth');
|
const authModule = ALL_CONFIGURABLE_MODULES.find(module => module === 'auth');
|
||||||
@@ -53,13 +53,35 @@ export const SettingsItem = ({ isCollapsed }: { isCollapsed: boolean }) => {
|
|||||||
</NavigationMenuPrimitive.Trigger>
|
</NavigationMenuPrimitive.Trigger>
|
||||||
<NavigationMenuPrimitive.Content>
|
<NavigationMenuPrimitive.Content>
|
||||||
<ul
|
<ul
|
||||||
className="border rounded-lg w-full flex flex-col p-1"
|
className="border rounded-lg w-full flex flex-col p-1 min-w-[160px] max-h-[200px] overflow-y-auto"
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: cssVarV2('layer/background/overlayPanel'),
|
backgroundColor: cssVarV2('layer/background/overlayPanel'),
|
||||||
borderColor: cssVarV2('layer/insideBorder/blackBorder'),
|
borderColor: cssVarV2('layer/insideBorder/blackBorder'),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{ALL_CONFIGURABLE_MODULES.map(module => (
|
{authModule ? (
|
||||||
|
<li key={authModule} className="flex">
|
||||||
|
<NavLink
|
||||||
|
to={`/admin/settings/${authModule}`}
|
||||||
|
className={cn(
|
||||||
|
buttonVariants({
|
||||||
|
variant: 'ghost',
|
||||||
|
className:
|
||||||
|
'p-2 rounded-[6px] text-[14px] w-full justify-start font-normal',
|
||||||
|
})
|
||||||
|
)}
|
||||||
|
style={({ isActive }) => ({
|
||||||
|
backgroundColor: isActive
|
||||||
|
? cssVarV2('selfhost/button/sidebarButton/bg/select')
|
||||||
|
: undefined,
|
||||||
|
})}
|
||||||
|
onClick={() => setCurrentModule?.(authModule)}
|
||||||
|
>
|
||||||
|
{authModule}
|
||||||
|
</NavLink>
|
||||||
|
</li>
|
||||||
|
) : null}
|
||||||
|
{otherModules.map(module => (
|
||||||
<li key={module} className="flex">
|
<li key={module} className="flex">
|
||||||
<NavLink
|
<NavLink
|
||||||
to={`/admin/settings/${module}`}
|
to={`/admin/settings/${module}`}
|
||||||
@@ -67,7 +89,7 @@ export const SettingsItem = ({ isCollapsed }: { isCollapsed: boolean }) => {
|
|||||||
buttonVariants({
|
buttonVariants({
|
||||||
variant: 'ghost',
|
variant: 'ghost',
|
||||||
className:
|
className:
|
||||||
'p-1.5 rounded-[6px] text-[14px] w-full justify-start',
|
'p-2 rounded-[6px] text-[14px] w-full justify-start font-normal',
|
||||||
})
|
})
|
||||||
)}
|
)}
|
||||||
style={({ isActive }) => ({
|
style={({ isActive }) => ({
|
||||||
@@ -91,7 +113,7 @@ export const SettingsItem = ({ isCollapsed }: { isCollapsed: boolean }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Accordion type="multiple" className="w-full h-full overflow-hidden">
|
<Accordion type="multiple" className="w-full overflow-hidden">
|
||||||
<AccordionItem
|
<AccordionItem
|
||||||
value="item-1"
|
value="item-1"
|
||||||
className="border-b-0 h-full flex flex-col gap-1 w-full"
|
className="border-b-0 h-full flex flex-col gap-1 w-full"
|
||||||
@@ -130,7 +152,7 @@ export const SettingsItem = ({ isCollapsed }: { isCollapsed: boolean }) => {
|
|||||||
>
|
>
|
||||||
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit] [&>div]:!block">
|
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit] [&>div]:!block">
|
||||||
{authModule && (
|
{authModule && (
|
||||||
<CollapsibleItem
|
<NormalSubItem
|
||||||
title={authModule}
|
title={authModule}
|
||||||
changeModule={setCurrentModule}
|
changeModule={setCurrentModule}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -24,6 +24,40 @@ interface UserDropdownProps {
|
|||||||
isCollapsed: boolean;
|
isCollapsed: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const UserInfo = ({
|
||||||
|
name,
|
||||||
|
email,
|
||||||
|
avatarUrl,
|
||||||
|
}: {
|
||||||
|
email: string;
|
||||||
|
avatarUrl: string | null;
|
||||||
|
name?: string;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Avatar className="w-8 h-8">
|
||||||
|
<AvatarImage src={avatarUrl ?? undefined} />
|
||||||
|
<AvatarFallback>
|
||||||
|
<CircleUser size={32} />
|
||||||
|
</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
<div className="flex flex-col font-medium gap-1">
|
||||||
|
{name ?? 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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export function UserDropdown({ isCollapsed }: UserDropdownProps) {
|
export function UserDropdown({ isCollapsed }: UserDropdownProps) {
|
||||||
const currentUser = useCurrentUser();
|
const currentUser = useCurrentUser();
|
||||||
const relative = useRevalidateCurrentUser();
|
const relative = useRevalidateCurrentUser();
|
||||||
@@ -53,7 +87,15 @@ 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">
|
||||||
|
{currentUser ? (
|
||||||
|
<UserInfo
|
||||||
|
email={currentUser.email}
|
||||||
|
name={currentUser.name}
|
||||||
|
avatarUrl={currentUser.avatarUrl}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</DropdownMenuLabel>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
<DropdownMenuItem onSelect={handleLogout}>Logout</DropdownMenuItem>
|
<DropdownMenuItem onSelect={handleLogout}>Logout</DropdownMenuItem>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
@@ -104,25 +146,13 @@ export function UserDropdown({ isCollapsed }: UserDropdownProps) {
|
|||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="end" side="right">
|
<DropdownMenuContent align="end" side="right">
|
||||||
<DropdownMenuLabel className="flex items-center gap-2">
|
<DropdownMenuLabel className="flex items-center gap-2">
|
||||||
<Avatar className="w-8 h-8">
|
{currentUser ? (
|
||||||
<AvatarImage src={currentUser?.avatarUrl ?? undefined} />
|
<UserInfo
|
||||||
<AvatarFallback>
|
email={currentUser.email}
|
||||||
<CircleUser size={32} />
|
name={currentUser.name}
|
||||||
</AvatarFallback>
|
avatarUrl={currentUser.avatarUrl}
|
||||||
</Avatar>
|
/>
|
||||||
<div className="flex flex-col font-medium gap-1">
|
) : null}
|
||||||
{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>
|
</DropdownMenuLabel>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
<DropdownMenuItem onSelect={handleLogout}>Logout</DropdownMenuItem>
|
<DropdownMenuItem onSelect={handleLogout}>Logout</DropdownMenuItem>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { Input } from '@affine/admin/components/ui/input';
|
|||||||
import { Switch } from '@affine/admin/components/ui/switch';
|
import { Switch } from '@affine/admin/components/ui/switch';
|
||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
|
|
||||||
|
import { Textarea } from '../../components/ui/textarea';
|
||||||
import { isEqual } from './utils';
|
import { isEqual } from './utils';
|
||||||
|
|
||||||
interface ConfigInputProps {
|
interface ConfigInputProps {
|
||||||
@@ -59,7 +60,7 @@ const Inputs: Record<
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
JSON: function ObjectInput({ defaultValue, onChange }) {
|
JSON: function ObjectInput({ defaultValue, onChange }) {
|
||||||
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleInputChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||||
try {
|
try {
|
||||||
const value = JSON.parse(e.target.value);
|
const value = JSON.parse(e.target.value);
|
||||||
onChange(value);
|
onChange(value);
|
||||||
@@ -67,10 +68,10 @@ const Inputs: Record<
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Input
|
<Textarea
|
||||||
type="text"
|
|
||||||
defaultValue={JSON.stringify(defaultValue)}
|
defaultValue={JSON.stringify(defaultValue)}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
|
className="w-full"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -98,9 +99,15 @@ export const ConfigInput = ({
|
|||||||
const isValueEqual = isEqual(value, defaultValue);
|
const isValueEqual = isEqual(value, defaultValue);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="flex flex-col items-end gap-2 w-full">
|
||||||
<Input defaultValue={defaultValue} onChange={onValueChange} />
|
<Input defaultValue={defaultValue} onChange={onValueChange} />
|
||||||
<div style={{ opacity: isValueEqual ? 0 : 1 }}>
|
<div
|
||||||
|
className="w-full break-words"
|
||||||
|
style={{
|
||||||
|
opacity: isValueEqual ? 0 : 1,
|
||||||
|
height: isValueEqual ? 0 : 'auto',
|
||||||
|
}}
|
||||||
|
>
|
||||||
<span
|
<span
|
||||||
className="line-through"
|
className="line-through"
|
||||||
style={{
|
style={{
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export const ConfirmChanges = ({
|
|||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
{modifiedKeys.length > 0 ? (
|
{modifiedKeys.length > 0 ? (
|
||||||
<pre className="flex flex-col text-sm bg-zinc-100 gap-1 min-h-[64px] rounded-md p-[12px_16px_16px_12px] mt-2 overflow-hidden">
|
<pre className="flex flex-col text-sm bg-zinc-100 gap-1 min-h-[64px] rounded-md p-[12px_16px_16px_12px] mt-2 overflow-auto">
|
||||||
<p>{'{'}</p>
|
<p>{'{'}</p>
|
||||||
{modifiedKeys.map(key => (
|
{modifiedKeys.map(key => (
|
||||||
<p key={key}>
|
<p key={key}>
|
||||||
@@ -70,7 +70,7 @@ export const ConfirmChanges = ({
|
|||||||
) : (
|
) : (
|
||||||
'There is no change.'
|
'There is no change.'
|
||||||
)}
|
)}
|
||||||
<DialogFooter>
|
<DialogFooter className="mt-6">
|
||||||
<div className="flex justify-end items-center w-full gap-2">
|
<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>
|
||||||
|
|||||||
@@ -106,6 +106,9 @@ export const AdminPanel = ({
|
|||||||
key={field}
|
key={field}
|
||||||
id={field}
|
id={field}
|
||||||
description={desc}
|
description={desc}
|
||||||
|
orientation={
|
||||||
|
type === 'Boolean' ? 'horizontal' : 'vertical'
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<ConfigInput
|
<ConfigInput
|
||||||
module={module}
|
module={module}
|
||||||
|
|||||||
@@ -3,15 +3,17 @@ import { type ReactNode } from 'react';
|
|||||||
export const RuntimeSettingRow = ({
|
export const RuntimeSettingRow = ({
|
||||||
id,
|
id,
|
||||||
description,
|
description,
|
||||||
|
orientation = 'horizontal',
|
||||||
children,
|
children,
|
||||||
}: {
|
}: {
|
||||||
id: string;
|
id: string;
|
||||||
description: string;
|
description: string;
|
||||||
|
orientation?: 'horizontal' | 'vertical';
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="flex justify-between flex-grow overflow-y-auto space-y-[10px] gap-5 "
|
className={`flex justify-between flex-grow space-y-[10px] gap-5 ${orientation === 'vertical' ? 'flex-col' : 'flex-row'}`}
|
||||||
id={id}
|
id={id}
|
||||||
>
|
>
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
|
|||||||
Reference in New Issue
Block a user