;
+ t: TFunction<'translation', undefined, 'translation'>;
}
-
export const DataLimitField = ({ form, disabled, t }: DataLimitFieldProps) => {
- return (
-
- {t("userDialog.dataLimit")}
- {
- return (
-
- );
- }}
+ return (
+
+ {t('userDialog.dataLimit')}
+ {
+ return (
+
-
- )
-}
+ );
+ }}
+ />
+
+ )
+}
\ No newline at end of file
diff --git a/app/dashboard/src/components/UserDialog/DefaultValues.ts b/app/dashboard/src/components/UserDialog/DefaultValues.ts
index c9e83b7..c86ca51 100644
--- a/app/dashboard/src/components/UserDialog/DefaultValues.ts
+++ b/app/dashboard/src/components/UserDialog/DefaultValues.ts
@@ -1,15 +1,15 @@
-import { FormType } from "./FormSchema";
+import { FormType } from './FormSchema';
export const getDefaultValues = (): FormType => {
- return {
- services: [],
- data_limit: 0,
- expire: null,
- username: "",
- data_limit_reset_strategy: "no_reset",
- status: "active",
- note: "",
- };
+ return {
+ services: [],
+ data_limit: 0,
+ expire: null,
+ username: '',
+ data_limit_reset_strategy: 'no_reset',
+ status: 'active',
+ note: '',
+ };
};
diff --git a/app/dashboard/src/components/UserDialog/ExpireDateField.tsx b/app/dashboard/src/components/UserDialog/ExpireDateField.tsx
index 69bebf7..eca499c 100644
--- a/app/dashboard/src/components/UserDialog/ExpireDateField.tsx
+++ b/app/dashboard/src/components/UserDialog/ExpireDateField.tsx
@@ -1,91 +1,91 @@
import {
- FormControl,
- FormHelperText,
- FormLabel,
-} from "@chakra-ui/react";
-import dayjs from "dayjs";
-import { i18n, TFunction } from "i18next";
-import ReactDatePicker from "react-datepicker";
-import { Controller } from "react-hook-form";
-import { relativeExpiryDate } from "utils/dateFormatter";
-import { Input } from "../Input";
+ FormControl,
+ FormHelperText,
+ FormLabel,
+} from '@chakra-ui/react';
+import dayjs from 'dayjs';
+import { i18n, TFunction } from 'i18next';
+import ReactDatePicker from 'react-datepicker';
+import { Controller } from 'react-hook-form';
+import { relativeExpiryDate } from 'utils/dateFormatter';
+import { Input } from '../Input';
interface ExpireDateFieldProps {
form: any;
disabled: boolean;
- t: TFunction<"translation", undefined, "translation">;
+ t: TFunction<'translation', undefined, 'translation'>;
i18n: i18n;
}
export const ExpireDateField = ({ form, t, i18n, disabled }: ExpireDateFieldProps) => {
- return (
-
- {t("userDialog.expiryDate")}
- {
- function createDateAsUTC(num: number) {
- return dayjs(
- dayjs(num * 1000).utc()
- // .format("MMMM D, YYYY") // exception with: dayjs.locale(lng);
- ).toDate();
- }
- const { status, time } = relativeExpiryDate(
- field.value
- );
- return (
- <>
- {
- field.onChange({
- target: {
- value: date
- ? dayjs(
- dayjs(date)
- .set("hour", 23)
- .set("minute", 59)
- .set("second", 59)
- )
- .utc()
- .valueOf() / 1000
- : 0,
- name: "expire",
- },
- });
- }}
- customInput={
-
- }
- />
- {field.value ? (
-
- {t(status, { time: time })}
-
- ) : (
- ""
- )}
- >
- );
+ return (
+
+ {t('userDialog.expiryDate')}
+ {
+ function createDateAsUTC(num: number) {
+ return dayjs(
+ dayjs(num * 1000).utc()
+ // .format("MMMM D, YYYY") // exception with: dayjs.locale(lng);
+ ).toDate();
+ }
+ const { status, time } = relativeExpiryDate(
+ field.value
+ );
+ return (
+ <>
+ {
+ field.onChange({
+ target: {
+ value: date
+ ? dayjs(
+ dayjs(date)
+ .set('hour', 23)
+ .set('minute', 59)
+ .set('second', 59)
+ )
+ .utc()
+ .valueOf() / 1000
+ : 0,
+ name: 'expire',
+ },
+ });
}}
- />
-
- )
+ customInput={
+
+ }
+ />
+ {field.value ? (
+
+ {t(status, { time: time })}
+
+ ) : (
+ ''
+ )}
+ >
+ );
+ }}
+ />
+
+ )
}
diff --git a/app/dashboard/src/components/UserDialog/FormSchema.ts b/app/dashboard/src/components/UserDialog/FormSchema.ts
index 5925567..868b3fd 100644
--- a/app/dashboard/src/components/UserDialog/FormSchema.ts
+++ b/app/dashboard/src/components/UserDialog/FormSchema.ts
@@ -1,22 +1,22 @@
-import { z } from "zod";
-import { UserCreate } from "types/User";
+import { z } from 'zod';
+import { UserCreate } from 'types/User';
export type FormType = Pick
;
export const schema = z.object({
- username: z.string().min(1, { message: "Username is required" }),
- note: z.string().nullable(),
- data_limit: z
- .string()
- .min(0, "The minimum number is 0")
- .or(z.number())
- .nullable()
- .transform((str) => {
- if (str) return Number((parseFloat(String(str)) * 1073741824).toFixed(5));
- return 0;
- }),
- expire: z.number().nullable(),
- data_limit_reset_strategy: z.string(),
- status: z.string(),
- services: z.array(z.number()).nonempty({ message: "At least one service is required" }),
+ username: z.string().min(1, { message: 'Username is required' }),
+ note: z.string().nullable(),
+ data_limit: z
+ .string()
+ .min(0, 'The minimum number is 0')
+ .or(z.number())
+ .nullable()
+ .transform((str) => {
+ if (str) return Number((parseFloat(String(str)) * 1073741824).toFixed(5));
+ return 0;
+ }),
+ expire: z.number().nullable(),
+ data_limit_reset_strategy: z.string(),
+ status: z.string(),
+ services: z.array(z.number()).nonempty({ message: 'At least one service is required' }),
});
diff --git a/app/dashboard/src/components/UserDialog/ModalFooter.tsx b/app/dashboard/src/components/UserDialog/ModalFooter.tsx
index 15703bb..19627d8 100644
--- a/app/dashboard/src/components/UserDialog/ModalFooter.tsx
+++ b/app/dashboard/src/components/UserDialog/ModalFooter.tsx
@@ -1,107 +1,107 @@
import React from 'react';
import {
- Button,
- Tooltip,
- HStack,
- IconButton,
- Spinner,
-} from "@chakra-ui/react";
-import { DeleteIcon } from "../DeleteUserModal";
-import { UserUsageIcon } from "./UserDialogIcons";
+ Button,
+ Tooltip,
+ HStack,
+ IconButton,
+ Spinner,
+} from '@chakra-ui/react';
+import { DeleteIcon } from '../DeleteUserModal';
+import { UserUsageIcon } from './UserDialogIcons';
import { useTranslation } from 'react-i18next';
import { User } from 'types/User';
import { ModalFooter } from '@chakra-ui/react';
interface UserDialogFooterProps {
- isEditing: boolean;
- onDeletingUser: (user: User) => void;
- handleUsageToggle: () => void;
- handleResetUsage: () => void;
- handleRevokeSubscription: () => void;
- loading: boolean;
- editingUser: User | null;
- onClose: () => void;
+ isEditing: boolean;
+ onDeletingUser: (user: User) => void;
+ handleUsageToggle: () => void;
+ handleResetUsage: () => void;
+ handleRevokeSubscription: () => void;
+ loading: boolean;
+ editingUser: User | null;
+ onClose: () => void;
}
export const UserDialogModalFooter: React.FC = ({
- isEditing,
- onClose, editingUser,
- onDeletingUser,
- handleUsageToggle,
- handleResetUsage,
- handleRevokeSubscription,
- loading,
+ isEditing,
+ onClose, editingUser,
+ onDeletingUser,
+ handleUsageToggle,
+ handleResetUsage,
+ handleRevokeSubscription,
+ loading,
}) => {
- const { t, i18n } = useTranslation();
- return (
+ const { t } = useTranslation();
+ return (
-
-
-
+
+
+ {isEditing && editingUser !== null && (
+ <>
+
+ {
+ onDeletingUser(editingUser);
+ onClose();
+ }}
>
- {isEditing && editingUser !== null && (
- <>
-
- {
- onDeletingUser(editingUser);
- onClose();
- }}
- >
-
-
-
-
-
-
-
-
-
-
- >
- )}
-
-
+
+
+
+
- : undefined}
- disabled={false}
- >
- {isEditing ? t("userDialog.editUser") : t("createUser")}
-
-
-
-
- );
+
+
+
+
+
+ >
+ )}
+
+
+ : undefined}
+ disabled={false}
+ >
+ {isEditing ? t('userDialog.editUser') : t('createUser')}
+
+
+
+
+ );
};
diff --git a/app/dashboard/src/components/UserDialog/ModalHeader.tsx b/app/dashboard/src/components/UserDialog/ModalHeader.tsx
index 4973ae1..ef6bb65 100644
--- a/app/dashboard/src/components/UserDialog/ModalHeader.tsx
+++ b/app/dashboard/src/components/UserDialog/ModalHeader.tsx
@@ -1,19 +1,19 @@
-import { Text, HStack, ModalHeader } from "@chakra-ui/react";
-import { ChakraIcon } from "types/ChakraIcon";
-import { Icon } from "../Icon";
+import { Text, HStack, ModalHeader } from '@chakra-ui/react';
+import { ChakraIcon } from 'types/ChakraIcon';
+import { Icon } from '../Icon';
export const UserDialogModalHeader = ({ HeaderIcon, title }: { HeaderIcon: ChakraIcon, title: string }) => {
- return (
-
-
-
- {HeaderIcon && }
-
-
- {title}
-
-
-
- );
+ return (
+
+
+
+ {HeaderIcon && }
+
+
+ {title}
+
+
+
+ );
}
diff --git a/app/dashboard/src/components/UserDialog/NoteField.tsx b/app/dashboard/src/components/UserDialog/NoteField.tsx
index 39a7544..618a0f6 100644
--- a/app/dashboard/src/components/UserDialog/NoteField.tsx
+++ b/app/dashboard/src/components/UserDialog/NoteField.tsx
@@ -3,20 +3,20 @@ import { TFunction } from 'i18next';
interface NoteFieldProps {
form: any;
- t: TFunction<"translation", undefined, "translation">;
+ t: TFunction<'translation', undefined, 'translation'>;
}
export const NoteField = ({ t, form }: NoteFieldProps) => {
- return (
-
- {t("userDialog.note")}
-
-
- {form.formState.errors?.note?.message}
-
-
- )
+ return (
+
+ {t('userDialog.note')}
+
+
+ {form.formState.errors?.note?.message}
+
+
+ )
}
diff --git a/app/dashboard/src/components/UserDialog/PeriodicUsageReset.tsx b/app/dashboard/src/components/UserDialog/PeriodicUsageReset.tsx
index d476b47..73bbb4b 100644
--- a/app/dashboard/src/components/UserDialog/PeriodicUsageReset.tsx
+++ b/app/dashboard/src/components/UserDialog/PeriodicUsageReset.tsx
@@ -1,40 +1,39 @@
import { FormControl, FormLabel, Select } from '@chakra-ui/react';
-import { Controller } from "react-hook-form";
-import { resetStrategy } from "constants/UserSettings";
+import { Controller } from 'react-hook-form';
+import { resetStrategy } from 'constants/UserSettings';
import { TFunction } from 'i18next';
interface PreiodicUsageResetProps {
form: any;
- t: TFunction<"translation", undefined, "translation">;
+ t: TFunction<'translation', undefined, 'translation'>;
}
export const PeriodicUsageReset = ({ form, t }: PreiodicUsageResetProps) => {
- return (
-
-
-
- {t("userDialog.periodicUsageReset")}
-
- {
- return (
-
- );
- }}
- />
-
- )
+ return (
+
+
+ {t('userDialog.periodicUsageReset')}
+
+ {
+ return (
+
+ );
+ }}
+ />
+
+ )
}
diff --git a/app/dashboard/src/components/UserDialog/ServicePropertyStatus.tsx b/app/dashboard/src/components/UserDialog/ServicePropertyStatus.tsx
index c753409..e1651fb 100644
--- a/app/dashboard/src/components/UserDialog/ServicePropertyStatus.tsx
+++ b/app/dashboard/src/components/UserDialog/ServicePropertyStatus.tsx
@@ -1,5 +1,5 @@
-import { Flex } from "@chakra-ui/react";
-import { FC, useState } from "react";
+import { Flex } from '@chakra-ui/react';
+import { FC, useState } from 'react';
import { ChakraIcon } from 'types/ChakraIcon';
@@ -9,11 +9,12 @@ type ServicePropertyStatusProps = {
}
export const ServicePropertyStatus: FC = ({ value, StatusIcon }) => {
- const [valueState, _] = useState(value);
- return (
-
-
- {valueState}
-
- );
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ const [valueState, _] = useState(value);
+ return (
+
+
+ {valueState}
+
+ );
};
diff --git a/app/dashboard/src/components/UserDialog/ServicesField.tsx b/app/dashboard/src/components/UserDialog/ServicesField.tsx
index 2159259..31b127b 100644
--- a/app/dashboard/src/components/UserDialog/ServicesField.tsx
+++ b/app/dashboard/src/components/UserDialog/ServicesField.tsx
@@ -5,41 +5,39 @@ import { Service } from 'types/Service';
import { ServicesSelectList } from './ServicesSelectList';
interface ServicesFieldProps {
- form: any;
- services: Service[];
- disabled: boolean;
- t: TFunction<"translation", undefined, "translation">;
+ form: any;
+ services: Service[];
+ t: TFunction<'translation', undefined, 'translation'>;
}
-export const ServicesField = ({ form, disabled, t, services }: ServicesFieldProps) => {
- return services.length === 0 ? (
-
- ) : (
-
- {t("userDialog.services")}
- {
- return (
-
- );
- }}
+export const ServicesField = ({ form, t, services }: ServicesFieldProps) => {
+ return services.length === 0 ? (
+
+ ) : (
+
+ {t('userDialog.services')}
+ {
+ return (
+
-
- {t(
- form.formState.errors.services
- ?.message as string
- )}
-
-
- );
+ );
+ }}
+ />
+
+ {t(
+ form.formState.errors.services
+ ?.message as string
+ )}
+
+
+ );
}
diff --git a/app/dashboard/src/components/UserDialog/ServicesSelectList.tsx b/app/dashboard/src/components/UserDialog/ServicesSelectList.tsx
index e287d8e..22e4d08 100644
--- a/app/dashboard/src/components/UserDialog/ServicesSelectList.tsx
+++ b/app/dashboard/src/components/UserDialog/ServicesSelectList.tsx
@@ -1,28 +1,28 @@
import {
- Box,
- chakra,
- Flex,
- Spacer,
- Stack,
- Text,
-} from "@chakra-ui/react";
-import { ServerStackIcon, UsersIcon } from "@heroicons/react/24/outline";
-import { FC, forwardRef, Ref, useState } from "react";
+ Box,
+ chakra,
+ Flex,
+ Spacer,
+ Stack,
+ Text,
+} from '@chakra-ui/react';
+import { ServerStackIcon, UsersIcon } from '@heroicons/react/24/outline';
+import { forwardRef, Ref } from 'react';
import {
- useFormContext,
-} from "react-hook-form";
-import { Service } from "types/Service";
+ useFormContext,
+} from 'react-hook-form';
+import { Service } from 'types/Service';
import { ServicePropertyStatus } from './ServicePropertyStatus';
const ServiceIconBaseStyle = {
- baseStyle: {
- strokeWidth: "2px",
- color: "primary.500",
- w: 5,
- h: 5,
- mx: "6px",
- },
+ baseStyle: {
+ strokeWidth: '2px',
+ color: 'primary.500',
+ w: 5,
+ h: 5,
+ mx: '6px',
+ },
}
const UsersServiceIcon = chakra(UsersIcon, ServiceIconBaseStyle);
@@ -30,85 +30,85 @@ const UsersServiceIcon = chakra(UsersIcon, ServiceIconBaseStyle);
const InboundsServiceIcon = chakra(ServerStackIcon, ServiceIconBaseStyle);
type CustomCheckboxProps = {
- isChecked: boolean;
- onChange: () => void;
- service: Service;
- value: string;
+ isChecked: boolean;
+ onChange: () => void;
+ service: Service;
+ value: string;
}
const CustomCheckbox = forwardRef(
- ({ isChecked, onChange, service, ...props }: CustomCheckboxProps, ref: Ref) => (
-
-
- {service.name}
-
-
-
-
- /
-
-
-
- )
+ ({ isChecked, onChange, service, ...props }: CustomCheckboxProps, ref: Ref) => (
+
+
+ {service.name}
+
+
+
+
+ /
+
+
+
+ )
);
interface ServicesSelectListProps {
- list: Service[];
+ list: Service[];
}
export const ServicesSelectList = forwardRef(
- function({ list, ...props }, ref) {
- const form = useFormContext();
+ function({ list }, ref) {
+ const form = useFormContext();
- const handleServiceToggle = (serviceId: number) => {
- const currentServices = form.getValues('services');
- const isSelected = currentServices.includes(serviceId);
+ const handleServiceToggle = (serviceId: number) => {
+ const currentServices = form.getValues('services');
+ const isSelected = currentServices.includes(serviceId);
- let updatedServices;
+ let updatedServices;
- if (isSelected) {
- updatedServices = currentServices.filter((id: number) => id !== serviceId);
- } else {
- updatedServices = [...currentServices, serviceId];
- }
+ if (isSelected) {
+ updatedServices = currentServices.filter((id: number) => id !== serviceId);
+ } else {
+ updatedServices = [...currentServices, serviceId];
+ }
- form.setValue('services', updatedServices, { shouldValidate: true });
- };
+ form.setValue('services', updatedServices, { shouldValidate: true });
+ };
- return (
-
- {list.map((service: Service) => (
-
-
- handleServiceToggle(service.id)}
- service={service}
- value={String(service.id)}
- />
-
-
- ))}
-
- );
- });
+ return (
+
+ {list.map((service: Service) => (
+
+
+ handleServiceToggle(service.id)}
+ service={service}
+ value={String(service.id)}
+ />
+
+
+ ))}
+
+ );
+ });
diff --git a/app/dashboard/src/components/UserDialog/UserDialogIcons.ts b/app/dashboard/src/components/UserDialog/UserDialogIcons.ts
index e01db84..0bb1dec 100644
--- a/app/dashboard/src/components/UserDialog/UserDialogIcons.ts
+++ b/app/dashboard/src/components/UserDialog/UserDialogIcons.ts
@@ -1,28 +1,28 @@
-import { chakra } from "@chakra-ui/react";
+import { chakra } from '@chakra-ui/react';
import {
- ChartPieIcon,
- PencilIcon,
- UserPlusIcon,
-} from "@heroicons/react/24/outline";
+ ChartPieIcon,
+ PencilIcon,
+ UserPlusIcon,
+} from '@heroicons/react/24/outline';
export const AddUserIcon = chakra(UserPlusIcon, {
- baseStyle: {
- w: 5,
- h: 5,
- },
+ baseStyle: {
+ w: 5,
+ h: 5,
+ },
});
export const EditUserIcon = chakra(PencilIcon, {
- baseStyle: {
- w: 5,
- h: 5,
- },
+ baseStyle: {
+ w: 5,
+ h: 5,
+ },
});
export const UserUsageIcon = chakra(ChartPieIcon, {
- baseStyle: {
- w: 5,
- h: 5,
- },
+ baseStyle: {
+ w: 5,
+ h: 5,
+ },
});
diff --git a/app/dashboard/src/components/UserDialog/UsernameField.tsx b/app/dashboard/src/components/UserDialog/UsernameField.tsx
index 60df887..05007ca 100644
--- a/app/dashboard/src/components/UserDialog/UsernameField.tsx
+++ b/app/dashboard/src/components/UserDialog/UsernameField.tsx
@@ -1,63 +1,63 @@
import { HStack, Input, Tooltip, Box, Switch, FormControl, FormLabel } from '@chakra-ui/react';
import { TFunction } from 'i18next';
-import { Controller } from "react-hook-form";
+import { Controller } from 'react-hook-form';
interface UsernameFieldProps {
form: any;
isEditing: boolean;
- t: TFunction<"translation", undefined, "translation">;
+ t: TFunction<'translation', undefined, 'translation'>;
disabled: boolean;
}
export const UsernameField = ({ form, isEditing, t, disabled }: UsernameFieldProps) => {
- return (
-
- {t("username")}
-
-
- {isEditing && (
-
- {
- return (
-
-
- {
- if (e.target.checked) {
- field.onChange("active");
- } else {
- field.onChange("disabled");
- }
- }}
- />
-
-
- );
- }}
- />
-
- )}
-
-
- )
+ return (
+
+ {t('username')}
+
+
+ {isEditing && (
+
+ {
+ return (
+
+
+ {
+ if (e.target.checked) {
+ field.onChange('active');
+ } else {
+ field.onChange('disabled');
+ }
+ }}
+ />
+
+
+ );
+ }}
+ />
+
+ )}
+
+
+ )
}
diff --git a/app/dashboard/src/components/UserDialog/index.tsx b/app/dashboard/src/components/UserDialog/index.tsx
index 4193477..decc676 100644
--- a/app/dashboard/src/components/UserDialog/index.tsx
+++ b/app/dashboard/src/components/UserDialog/index.tsx
@@ -1,307 +1,306 @@
import {
- Alert,
- AlertIcon,
- Box,
- Collapse,
- Flex,
- Grid,
- GridItem,
- ModalBody,
- ModalCloseButton,
- ModalContent,
- Modal,
- VStack,
- useColorMode,
- useToast,
- ModalOverlay,
-} from "@chakra-ui/react";
-import { zodResolver } from "@hookform/resolvers/zod";
-import { FilterUsageType, useDashboard } from "contexts/DashboardContext";
-import dayjs from "dayjs";
-import { FC, useEffect, useState } from "react";
-import ReactApexChart from "react-apexcharts";
-import { FormProvider, useForm, useWatch } from "react-hook-form";
-import { useTranslation } from "react-i18next";
+ Alert,
+ AlertIcon,
+ Box,
+ Collapse,
+ Flex,
+ Grid,
+ GridItem,
+ ModalBody,
+ ModalCloseButton,
+ ModalContent,
+ Modal,
+ VStack,
+ useColorMode,
+ useToast,
+ ModalOverlay,
+} from '@chakra-ui/react';
+import { zodResolver } from '@hookform/resolvers/zod';
+import { FilterUsageType, useDashboard } from 'contexts/DashboardContext';
+import dayjs from 'dayjs';
+import { FC, useEffect, useState } from 'react';
+import ReactApexChart from 'react-apexcharts';
+import { FormProvider, useForm, useWatch } from 'react-hook-form';
+import { useTranslation } from 'react-i18next';
import {
- User,
- UserCreate,
-} from "types/User";
-import { UsageFilter, createUsageConfig } from "../UsageFilter";
-import { AddUserIcon, EditUserIcon } from "./UserDialogIcons";
-import { UserDialogModalHeader } from "./ModalHeader";
-import { UserDialogModalFooter } from "./ModalFooter";
-import { getDefaultValues } from "./DefaultValues";
-import { schema, FormType } from "./FormSchema";
-import { ServicesField } from "./ServicesField";
-import { UsernameField } from "./UsernameField";
-import { DataLimitField } from "./DataLimitField";
-import { PeriodicUsageReset as PeriodicUsageReset } from "./PeriodicUsageReset";
-import { ExpireDateField } from "./ExpireDateField";
-import { NoteField } from "./NoteField";
-import { Service } from "types/Service";
+ User,
+ UserCreate,
+} from 'types/User';
+import { UsageFilter, createUsageConfig } from '../UsageFilter';
+import { AddUserIcon, EditUserIcon } from './UserDialogIcons';
+import { UserDialogModalHeader } from './ModalHeader';
+import { UserDialogModalFooter } from './ModalFooter';
+import { getDefaultValues } from './DefaultValues';
+import { schema, FormType } from './FormSchema';
+import { ServicesField } from './ServicesField';
+import { UsernameField } from './UsernameField';
+import { DataLimitField } from './DataLimitField';
+import { PeriodicUsageReset as PeriodicUsageReset } from './PeriodicUsageReset';
+import { ExpireDateField } from './ExpireDateField';
+import { NoteField } from './NoteField';
+import { Service } from 'types/Service';
const formatUser = (user: User): FormType => {
- const services: number[] = user.services.map((service: number | Service): number => {
- return (typeof service !== 'number') ? service.id : service;
- });
- return {
- ...user,
- services,
- data_limit: user.data_limit
- ? Number((user.data_limit / 1073741824).toFixed(5))
- : user.data_limit,
- };
+ const services: number[] = user.services.map((service: number | Service): number => {
+ return (typeof service !== 'number') ? service.id : service;
+ });
+ return {
+ ...user,
+ services,
+ data_limit: user.data_limit
+ ? Number((user.data_limit / 1073741824).toFixed(5))
+ : user.data_limit,
+ };
};
export type UserDialogProps = {};
export const UserDialog: FC = () => {
- const {
- editingUser,
- isCreatingNewUser,
- services,
- onCreateUser,
- editUser,
- fetchUserUsage,
- refetchServices,
- onEditingUser,
- createUser,
- onDeletingUser,
- } = useDashboard();
- const isEditing = !!editingUser;
- const isOpen = isCreatingNewUser || isEditing;
- const [loading, setLoading] = useState(false);
- const [error, setError] = useState("");
- const toast = useToast();
- const { t, i18n } = useTranslation();
+ const {
+ editingUser,
+ isCreatingNewUser,
+ services,
+ onCreateUser,
+ editUser,
+ fetchUserUsage,
+ refetchServices,
+ onEditingUser,
+ createUser,
+ onDeletingUser,
+ } = useDashboard();
+ const isEditing = !!editingUser;
+ const isOpen = isCreatingNewUser || isEditing;
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState('');
+ const toast = useToast();
+ const { t, i18n } = useTranslation();
- const { colorMode } = useColorMode();
+ const { colorMode } = useColorMode();
- const [usageVisible, setUsageVisible] = useState(false);
- const handleUsageToggle = () => {
- setUsageVisible((current) => !current);
- };
-
- const form = useForm({
- defaultValues: getDefaultValues(),
- resolver: zodResolver(schema),
- });
+ const [usageVisible, setUsageVisible] = useState(false);
+ const handleUsageToggle = () => {
+ setUsageVisible((current) => !current);
+ };
- const [dataLimit] = useWatch({
- control: form.control,
- name: ["data_limit"],
- });
-
- const usageTitle = t("userDialog.total");
- const [usage, setUsage] = useState(createUsageConfig(colorMode, usageTitle));
- const [usageFilter, setUsageFilter] = useState("1m");
- const fetchUsageWithFilter = (query: FilterUsageType) => {
- fetchUserUsage(editingUser!, query).then((data: any) => {
- const labels = [];
- const series = [];
- for (const key in data.usages) {
- series.push(data.usages[key].used_traffic);
- labels.push(data.usages[key].node_name);
- }
- setUsage(createUsageConfig(colorMode, usageTitle, series, labels));
- });
- };
+ const form = useForm({
+ defaultValues: getDefaultValues(),
+ resolver: zodResolver(schema),
+ });
- useEffect(() => {
- if (isOpen) {
- refetchServices();
- }
- }, [isOpen]);
+ const [dataLimit] = useWatch({
+ control: form.control,
+ name: ['data_limit'],
+ });
+ const usageTitle = t('userDialog.total');
+ const [usage, setUsage] = useState(createUsageConfig(colorMode, usageTitle));
+ const [usageFilter, setUsageFilter] = useState('1m');
+ const fetchUsageWithFilter = (query: FilterUsageType) => {
+ fetchUserUsage(editingUser!, query).then((data: any) => {
+ const labels = [];
+ const series = [];
+ for (const key in data.usages) {
+ series.push(data.usages[key].used_traffic);
+ labels.push(data.usages[key].node_name);
+ }
+ setUsage(createUsageConfig(colorMode, usageTitle, series, labels));
+ });
+ };
- useEffect(() => {
- if (editingUser) {
- form.reset(formatUser(editingUser));
+ useEffect(() => {
+ if (isOpen) {
+ refetchServices();
+ }
+ }, [isOpen]);
- fetchUsageWithFilter({
- start: dayjs().utc().subtract(30, "day").format("YYYY-MM-DDTHH:00:00"),
- });
- }
- }, [editingUser]);
- const submit = async (values: FormType) => {
- setLoading(true);
- const methods = { edited: editUser, created: createUser };
- const method = isEditing ? "edited" : "created";
- setError(null);
- const { services, username, ...rest } = values;
+ useEffect(() => {
+ if (editingUser) {
+ form.reset(formatUser(editingUser));
- let body: UserCreate = {
- ...rest,
- username,
- services,
- data_limit: values.data_limit,
- data_limit_reset_strategy:
- values.data_limit && values.data_limit > 0
- ? values.data_limit_reset_strategy
- : "no_reset",
- status:
- values.status === "active" || values.status === "disabled"
- ? values.status
- : "active",
- };
+ fetchUsageWithFilter({
+ start: dayjs().utc().subtract(30, 'day').format('YYYY-MM-DDTHH:00:00'),
+ });
+ }
+ }, [editingUser]);
- await methods[method](body)
- .then(() => {
- toast({
- title: t(
- isEditing ? "userDialog.userEdited" : "userDialog.userCreated",
- { username: values.username }
- ),
- status: "success",
- isClosable: true,
- position: "top",
- duration: 3000,
- });
- onClose();
- })
- .catch((err) => {
- if (err?.response?.status === 409 || err?.response?.status === 400)
- setError(err?.response?._data?.detail);
- if (err?.response?.status === 422) {
- Object.keys(err.response._data.detail).forEach((key) => {
- setError(err?.response._data.detail[key] as string);
- form.setError(
- key as "services" | "username" | "data_limit" | "expire",
- {
- type: "custom",
- message: err.response._data.detail[key],
- }
- );
- });
- }
- })
- .finally(() => {
- setLoading(false);
- });
- };
+ const submit = async (values: FormType) => {
+ setLoading(true);
+ const methods = { edited: editUser, created: createUser };
+ const method = isEditing ? 'edited' : 'created';
+ setError(null);
+ const { services, username, ...rest } = values;
- const onClose = () => {
- form.reset(getDefaultValues());
- onCreateUser(false);
- onEditingUser(null);
- setError(null);
- setUsageVisible(false);
- setUsageFilter("1m");
+ let body: UserCreate = {
+ ...rest,
+ username,
+ services,
+ data_limit: values.data_limit,
+ data_limit_reset_strategy:
+ values.data_limit && values.data_limit > 0
+ ? values.data_limit_reset_strategy
+ : 'no_reset',
+ status:
+ values.status === 'active' || values.status === 'disabled'
+ ? values.status
+ : 'active',
};
- const handleResetUsage = () => {
- useDashboard.setState({ resetUsageUser: editingUser });
- };
+ await methods[method](body)
+ .then(() => {
+ toast({
+ title: t(
+ isEditing ? 'userDialog.userEdited' : 'userDialog.userCreated',
+ { username: values.username }
+ ),
+ status: 'success',
+ isClosable: true,
+ position: 'top',
+ duration: 3000,
+ });
+ onClose();
+ })
+ .catch((err) => {
+ if (err?.response?.status === 409 || err?.response?.status === 400)
+ setError(err?.response?._data?.detail);
+ if (err?.response?.status === 422) {
+ Object.keys(err.response._data.detail).forEach((key) => {
+ setError(err?.response._data.detail[key] as string);
+ form.setError(
+ key as 'services' | 'username' | 'data_limit' | 'expire',
+ {
+ type: 'custom',
+ message: err.response._data.detail[key],
+ }
+ );
+ });
+ }
+ })
+ .finally(() => {
+ setLoading(false);
+ });
+ };
- const handleRevokeSubscription = () => {
- useDashboard.setState({ revokeSubscriptionUser: editingUser });
- };
+ const onClose = () => {
+ form.reset(getDefaultValues());
+ onCreateUser(false);
+ onEditingUser(null);
+ setError(null);
+ setUsageVisible(false);
+ setUsageFilter('1m');
+ };
- const disabled = loading;
+ const handleResetUsage = () => {
+ useDashboard.setState({ resetUsageUser: editingUser });
+ };
- return (
-
-
-
-
-
+
+
+
+ );
};
diff --git a/app/dashboard/src/components/UsersTable.tsx b/app/dashboard/src/components/UsersTable.tsx
index 7749272..38a8700 100644
--- a/app/dashboard/src/components/UsersTable.tsx
+++ b/app/dashboard/src/components/UsersTable.tsx
@@ -25,7 +25,7 @@ import {
Tr,
useBreakpointValue,
VStack,
-} from "@chakra-ui/react";
+} from '@chakra-ui/react';
import {
CheckIcon,
ChevronDownIcon,
@@ -33,21 +33,21 @@ import {
LinkIcon,
PencilIcon,
QrCodeIcon,
-} from "@heroicons/react/24/outline";
-import { ReactComponent as AddFileIcon } from "assets/add_file.svg";
-import classNames from "classnames";
-import { resetStrategy, statusColors } from "constants/UserSettings";
-import { useDashboard } from "contexts/DashboardContext";
-import { t } from "i18next";
-import { FC, Fragment, useEffect, useState } from "react";
-import CopyToClipboard from "react-copy-to-clipboard";
-import { useTranslation } from "react-i18next";
-import { User } from "types/User";
-import { formatBytes } from "utils/formatByte";
-import { OnlineBadge } from "./OnlineBadge";
-import { OnlineStatus } from "./OnlineStatus";
-import { Pagination } from "./Pagination";
-import { StatusBadge } from "./StatusBadge";
+} from '@heroicons/react/24/outline';
+import { ReactComponent as AddFileIcon } from 'assets/add_file.svg';
+import classNames from 'classnames';
+import { resetStrategy, statusColors } from 'constants/UserSettings';
+import { useDashboard } from 'contexts/DashboardContext';
+import { t } from 'i18next';
+import { FC, Fragment, useEffect, useState } from 'react';
+import CopyToClipboard from 'react-copy-to-clipboard';
+import { useTranslation } from 'react-i18next';
+import { User } from 'types/User';
+import { formatBytes } from 'utils/formatByte';
+import { OnlineBadge } from './OnlineBadge';
+import { OnlineStatus } from './OnlineStatus';
+import { Pagination } from './Pagination';
+import { StatusBadge } from './StatusBadge';
const EmptySectionIcon = chakra(AddFileIcon);
@@ -71,8 +71,8 @@ const QRIcon = chakra(QrCodeIcon, iconProps);
const EditIcon = chakra(PencilIcon, iconProps);
const SortIcon = chakra(ChevronDownIcon, {
baseStyle: {
- width: "15px",
- height: "15px",
+ width: '15px',
+ height: '15px',
},
});
type UsageSliderProps = {
@@ -89,10 +89,10 @@ const getResetStrategy = (strategy: string): string => {
return entry.title;
}
}
- return "No";
+ return 'No';
};
const UsageSliderCompact: FC = (props) => {
- const { used, total, dataLimitResetStrategy, totalUsedTraffic } = props;
+ const { used, total } = props;
const isUnlimited = total === 0 || total === null;
return (
= (props) => {
fontWeight="medium"
color="gray.600"
_dark={{
- color: "gray.400",
+ color: 'gray.400',
}}
>
- {formatBytes(used)} /{" "}
+ {formatBytes(used)} /{' '}
{isUnlimited ? (
∞
@@ -132,7 +132,7 @@ const UsageSlider: FC = (props) => {
@@ -145,28 +145,28 @@ const UsageSlider: FC = (props) => {
fontWeight="medium"
color="gray.600"
_dark={{
- color: "gray.400",
+ color: 'gray.400',
}}
>
- {formatBytes(used)} /{" "}
+ {formatBytes(used)} /{' '}
{isUnlimited ? (
∞
) : (
formatBytes(total) +
- (dataLimitResetStrategy && dataLimitResetStrategy !== "no_reset"
- ? " " +
- t(
- "userDialog.resetStrategy" +
- getResetStrategy(dataLimitResetStrategy)
- )
- : "")
+ (dataLimitResetStrategy && dataLimitResetStrategy !== 'no_reset'
+ ? ' ' +
+ t(
+ 'userDialog.resetStrategy' +
+ getResetStrategy(dataLimitResetStrategy)
+ )
+ : '')
)}
- {t("usersTable.total")}: {formatBytes(totalUsedTraffic)}
+ {t('usersTable.total')}: {formatBytes(totalUsedTraffic)}
>
@@ -180,7 +180,7 @@ export const Sort: FC = ({ sort, column }) => {
if (sort.includes(column))
return (
);
return null;
@@ -205,11 +205,11 @@ export const UsersTable: FC = (props) => {
useEffect(() => {
const calcTop = () => {
- const el = document.querySelectorAll("#filters")[0] as HTMLElement;
+ const el = document.querySelectorAll('#filters')[0] as HTMLElement;
setTop(`${el.offsetHeight}px`);
};
- window.addEventListener("scroll", calcTop);
- () => window.removeEventListener("scroll", calcTop);
+ window.addEventListener('scroll', calcTop);
+ () => window.removeEventListener('scroll', calcTop);
}, []);
const isFiltered = users.length !== totalUsers.total;
@@ -217,10 +217,10 @@ export const UsersTable: FC = (props) => {
const handleSort = (column: string) => {
let newSort = filters.sort;
if (newSort.includes(column)) {
- if (newSort.startsWith("-")) {
- newSort = "-created_at";
+ if (newSort.startsWith('-')) {
+ newSort = '-created_at';
} else {
- newSort = "-" + column;
+ newSort = '-' + column;
}
} else {
newSort = column;
@@ -240,10 +240,10 @@ export const UsersTable: FC = (props) => {
};
return (
-
+
@@ -255,11 +255,11 @@ export const UsersTable: FC = (props) => {
minW="120px"
pl={4}
pr={4}
- cursor={"pointer"}
- onClick={handleSort.bind(null, "username")}
+ cursor={'pointer'}
+ onClick={handleSort.bind(null, 'username')}
>
- {t("users")}
+ {t('users')}
@@ -270,24 +270,24 @@ export const UsersTable: FC = (props) => {
pl={0}
pr={0}
w="140px"
- cursor={"pointer"}
+ cursor={'pointer'}
>
- {t("usersTable.status")}
- {filters.status ? ": " + filters.status : ""}
+ {t('usersTable.status')}
+ {filters.status ? ': ' + filters.status : ''}