Files
AFFiNE/packages/frontend/admin/src/modules/settings/index.tsx
T
forehalo 2f139bd02c chore(admin): remove useless config diff (#12545)
<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Added a GraphQL mutation to validate multiple app configuration updates, returning detailed validation results for each item.
  - Extended the API schema to support validation feedback, enabling client-side checks before applying changes.
  - Introduced a detailed, parameterized error message system for configuration validation errors.
  - Enabled validation of configuration inputs via the admin UI with clear, descriptive error messages.

- **Improvements**
  - Enhanced error reporting with specific, context-rich messages for invalid app configurations.
  - Simplified admin settings UI by removing the confirmation dialog and streamlining save actions.
  - Improved clarity and maintainability of validation logic and error handling components.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-27 06:07:26 +00:00

120 lines
3.4 KiB
TypeScript

import { Button } from '@affine/admin/components/ui/button';
import { ScrollArea } from '@affine/admin/components/ui/scroll-area';
import { get } from 'lodash-es';
import { CheckIcon } from 'lucide-react';
import { useCallback } from 'react';
import { Header } from '../header';
import { useNav } from '../nav/context';
import {
ALL_CONFIG_DESCRIPTORS,
ALL_SETTING_GROUPS,
type AppConfig,
} from './config';
import { type ConfigInputProps, ConfigRow } from './config-input-row';
import { useAppConfig } from './use-app-config';
export function SettingsPage() {
const { appConfig, update, save, patchedAppConfig, updates } = useAppConfig();
const disableSave = Object.keys(updates).length === 0;
const saveChanges = useCallback(() => {
if (disableSave) {
return;
}
save();
}, [save, disableSave]);
return (
<div className="h-screen flex-1 flex-col flex">
<Header
title="Settings"
endFix={
<Button
type="submit"
size="icon"
className="w-7 h-7"
variant="ghost"
onClick={saveChanges}
disabled={disableSave}
>
<CheckIcon size={20} />
</Button>
}
/>
<AdminPanel
onUpdate={update}
appConfig={appConfig}
patchedAppConfig={patchedAppConfig}
/>
</div>
);
}
const AdminPanel = ({
appConfig,
patchedAppConfig,
onUpdate,
}: {
appConfig: AppConfig;
patchedAppConfig: AppConfig;
onUpdate: (path: string, value: any) => void;
}) => {
const { currentModule } = useNav();
const group = ALL_SETTING_GROUPS.find(
group => group.module === currentModule
);
if (!group) {
return null;
}
const { name, module, fields, operations } = group;
return (
<ScrollArea className="h-full">
<div className="flex flex-col h-full gap-5 py-5 px-6 w-full max-w-[800px] mx-auto">
<div className="text-2xl font-semibold">{name}</div>
<div className="flex flex-col gap-10" id={`config-module-${module}`}>
{fields.map(field => {
let desc: string;
let props: ConfigInputProps;
if (typeof field === 'string') {
const descriptor = ALL_CONFIG_DESCRIPTORS[module][field];
desc = descriptor.desc;
props = {
field: `${module}/${field}`,
desc,
type: descriptor.type,
options: [],
defaultValue: get(appConfig[module], field),
onChange: onUpdate,
};
} else {
const descriptor = ALL_CONFIG_DESCRIPTORS[module][field.key];
props = {
field: `${module}/${field.key}${field.sub ? `/${field.sub}` : ''}`,
desc: field.desc ?? descriptor.desc,
type: field.type ?? descriptor.type,
// @ts-expect-error for enum type
options: field.options,
defaultValue: get(
appConfig[module],
field.key + (field.sub ? '.' + field.sub : '')
),
onChange: onUpdate,
};
}
return <ConfigRow key={props.field} {...props} />;
})}
{operations?.map(Operation => (
<Operation key={Operation.name} appConfig={patchedAppConfig} />
))}
</div>
</div>
</ScrollArea>
);
};