Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
650 changes: 218 additions & 432 deletions web/package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@
"umi-request": "^1.4.0",
"unist-util-visit-parents": "^6.0.1",
"uuid": "^9.0.1",
"xlsx": "^0.18.5",
"zod": "^3.23.8",
"zustand": "^4.5.2"
},
Expand Down
96 changes: 52 additions & 44 deletions web/src/components/empty/empty.tsx
Original file line number Diff line number Diff line change
@@ -1,58 +1,66 @@
import { cn } from '@/lib/utils';
import { t } from 'i18next';
import { useIsDarkTheme } from '../theme-provider';

type EmptyProps = {
className?: string;
children?: React.ReactNode;
};

const EmptyIcon = () => (
<svg
width="184"
height="152"
viewBox="0 0 184 152"
xmlns="http://www.w3.org/2000/svg"
>
<title>{t('common.noData')}</title>
<g fill="none" fillRule="evenodd">
<g transform="translate(24 31.67)">
<ellipse
fillOpacity=".8"
fill="#F5F5F7"
cx="67.797"
cy="106.89"
rx="67.797"
ry="12.668"
></ellipse>
<path
d="M122.034 69.674L98.109 40.229c-1.148-1.386-2.826-2.225-4.593-2.225h-51.44c-1.766 0-3.444.839-4.592 2.225L13.56 69.674v15.383h108.475V69.674z"
fill="#AEB8C2"
></path>
<path
d="M101.537 86.214L80.63 61.102c-1.001-1.207-2.507-1.867-4.048-1.867H31.724c-1.54 0-3.047.66-4.048 1.867L6.769 86.214v13.792h94.768V86.214z"
fill="url(#linearGradient-1)"
transform="translate(13.56)"
></path>
<path
d="M33.83 0h67.933a4 4 0 0 1 4 4v93.344a4 4 0 0 1-4 4H33.83a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4z"
fill="#F5F5F7"
></path>
const EmptyIcon = () => {
const isDarkTheme = useIsDarkTheme();

return (
<svg
width="184"
height="152"
viewBox="0 0 184 152"
xmlns="http://www.w3.org/2000/svg"
>
<title>{t('common.noData')}</title>
<g fill="none" fillRule="evenodd">
<g transform="translate(24 31.67)">
<ellipse
fillOpacity=".8"
fill={isDarkTheme ? '#28282A' : '#F5F5F7'}
cx="67.797"
cy="106.89"
rx="67.797"
ry="12.668"
></ellipse>
<path
d="M122.034 69.674L98.109 40.229c-1.148-1.386-2.826-2.225-4.593-2.225h-51.44c-1.766 0-3.444.839-4.592 2.225L13.56 69.674v15.383h108.475V69.674z"
fill={isDarkTheme ? '#736960' : '#AEB8C2'}
></path>
<path
d="M101.537 86.214L80.63 61.102c-1.001-1.207-2.507-1.867-4.048-1.867H31.724c-1.54 0-3.047.66-4.048 1.867L6.769 86.214v13.792h94.768V86.214z"
fill="url(#linearGradient-1)"
transform="translate(13.56)"
></path>
<path
d="M33.83 0h67.933a4 4 0 0 1 4 4v93.344a4 4 0 0 1-4 4H33.83a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4z"
fill={isDarkTheme ? '#28282A' : '#F5F5F7'}
></path>
<path
d="M42.678 9.953h50.237a2 2 0 0 1 2 2V36.91a2 2 0 0 1-2 2H42.678a2 2 0 0 1-2-2V11.953a2 2 0 0 1 2-2zM42.94 49.767h49.713a2.262 2.262 0 1 1 0 4.524H42.94a2.262 2.262 0 0 1 0-4.524zM42.94 61.53h49.713a2.262 2.262 0 1 1 0 4.525H42.94a2.262 2.262 0 0 1 0-4.525zM121.813 105.032c-.775 3.071-3.497 5.36-6.735 5.36H20.515c-3.238 0-5.96-2.29-6.734-5.36a7.309 7.309 0 0 1-.222-1.79V69.675h26.318c2.907 0 5.25 2.448 5.25 5.42v.04c0 2.971 2.37 5.37 5.277 5.37h34.785c2.907 0 5.277-2.421 5.277-5.393V75.1c0-2.972 2.343-5.426 5.25-5.426h26.318v33.569c0 .617-.077 1.216-.221 1.789z"
fill={isDarkTheme ? '#45413A' : '#DCE0E6'}
></path>
</g>
<path
d="M42.678 9.953h50.237a2 2 0 0 1 2 2V36.91a2 2 0 0 1-2 2H42.678a2 2 0 0 1-2-2V11.953a2 2 0 0 1 2-2zM42.94 49.767h49.713a2.262 2.262 0 1 1 0 4.524H42.94a2.262 2.262 0 0 1 0-4.524zM42.94 61.53h49.713a2.262 2.262 0 1 1 0 4.525H42.94a2.262 2.262 0 0 1 0-4.525zM121.813 105.032c-.775 3.071-3.497 5.36-6.735 5.36H20.515c-3.238 0-5.96-2.29-6.734-5.36a7.309 7.309 0 0 1-.222-1.79V69.675h26.318c2.907 0 5.25 2.448 5.25 5.42v.04c0 2.971 2.37 5.37 5.277 5.37h34.785c2.907 0 5.277-2.421 5.277-5.393V75.1c0-2.972 2.343-5.426 5.25-5.426h26.318v33.569c0 .617-.077 1.216-.221 1.789z"
fill="#DCE0E6"
d="M149.121 33.292l-6.83 2.65a1 1 0 0 1-1.317-1.23l1.937-6.207c-2.589-2.944-4.109-6.534-4.109-10.408C138.802 8.102 148.92 0 161.402 0 173.881 0 184 8.102 184 18.097c0 9.995-10.118 18.097-22.599 18.097-4.528 0-8.744-1.066-12.28-2.902z"
fill={isDarkTheme ? '#45413A' : '#DCE0E6'}
></path>
<g
transform="translate(149.65 15.383)"
fill={isDarkTheme ? '#222' : '#FFF'}
>
<ellipse cx="20.654" cy="3.167" rx="2.849" ry="2.815"></ellipse>
<path d="M5.698 5.63H0L2.898.704zM9.259.704h4.985V5.63H9.259z"></path>
</g>
</g>
<path
d="M149.121 33.292l-6.83 2.65a1 1 0 0 1-1.317-1.23l1.937-6.207c-2.589-2.944-4.109-6.534-4.109-10.408C138.802 8.102 148.92 0 161.402 0 173.881 0 184 8.102 184 18.097c0 9.995-10.118 18.097-22.599 18.097-4.528 0-8.744-1.066-12.28-2.902z"
fill="#DCE0E6"
></path>
<g transform="translate(149.65 15.383)" fill="#FFF">
<ellipse cx="20.654" cy="3.167" rx="2.849" ry="2.815"></ellipse>
<path d="M5.698 5.63H0L2.898.704zM9.259.704h4.985V5.63H9.259z"></path>
</g>
</g>
</svg>
);
</svg>
);
};

const Empty = (props: EmptyProps) => {
const { className, children } = props;
Expand Down
6 changes: 6 additions & 0 deletions web/src/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1960,7 +1960,13 @@ Important structured information may include: names, dates, locations, events, k
newRole: 'New Role',
addNewRole: 'Add new role',
roleName: 'Role name',
roleNameRequired: 'Role name is required',
resources: 'Resources',

editRoleDescription: 'Edit role description',
deleteRole: 'Delete role',
deleteRoleConfirmation:
'Are you sure you want to delete this role? This action cannot be undone.',
},
},
};
2 changes: 1 addition & 1 deletion web/src/pages/admin/components/enterprise-feature.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { IS_ENTERPRISE } from '../utils';
export default function EnterpriseFeature({
children,
}: {
children: () => React.ReactNode;
children: React.ReactNode | (() => React.ReactNode);
}) {
return IS_ENTERPRISE
? typeof children === 'function'
Expand Down
2 changes: 1 addition & 1 deletion web/src/pages/admin/components/theme-switch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const ThemeSwitch = forwardRef<
return (
<Root
ref={ref}
className={cn('relative rounded-full')}
className={cn('relative rounded-full', className)}
{...props}
checked={isDark}
onCheckedChange={(value) =>
Expand Down
41 changes: 7 additions & 34 deletions web/src/pages/admin/forms/import-excel-form.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { Checkbox } from '@/components/ui/checkbox';
import {
Form,
FormControl,
Expand All @@ -14,8 +13,8 @@ import { useForm } from 'react-hook-form';
import { Trans, useTranslation } from 'react-i18next';
import { z } from 'zod';

interface ImportExcelFormData {
file: FileList;
export interface ImportExcelFormData {
file: File;
overwriteExisting: boolean;
}

Expand Down Expand Up @@ -43,6 +42,7 @@ export const ImportExcelForm = ({
<FormField
control={form.control}
name="file"
// eslint-disable-next-line @typescript-eslint/no-unused-vars
render={({ field: { onChange, value, ...field } }) => (
<FormItem>
<FormLabel className="text-sm font-medium">
Expand All @@ -56,7 +56,7 @@ export const ImportExcelForm = ({
className="mt-2 px-3 h-10 bg-bg-input border-border-button file:mr-4 file:py-2 file:px-4 file:rounded-full file:border-0 file:text-sm file:font-semibold file:bg-bg-accent file:text-text-primary hover:file:bg-bg-accent/80"
onChange={(e) => {
const files = e.target.files;
onChange(files);
onChange(files?.[0]);
}}
{...field}
/>
Expand All @@ -66,27 +66,7 @@ export const ImportExcelForm = ({
)}
/>

{/* Overwrite checkbox */}
<FormField
control={form.control}
name="overwriteExisting"
render={({ field }) => (
<FormItem>
<FormLabel className="flex items-center gap-2 text-sm font-medium">
<FormControl>
<Checkbox
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>

{t('admin.importOverwriteExistingEmails')}
</FormLabel>
</FormItem>
)}
/>

<p className="text-xs text-text-secondary">
<p className="text-sm text-text-secondary">
<Trans
i18nKey="admin.importFileTips"
components={{ code: <code /> }}
Expand All @@ -105,21 +85,14 @@ function useImportExcelForm() {
const schema = useMemo(() => {
return z.object({
file: z
.any()
.refine((files) => files && files.length > 0, {
message: t('admin.importFileRequired'),
})
.instanceof(File, { message: t('admin.importFileRequired') })
.refine(
(files) => {
if (!files || files.length === 0) return false;
const [file] = files;
(file) => {
return (
file.type ===
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ||
// || file.type === 'application/vnd.ms-excel'
file.name.endsWith('.xlsx')
);
// || file.name.endsWith('.xls');
},
{
message: t('admin.invalidExcelFile'),
Expand Down
65 changes: 36 additions & 29 deletions web/src/pages/admin/forms/role-form.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
import { useCallback, useId, useMemo } from 'react';
import { useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';

import { zodResolver } from '@hookform/resolvers/zod';
import { useQuery } from '@tanstack/react-query';
import { z } from 'zod';

import { Card, CardContent } from '@/components/ui/card';
import {
Form,
Expand All @@ -16,15 +24,11 @@ import {
TabsList,
TabsTrigger,
} from '@/components/ui/tabs-underlined';
import { AdminService, listResources } from '@/services/admin-service';
import { zodResolver } from '@hookform/resolvers/zod';
import { useQuery } from '@tanstack/react-query';
import { useCallback, useId, useMemo } from 'react';
import { useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { z } from 'zod';

interface CreateRoleFormData {
import { listResources } from '@/services/admin-service';
import { PERMISSION_TYPES, formMergeDefaultValues } from '../utils';

export interface CreateRoleFormData {
name: string;
description: string;
permissions: Record<string, AdminService.PermissionData>;
Expand All @@ -36,8 +40,6 @@ interface CreateRoleFormProps {
onSubmit?: (data: CreateRoleFormData) => void;
}

const PERMISSION_TYPES = ['enable', 'read', 'write', 'share'] as const;

export const CreateRoleForm = ({
id,
form,
Expand All @@ -48,6 +50,7 @@ export const CreateRoleForm = ({
const { data: resourceTypes } = useQuery({
queryKey: ['admin/resourceTypes'],
queryFn: async () => (await listResources()).data.data.resource_types,
retry: false,
});

return (
Expand Down Expand Up @@ -108,9 +111,9 @@ export const CreateRoleForm = ({
<TabsTrigger
key={resourceType}
value={resourceType}
className="text-text-secondary border-border-button dark:data-[state=active]:bg-bg-input"
className="text-text-secondary !border-border-button data-[state=active]:bg-bg-card data-[state=active]:text-text-primary"
>
{t(`admin.resourceType.${resourceType}`)}
{t(`admin.resourceType.${resourceType.toLowerCase()}`)}
</TabsTrigger>
))}
</TabsList>
Expand All @@ -121,16 +124,16 @@ export const CreateRoleForm = ({
value={resourceType}
className="space-y-4"
>
<Card className="border-0 bg-bg-card">
<Card className="border-0 bg-bg-card !shadow-none">
<CardContent className="p-6">
<div className="grid grid-cols-4 gap-4">
{PERMISSION_TYPES.map((permissionType) => (
<FormField
key={permissionType}
name={`permissions.${resourceType}.${permissionType}`}
render={({ field }) => (
<FormItem>
<FormLabel className="flex items-center gap-2">
<FormItem className="space-y-0 inline-flex items-center gap-2">
<FormLabel>
{t(`admin.permissionType.${permissionType}`)}
</FormLabel>
<FormControl>
Expand Down Expand Up @@ -158,42 +161,46 @@ export const CreateRoleForm = ({

// Export the form validation state for parent component
function useCreateRoleForm(props?: {
defaultValues: Partial<CreateRoleFormData>;
defaultValues:
| Partial<CreateRoleFormData>
| (() => Promise<CreateRoleFormData>);
}) {
const { t } = useTranslation();
const id = useId();

const schema = useMemo(() => {
return z.object({
name: z.string().min(1, { message: 'Role name is required' }),
name: z.string().min(1, { message: t('admin.roleNameRequired') }),
description: z.string().optional(),
permissions: z.record(
z.string(),
z.object({
enable: z.boolean(),
read: z.boolean(),
write: z.boolean(),
share: z.boolean(),
enable: z.boolean().optional(),
read: z.boolean().optional(),
write: z.boolean().optional(),
share: z.boolean().optional(),
}),
),
});
}, [t]);

const form = useForm<CreateRoleFormData>({
defaultValues: {
name: '',
description: '',
permissions: {},
...(props?.defaultValues ?? {}),
},
defaultValues: formMergeDefaultValues(
{
name: '',
description: '',
permissions: {},
},
props?.defaultValues,
),
resolver: zodResolver(schema),
});

const FormComponent = useCallback(
(props: Partial<CreateRoleFormProps>) => (
<CreateRoleForm id="create-role-form" form={form} {...props} />
<CreateRoleForm id={id} form={form} {...props} />
),
[form],
[id, form],
);

return {
Expand Down
4 changes: 2 additions & 2 deletions web/src/pages/admin/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Button } from '@/components/ui/button';
import message from '@/components/ui/message';
import { cn } from '@/lib/utils';
import { Routes } from '@/routes';
import adminService from '@/services/admin-service';
import { logout } from '@/services/admin-service';
import authorizationUtil from '@/utils/authorization-util';
import { useMutation } from '@tanstack/react-query';
import {
Expand Down Expand Up @@ -60,7 +60,7 @@ const AdminLayout = () => {
const logoutMutation = useMutation({
mutationKey: ['adminLogout'],
mutationFn: async () => {
await adminService.logout();
await logout();

message.success(t('message.logout'));
authorizationUtil.removeAll();
Expand Down
Loading