gitscope
https://dev.kronoz.win/r/marzneshin.git
app/dashboard/src/pages/hosts/users/dialog/index.tsx · v0.1.1
Source
1 6aab6f0 Mrdn 2y ago import {
2 6aab6f0 Mrdn 2y ago Alert,
3 6aab6f0 Mrdn 2y ago Button,
4 6aab6f0 Mrdn 2y ago Tooltip,
5 6aab6f0 Mrdn 2y ago IconButton,
6 6aab6f0 Mrdn 2y ago Spinner,
7 6aab6f0 Mrdn 2y ago AlertIcon,
8 6aab6f0 Mrdn 2y ago Box,
9 6aab6f0 Mrdn 2y ago Collapse,
10 6aab6f0 Mrdn 2y ago Flex,
11 6aab6f0 Mrdn 2y ago Grid,
12 6aab6f0 Mrdn 2y ago GridItem,
13 6aab6f0 Mrdn 2y ago ModalBody,
14 6aab6f0 Mrdn 2y ago ModalCloseButton,
15 6aab6f0 Mrdn 2y ago ModalContent,
16 6aab6f0 Mrdn 2y ago Modal,
17 6aab6f0 Mrdn 2y ago VStack,
18 6aab6f0 Mrdn 2y ago useColorMode,
19 6aab6f0 Mrdn 2y ago useToast,
20 6aab6f0 Mrdn 2y ago ModalOverlay,
21 6aab6f0 Mrdn 2y ago HStack,
22 6aab6f0 Mrdn 2y ago } from '@chakra-ui/react';
23 6aab6f0 Mrdn 2y ago import { zodResolver } from '@hookform/resolvers/zod';
24 6aab6f0 Mrdn 2y ago import dayjs from 'dayjs';
25 6aab6f0 Mrdn 2y ago import { FC, useEffect, useState } from 'react';
26 6aab6f0 Mrdn 2y ago import ReactApexChart from 'react-apexcharts';
27 6aab6f0 Mrdn 2y ago import { FormProvider, useForm, useWatch } from 'react-hook-form';
28 6aab6f0 Mrdn 2y ago import { useTranslation } from 'react-i18next';
29 6aab6f0 Mrdn 2y ago import {
30 6aab6f0 Mrdn 2y ago User,
31 6aab6f0 Mrdn 2y ago FilterUsageType,
32 6aab6f0 Mrdn 2y ago UserCreate,
33 6aab6f0 Mrdn 2y ago Service
34 6aab6f0 Mrdn 2y ago } from 'types';
35 6aab6f0 Mrdn 2y ago import { UsageFilter, createUsageConfig } from 'components/usage-filter';
36 6aab6f0 Mrdn 2y ago import {
37 6aab6f0 Mrdn 2y ago AddIcon,
38 6aab6f0 Mrdn 2y ago EditIcon,
39 6aab6f0 Mrdn 2y ago DialogModalHeader,
40 6aab6f0 Mrdn 2y ago DialogModalFooter,
41 6aab6f0 Mrdn 2y ago DeleteIcon
42 6aab6f0 Mrdn 2y ago } from 'components/dialog';
43 6aab6f0 Mrdn 2y ago import { UserUsageIcon } from './icons';
44 6aab6f0 Mrdn 2y ago import { getDefaultValues } from './default-values';
45 6aab6f0 Mrdn 2y ago import { schema, FormType } from './form-schema';
46 6aab6f0 Mrdn 2y ago import { ServicesField } from './services-field';
47 6aab6f0 Mrdn 2y ago import { UsernameField } from './username-field';
48 6aab6f0 Mrdn 2y ago import { DataLimitField } from './data-limit-field';
49 6aab6f0 Mrdn 2y ago import { PeriodicUsageReset } from './periodic-usage-reset';
50 6aab6f0 Mrdn 2y ago import { ExpireDateField } from './expire-date-field';
51 6aab6f0 Mrdn 2y ago import { NoteField } from './note-field';
52 6aab6f0 Mrdn 2y ago import { useUsers, useServices } from 'stores';
53 6aab6f0 Mrdn 2y ago
54 6aab6f0 Mrdn 2y ago const formatUser = (user: User): FormType => {
55 6aab6f0 Mrdn 2y ago const services: number[] = user.services.map((service: number | Service): number => {
56 6aab6f0 Mrdn 2y ago return (typeof service !== 'number') ? service.id : service;
57 6aab6f0 Mrdn 2y ago });
58 6aab6f0 Mrdn 2y ago return {
59 6aab6f0 Mrdn 2y ago ...user,
60 6aab6f0 Mrdn 2y ago services,
61 6aab6f0 Mrdn 2y ago data_limit: user.data_limit
62 6aab6f0 Mrdn 2y ago ? Number((user.data_limit / 1073741824).toFixed(5))
63 6aab6f0 Mrdn 2y ago : user.data_limit,
64 6aab6f0 Mrdn 2y ago };
65 6aab6f0 Mrdn 2y ago };
66 6aab6f0 Mrdn 2y ago
67 6aab6f0 Mrdn 2y ago export type UserDialogProps = {};
68 6aab6f0 Mrdn 2y ago
69 6aab6f0 Mrdn 2y ago export const UserDialog: FC<UserDialogProps> = () => {
70 6aab6f0 Mrdn 2y ago const {
71 6aab6f0 Mrdn 2y ago editingUser,
72 6aab6f0 Mrdn 2y ago isCreatingNewUser,
73 6aab6f0 Mrdn 2y ago onCreateUser,
74 6aab6f0 Mrdn 2y ago editUser,
75 6aab6f0 Mrdn 2y ago fetchUserUsage,
76 6aab6f0 Mrdn 2y ago onEditingUser,
77 6aab6f0 Mrdn 2y ago createUser,
78 6aab6f0 Mrdn 2y ago onDeletingUser,
79 6aab6f0 Mrdn 2y ago } = useUsers();
80 6aab6f0 Mrdn 2y ago const { refetchServices, services } = useServices();
81 6aab6f0 Mrdn 2y ago const isEditing = !!editingUser;
82 6aab6f0 Mrdn 2y ago const isOpen = isCreatingNewUser || isEditing;
83 6aab6f0 Mrdn 2y ago const [loading, setLoading] = useState(false);
84 6aab6f0 Mrdn 2y ago const [error, setError] = useState<string | null>('');
85 6aab6f0 Mrdn 2y ago const toast = useToast();
86 6aab6f0 Mrdn 2y ago const { t, i18n } = useTranslation();
87 6aab6f0 Mrdn 2y ago
88 6aab6f0 Mrdn 2y ago const { colorMode } = useColorMode();
89 6aab6f0 Mrdn 2y ago
90 6aab6f0 Mrdn 2y ago const [usageVisible, setUsageVisible] = useState(false);
91 6aab6f0 Mrdn 2y ago const handleUsageToggle = () => {
92 6aab6f0 Mrdn 2y ago setUsageVisible((current) => !current);
93 6aab6f0 Mrdn 2y ago };
94 6aab6f0 Mrdn 2y ago
95 6aab6f0 Mrdn 2y ago const form = useForm<FormType>({
96 6aab6f0 Mrdn 2y ago defaultValues: getDefaultValues(),
97 6aab6f0 Mrdn 2y ago resolver: zodResolver(schema),
98 6aab6f0 Mrdn 2y ago });
99 6aab6f0 Mrdn 2y ago
100 6aab6f0 Mrdn 2y ago const [dataLimit] = useWatch({
101 6aab6f0 Mrdn 2y ago control: form.control,
102 6aab6f0 Mrdn 2y ago name: ['data_limit'],
103 6aab6f0 Mrdn 2y ago });
104 6aab6f0 Mrdn 2y ago
105 6aab6f0 Mrdn 2y ago const usageTitle = t('userDialog.total');
106 6aab6f0 Mrdn 2y ago const [usage, setUsage] = useState(createUsageConfig(colorMode, usageTitle));
107 6aab6f0 Mrdn 2y ago const [usageFilter, setUsageFilter] = useState('1m');
108 6aab6f0 Mrdn 2y ago const fetchUsageWithFilter = (query: FilterUsageType) => {
109 6aab6f0 Mrdn 2y ago fetchUserUsage(editingUser!, query).then((data: any) => {
110 6aab6f0 Mrdn 2y ago const labels = [];
111 6aab6f0 Mrdn 2y ago const series = [];
112 6aab6f0 Mrdn 2y ago for (const key in data.usages) {
113 6aab6f0 Mrdn 2y ago series.push(data.usages[key].used_traffic);
114 6aab6f0 Mrdn 2y ago labels.push(data.usages[key].node_name);
115 6aab6f0 Mrdn 2y ago }
116 6aab6f0 Mrdn 2y ago setUsage(createUsageConfig(colorMode, usageTitle, series, labels));
117 6aab6f0 Mrdn 2y ago });
118 6aab6f0 Mrdn 2y ago };
119 6aab6f0 Mrdn 2y ago
120 6aab6f0 Mrdn 2y ago useEffect(() => {
121 6aab6f0 Mrdn 2y ago if (isOpen) {
122 6aab6f0 Mrdn 2y ago refetchServices();
123 6aab6f0 Mrdn 2y ago }
124 6aab6f0 Mrdn 2y ago }, [isOpen]);
125 6aab6f0 Mrdn 2y ago
126 6aab6f0 Mrdn 2y ago
127 6aab6f0 Mrdn 2y ago useEffect(() => {
128 6aab6f0 Mrdn 2y ago if (editingUser) {
129 6aab6f0 Mrdn 2y ago form.reset(formatUser(editingUser));
130 6aab6f0 Mrdn 2y ago
131 6aab6f0 Mrdn 2y ago fetchUsageWithFilter({
132 6aab6f0 Mrdn 2y ago start: dayjs().utc().subtract(30, 'day').format('YYYY-MM-DDTHH:00:00'),
133 6aab6f0 Mrdn 2y ago });
134 6aab6f0 Mrdn 2y ago }
135 6aab6f0 Mrdn 2y ago }, [editingUser]);
136 6aab6f0 Mrdn 2y ago
137 6aab6f0 Mrdn 2y ago const submit = async (values: FormType) => {
138 6aab6f0 Mrdn 2y ago setLoading(true);
139 6aab6f0 Mrdn 2y ago const methods = { edited: editUser, created: createUser };
140 6aab6f0 Mrdn 2y ago const method = isEditing ? 'edited' : 'created';
141 6aab6f0 Mrdn 2y ago setError(null);
142 6aab6f0 Mrdn 2y ago const { services, username, ...rest } = values;
143 6aab6f0 Mrdn 2y ago
144 6aab6f0 Mrdn 2y ago let body: UserCreate = {
145 6aab6f0 Mrdn 2y ago ...rest,
146 6aab6f0 Mrdn 2y ago username,
147 6aab6f0 Mrdn 2y ago services,
148 6aab6f0 Mrdn 2y ago data_limit: values.data_limit,
149 6aab6f0 Mrdn 2y ago data_limit_reset_strategy:
150 6aab6f0 Mrdn 2y ago values.data_limit && values.data_limit > 0
151 6aab6f0 Mrdn 2y ago ? values.data_limit_reset_strategy
152 6aab6f0 Mrdn 2y ago : 'no_reset',
153 6aab6f0 Mrdn 2y ago status:
154 6aab6f0 Mrdn 2y ago values.status === 'active' || values.status === 'disabled'
155 6aab6f0 Mrdn 2y ago ? values.status
156 6aab6f0 Mrdn 2y ago : 'active',
157 6aab6f0 Mrdn 2y ago };
158 6aab6f0 Mrdn 2y ago
159 6aab6f0 Mrdn 2y ago await methods[method](body)
160 6aab6f0 Mrdn 2y ago .then(() => {
161 6aab6f0 Mrdn 2y ago toast({
162 6aab6f0 Mrdn 2y ago title: t(
163 6aab6f0 Mrdn 2y ago isEditing ? 'userDialog.userEdited' : 'userDialog.userCreated',
164 6aab6f0 Mrdn 2y ago { username: values.username }
165 6aab6f0 Mrdn 2y ago ),
166 6aab6f0 Mrdn 2y ago status: 'success',
167 6aab6f0 Mrdn 2y ago isClosable: true,
168 6aab6f0 Mrdn 2y ago position: 'top',
169 6aab6f0 Mrdn 2y ago duration: 3000,
170 6aab6f0 Mrdn 2y ago });
171 6aab6f0 Mrdn 2y ago onClose();
172 6aab6f0 Mrdn 2y ago })
173 6aab6f0 Mrdn 2y ago .catch((err) => {
174 6aab6f0 Mrdn 2y ago if (err?.response?.status === 409 || err?.response?.status === 400)
175 6aab6f0 Mrdn 2y ago setError(err?.response?._data?.detail);
176 6aab6f0 Mrdn 2y ago if (err?.response?.status === 422) {
177 6aab6f0 Mrdn 2y ago Object.keys(err.response._data.detail).forEach((key) => {
178 6aab6f0 Mrdn 2y ago setError(err?.response._data.detail[key] as string);
179 6aab6f0 Mrdn 2y ago form.setError(
180 6aab6f0 Mrdn 2y ago key as 'services' | 'username' | 'data_limit' | 'expire',
181 6aab6f0 Mrdn 2y ago {
182 6aab6f0 Mrdn 2y ago type: 'custom',
183 6aab6f0 Mrdn 2y ago message: err.response._data.detail[key],
184 6aab6f0 Mrdn 2y ago }
185 6aab6f0 Mrdn 2y ago );
186 6aab6f0 Mrdn 2y ago });
187 6aab6f0 Mrdn 2y ago }
188 6aab6f0 Mrdn 2y ago })
189 6aab6f0 Mrdn 2y ago .finally(() => {
190 6aab6f0 Mrdn 2y ago setLoading(false);
191 6aab6f0 Mrdn 2y ago });
192 6aab6f0 Mrdn 2y ago };
193 6aab6f0 Mrdn 2y ago
194 6aab6f0 Mrdn 2y ago const onClose = () => {
195 6aab6f0 Mrdn 2y ago form.reset(getDefaultValues());
196 6aab6f0 Mrdn 2y ago onCreateUser(false);
197 6aab6f0 Mrdn 2y ago onEditingUser(null);
198 6aab6f0 Mrdn 2y ago setError(null);
199 6aab6f0 Mrdn 2y ago setUsageVisible(false);
200 6aab6f0 Mrdn 2y ago setUsageFilter('1m');
201 6aab6f0 Mrdn 2y ago };
202 6aab6f0 Mrdn 2y ago
203 6aab6f0 Mrdn 2y ago const handleResetUsage = () => {
204 6aab6f0 Mrdn 2y ago useUsers.setState({ resetUsageUser: editingUser });
205 6aab6f0 Mrdn 2y ago };
206 6aab6f0 Mrdn 2y ago
207 6aab6f0 Mrdn 2y ago const handleRevokeSubscription = () => {
208 6aab6f0 Mrdn 2y ago useUsers.setState({ revokeSubscriptionUser: editingUser });
209 6aab6f0 Mrdn 2y ago };
210 6aab6f0 Mrdn 2y ago
211 6aab6f0 Mrdn 2y ago const disabled = loading;
212 6aab6f0 Mrdn 2y ago
213 6aab6f0 Mrdn 2y ago return (
214 6aab6f0 Mrdn 2y ago <Modal isOpen={isOpen} onClose={onClose} size="2xl">
215 6aab6f0 Mrdn 2y ago <ModalOverlay bg="blackAlpha.300" backdropFilter="blur(10px)" />
216 6aab6f0 Mrdn 2y ago <FormProvider {...form} formState={form.formState}>
217 6aab6f0 Mrdn 2y ago <ModalContent mx="3">
218 6aab6f0 Mrdn 2y ago <form onSubmit={form.handleSubmit(submit)}>
219 6aab6f0 Mrdn 2y ago <DialogModalHeader HeaderIcon={isEditing ? EditIcon : AddIcon} title={isEditing ? t('userDialog.editUserTitle') : t('createNewUser')} />
220 6aab6f0 Mrdn 2y ago <ModalCloseButton mt={3} disabled={disabled} />
221 6aab6f0 Mrdn 2y ago <ModalBody>
222 6aab6f0 Mrdn 2y ago <Grid
223 6aab6f0 Mrdn 2y ago templateColumns={{
224 6aab6f0 Mrdn 2y ago base: 'repeat(1, 1fr)',
225 6aab6f0 Mrdn 2y ago md: 'repeat(2, 1fr)',
226 6aab6f0 Mrdn 2y ago }}
227 6aab6f0 Mrdn 2y ago gap={3}
228 6aab6f0 Mrdn 2y ago >
229 6aab6f0 Mrdn 2y ago <GridItem>
230 6aab6f0 Mrdn 2y ago <VStack justifyContent="space-between">
231 6aab6f0 Mrdn 2y ago <Flex
232 6aab6f0 Mrdn 2y ago flexDirection="column"
233 6aab6f0 Mrdn 2y ago gridAutoRows="min-content"
234 6aab6f0 Mrdn 2y ago w="full"
235 6aab6f0 Mrdn 2y ago >
236 6aab6f0 Mrdn 2y ago <UsernameField form={form} disabled={disabled} isEditing={isEditing} t={t} />
237 6aab6f0 Mrdn 2y ago <DataLimitField form={form} disabled={disabled} t={t} />
238 6aab6f0 Mrdn 2y ago
239 6aab6f0 Mrdn 2y ago <Collapse
240 6aab6f0 Mrdn 2y ago in={!!(dataLimit && dataLimit > 0)}
241 6aab6f0 Mrdn 2y ago animateOpacity
242 6aab6f0 Mrdn 2y ago style={{ width: '100%' }}
243 6aab6f0 Mrdn 2y ago >
244 6aab6f0 Mrdn 2y ago <PeriodicUsageReset form={form} t={t} />
245 6aab6f0 Mrdn 2y ago </Collapse>
246 6aab6f0 Mrdn 2y ago <ExpireDateField form={form} t={t} i18n={i18n} disabled={disabled} />
247 6aab6f0 Mrdn 2y ago <NoteField form={form} t={t} />
248 6aab6f0 Mrdn 2y ago </Flex>
249 6aab6f0 Mrdn 2y ago {error && (
250 6aab6f0 Mrdn 2y ago <Alert
251 6aab6f0 Mrdn 2y ago status="error"
252 6aab6f0 Mrdn 2y ago display={{ base: 'none', md: 'flex' }}
253 6aab6f0 Mrdn 2y ago >
254 6aab6f0 Mrdn 2y ago <AlertIcon />
255 6aab6f0 Mrdn 2y ago {error}
256 6aab6f0 Mrdn 2y ago </Alert>
257 6aab6f0 Mrdn 2y ago )}
258 6aab6f0 Mrdn 2y ago </VStack>
259 6aab6f0 Mrdn 2y ago </GridItem>
260 6aab6f0 Mrdn 2y ago
261 6aab6f0 Mrdn 2y ago <GridItem>
262 6aab6f0 Mrdn 2y ago <ServicesField t={t} services={services} form={form} />
263 6aab6f0 Mrdn 2y ago </GridItem>
264 6aab6f0 Mrdn 2y ago
265 6aab6f0 Mrdn 2y ago {isEditing && usageVisible && (
266 6aab6f0 Mrdn 2y ago <GridItem pt={6} colSpan={{ base: 1, md: 2 }}>
267 6aab6f0 Mrdn 2y ago <VStack gap={4}>
268 6aab6f0 Mrdn 2y ago <UsageFilter
269 6aab6f0 Mrdn 2y ago defaultValue={usageFilter}
270 6aab6f0 Mrdn 2y ago onChange={(filter, query) => {
271 6aab6f0 Mrdn 2y ago setUsageFilter(filter);
272 6aab6f0 Mrdn 2y ago fetchUsageWithFilter(query);
273 6aab6f0 Mrdn 2y ago }}
274 6aab6f0 Mrdn 2y ago />
275 6aab6f0 Mrdn 2y ago <Box
276 6aab6f0 Mrdn 2y ago width={{ base: '100%', md: '70%' }}
277 6aab6f0 Mrdn 2y ago justifySelf="center"
278 6aab6f0 Mrdn 2y ago >
279 6aab6f0 Mrdn 2y ago <ReactApexChart
280 6aab6f0 Mrdn 2y ago options={usage.options}
281 6aab6f0 Mrdn 2y ago series={usage.series}
282 6aab6f0 Mrdn 2y ago type="donut"
283 6aab6f0 Mrdn 2y ago />
284 6aab6f0 Mrdn 2y ago </Box>
285 6aab6f0 Mrdn 2y ago </VStack>
286 6aab6f0 Mrdn 2y ago </GridItem>
287 6aab6f0 Mrdn 2y ago )}
288 6aab6f0 Mrdn 2y ago </Grid>
289 6aab6f0 Mrdn 2y ago
290 6aab6f0 Mrdn 2y ago {error && (
291 6aab6f0 Mrdn 2y ago <Alert
292 6aab6f0 Mrdn 2y ago mt="3"
293 6aab6f0 Mrdn 2y ago status="error"
294 6aab6f0 Mrdn 2y ago display={{ base: 'flex', md: 'none' }}
295 6aab6f0 Mrdn 2y ago >
296 6aab6f0 Mrdn 2y ago <AlertIcon />
297 6aab6f0 Mrdn 2y ago {error}
298 6aab6f0 Mrdn 2y ago </Alert>
299 6aab6f0 Mrdn 2y ago )}
300 6aab6f0 Mrdn 2y ago </ModalBody>
301 6aab6f0 Mrdn 2y ago <DialogModalFooter>
302 6aab6f0 Mrdn 2y ago <HStack
303 6aab6f0 Mrdn 2y ago justifyContent="flex-start"
304 6aab6f0 Mrdn 2y ago w={{
305 6aab6f0 Mrdn 2y ago base: 'full',
306 6aab6f0 Mrdn 2y ago sm: 'unset',
307 6aab6f0 Mrdn 2y ago }}
308 6aab6f0 Mrdn 2y ago >
309 6aab6f0 Mrdn 2y ago {isEditing && editingUser !== null && (
310 6aab6f0 Mrdn 2y ago <>
311 6aab6f0 Mrdn 2y ago <Tooltip label={t('delete')} placement="top">
312 6aab6f0 Mrdn 2y ago <IconButton
313 6aab6f0 Mrdn 2y ago aria-label="Delete"
314 6aab6f0 Mrdn 2y ago size="sm"
315 6aab6f0 Mrdn 2y ago onClick={() => {
316 6aab6f0 Mrdn 2y ago onDeletingUser(editingUser);
317 6aab6f0 Mrdn 2y ago onClose();
318 6aab6f0 Mrdn 2y ago }}
319 6aab6f0 Mrdn 2y ago >
320 6aab6f0 Mrdn 2y ago <DeleteIcon />
321 6aab6f0 Mrdn 2y ago </IconButton>
322 6aab6f0 Mrdn 2y ago </Tooltip>
323 6aab6f0 Mrdn 2y ago <Tooltip label={t('userDialog.usage')} placement="top">
324 6aab6f0 Mrdn 2y ago <IconButton
325 6aab6f0 Mrdn 2y ago aria-label="usage"
326 6aab6f0 Mrdn 2y ago size="sm"
327 6aab6f0 Mrdn 2y ago onClick={handleUsageToggle}
328 6aab6f0 Mrdn 2y ago >
329 6aab6f0 Mrdn 2y ago <UserUsageIcon />
330 6aab6f0 Mrdn 2y ago </IconButton>
331 6aab6f0 Mrdn 2y ago </Tooltip>
332 6aab6f0 Mrdn 2y ago <Button onClick={handleResetUsage} size="sm">
333 6aab6f0 Mrdn 2y ago {t('userDialog.resetUsage')}
334 6aab6f0 Mrdn 2y ago </Button>
335 6aab6f0 Mrdn 2y ago <Button onClick={handleRevokeSubscription} size="sm">
336 6aab6f0 Mrdn 2y ago {t('userDialog.revokeSubscription')}
337 6aab6f0 Mrdn 2y ago </Button>
338 6aab6f0 Mrdn 2y ago </>
339 6aab6f0 Mrdn 2y ago )}
340 6aab6f0 Mrdn 2y ago </HStack>
341 6aab6f0 Mrdn 2y ago <HStack
342 6aab6f0 Mrdn 2y ago w="full"
343 6aab6f0 Mrdn 2y ago maxW={{ md: '50%', base: 'full' }}
344 6aab6f0 Mrdn 2y ago justify="end"
345 6aab6f0 Mrdn 2y ago >
346 6aab6f0 Mrdn 2y ago <Button
347 6aab6f0 Mrdn 2y ago type="submit"
348 6aab6f0 Mrdn 2y ago size="sm"
349 6aab6f0 Mrdn 2y ago px="8"
350 6aab6f0 Mrdn 2y ago colorScheme="primary"
351 6aab6f0 Mrdn 2y ago leftIcon={loading ? <Spinner size="xs" /> : undefined}
352 6aab6f0 Mrdn 2y ago disabled={false}
353 6aab6f0 Mrdn 2y ago >
354 6aab6f0 Mrdn 2y ago {isEditing ? t('userDialog.editUser') : t('createUser')}
355 6aab6f0 Mrdn 2y ago </Button>
356 6aab6f0 Mrdn 2y ago </HStack>
357 6aab6f0 Mrdn 2y ago </DialogModalFooter>
358 6aab6f0 Mrdn 2y ago </form>
359 6aab6f0 Mrdn 2y ago </ModalContent>
360 6aab6f0 Mrdn 2y ago </FormProvider>
361 6aab6f0 Mrdn 2y ago </Modal >
362 6aab6f0 Mrdn 2y ago );
363 6aab6f0 Mrdn 2y ago };