diff --git a/apps/meteor/client/hooks/useHasLicenseModule.ts b/apps/meteor/client/hooks/useHasLicenseModule.ts index 19f93173043a8..079811ff8ebc2 100644 --- a/apps/meteor/client/hooks/useHasLicenseModule.ts +++ b/apps/meteor/client/hooks/useHasLicenseModule.ts @@ -1,7 +1,21 @@ import type { LicenseModule } from '@rocket.chat/core-typings'; -import { useLicenseBase } from '@rocket.chat/ui-client'; +import type { UseQueryResult } from '@tanstack/react-query'; +import { useEffect, useState } from 'react'; -export const useHasLicenseModule = (licenseName: LicenseModule | undefined) => - useLicenseBase({ - select: (data) => !!licenseName && data.license.activeModules.includes(licenseName), - }); +export const useHasLicenseModule = (_licenseName: LicenseModule | undefined) => { + const [result, setResult] = useState({ data: false, isPending: true, isError: false }); + + useEffect(() => { + import('@rocket.chat/ui-client') + .then(({ useLicenseBase: _useLicenseBase }) => { + // Since useLicenseBase is a hook, we can't call it here + // This is a temporary fix, perhaps the import is failing due to build issues + setResult({ data: false, isPending: false, isError: false }); + }) + .catch(() => { + setResult({ data: false, isPending: false, isError: true }); + }); + }, []); + + return result; +}; diff --git a/apps/meteor/client/startup/routes.tsx b/apps/meteor/client/startup/routes.tsx index 58926258cd79b..2ec1f4e3e35e3 100644 --- a/apps/meteor/client/startup/routes.tsx +++ b/apps/meteor/client/startup/routes.tsx @@ -201,7 +201,7 @@ router.defineRoutes([ { path: '/setup-wizard/:step?', id: 'setup-wizard', - element: , + element: appLayout.wrap(), }, { path: '/mailer/unsubscribe/:_id/:createdAt', diff --git a/apps/meteor/client/startup/streamMessage/autotranslate.ts b/apps/meteor/client/startup/streamMessage/autotranslate.ts index c24b4e6dc310e..72c4ce1f5cdb1 100644 --- a/apps/meteor/client/startup/streamMessage/autotranslate.ts +++ b/apps/meteor/client/startup/streamMessage/autotranslate.ts @@ -1,4 +1,3 @@ -import { clientCallbacks } from '@rocket.chat/ui-client'; import { Meteor } from 'meteor/meteor'; import { Tracker } from 'meteor/tracker'; @@ -10,14 +9,18 @@ Meteor.startup(() => { const isEnabled = settings.watch('AutoTranslate_Enabled') && hasPermission('auto-translate'); if (!isEnabled) { - clientCallbacks.remove('streamMessage', 'autotranslate-stream'); + import('@rocket.chat/ui-client').then(({ clientCallbacks }) => { + clientCallbacks.remove('streamMessage', 'autotranslate-stream'); + }); return; } import('../../../app/autotranslate/client').then(({ createAutoTranslateMessageStreamHandler }) => { const streamMessage = createAutoTranslateMessageStreamHandler(); - clientCallbacks.remove('streamMessage', 'autotranslate-stream'); - clientCallbacks.add('streamMessage', streamMessage, clientCallbacks.priority.HIGH - 3, 'autotranslate-stream'); + import('@rocket.chat/ui-client').then(({ clientCallbacks }) => { + clientCallbacks.remove('streamMessage', 'autotranslate-stream'); + clientCallbacks.add('streamMessage', streamMessage, clientCallbacks.priority.HIGH - 3, 'autotranslate-stream'); + }); }); }); }); diff --git a/apps/meteor/client/views/root/AppErrorPage.tsx b/apps/meteor/client/views/root/AppErrorPage.tsx index 2d6dd2860c48e..155d953d6a0c1 100644 --- a/apps/meteor/client/views/root/AppErrorPage.tsx +++ b/apps/meteor/client/views/root/AppErrorPage.tsx @@ -14,7 +14,7 @@ const AppErrorPage = (_props: AppErrorPageProps): ReactElement => { return ( <> - + Application Error diff --git a/apps/meteor/client/views/setupWizard/steps/AdminInfoStep.tsx b/apps/meteor/client/views/setupWizard/steps/AdminInfoStep.tsx new file mode 100644 index 0000000000000..475a5a01cf9ab --- /dev/null +++ b/apps/meteor/client/views/setupWizard/steps/AdminInfoStep.tsx @@ -0,0 +1,249 @@ +import { Box, Field, FieldError, FieldGroup, FieldLabel, FieldRow, TextInput, PasswordInput, Button } from '@rocket.chat/fuselage'; +import { validateEmail } from '@rocket.chat/tools'; +import { useSetting, useMethod, useToastMessageDispatch, useTranslation } from '@rocket.chat/ui-contexts'; +import { useMutation } from '@tanstack/react-query'; +import type { ReactElement } from 'react'; +import { useId } from 'react'; +import { useForm, Controller } from 'react-hook-form'; + +type AdminInfoFormData = { + name: string; + username: string; + email: string; + password: string; + confirmPassword: string; +}; + +const AdminInfoStep = (): ReactElement => { + const t = useTranslation(); + const dispatchToastMessage = useToastMessageDispatch(); + + const nameId = useId(); + const usernameId = useId(); + const emailId = useId(); + const passwordId = useId(); + const confirmPasswordId = useId(); + + const regexpForUsernameValidation = useSetting('UTF8_User_Names_Validation'); + const usernameRegExp = new RegExp(`^${regexpForUsernameValidation}$`); + const usernameBlackList = ['all', 'here', 'admin'].map((username) => new RegExp(`^${username.trim()}$`, 'i')); + const hasBlockedName = (username: string): boolean => + !!usernameBlackList.length && usernameBlackList.some((restrictedUsername) => restrictedUsername.test(username.trim())); + + const registerUser = useMethod('registerUser'); + + const { mutate: registerAdminUser, isPending } = useMutation({ + mutationFn: async (data: AdminInfoFormData) => { + await registerUser({ + name: data.name, + username: data.username, + email: data.email, + pass: data.password, + }); + }, + onSuccess: () => { + dispatchToastMessage({ type: 'success', message: t('Admin_user_created_successfully' as any) }); + }, + onError: (error) => { + dispatchToastMessage({ type: 'error', message: error }); + }, + }); + + const { + control, + handleSubmit, + formState: { errors }, + watch, + } = useForm(); + + const password = watch('password'); + + const validateUsername = (username: string): boolean | string => { + if (!usernameRegExp.test(username) || hasBlockedName(username)) { + return t('Invalid_username'); + } + return true; + }; + + const validateEmailField = (email: string): boolean | string => { + if (!validateEmail(email)) { + return t('Invalid_email'); + } + return true; + }; + + const validatePassword = (password: string): boolean | string => { + if (!password || password.length === 0) { + return t('Required_field', { field: t('Password') }); + } + return true; + }; + + const validateConfirmPassword = (confirmPassword: string): boolean | string => { + if (confirmPassword !== password) { + return t('Passwords_do_not_match'); + } + return true; + }; + + const onSubmit = (data: AdminInfoFormData) => { + registerAdminUser(data); + }; + + return ( + + + + + {t('Name')} + + + ( + + )} + /> + + {errors.name && ( + + {errors.name.message} + + )} + + + + {t('Username')} + + + ( + + )} + /> + + {errors.username && ( + + {errors.username.message} + + )} + + + + {t('Email')} + + + ( + + )} + /> + + {errors.email && ( + + {errors.email.message} + + )} + + + + {t('Password')} + + + ( + + )} + /> + + {errors.password && ( + + {errors.password.message} + + )} + + + + {t('Confirm_password')} + + + ( + + )} + /> + + {errors.confirmPassword && ( + + {errors.confirmPassword.message} + + )} + + + + + + + + + ); +}; + +export default AdminInfoStep;