From a3fd192b3c9dc26c67303582752e8a56d7980207 Mon Sep 17 00:00:00 2001 From: AdityaJ2305 Date: Fri, 3 Jan 2025 20:24:54 +0530 Subject: [PATCH 01/83] update to shadcn form in userresetpassword --- src/components/Users/UserResetPassword.tsx | 314 +++++++++++---------- 1 file changed, 159 insertions(+), 155 deletions(-) diff --git a/src/components/Users/UserResetPassword.tsx b/src/components/Users/UserResetPassword.tsx index 5807ecd129b..91ed393eb4a 100644 --- a/src/components/Users/UserResetPassword.tsx +++ b/src/components/Users/UserResetPassword.tsx @@ -1,9 +1,21 @@ +import { zodResolver } from "@hookform/resolvers/zod"; import { useState } from "react"; +import { useForm } from "react-hook-form"; import { useTranslation } from "react-i18next"; +import { z } from "zod"; import CareIcon from "@/CAREUI/icons/CareIcon"; -import Form from "@/components/Form/Form"; +import { Button } from "@/components/ui/button"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; + import TextFormField from "@/components/Form/FormFields/TextFormField"; import { validateRule } from "@/components/Users/UserFormValidations"; import { UpdatePasswordForm } from "@/components/Users/models"; @@ -13,14 +25,29 @@ import routes from "@/Utils/request/api"; import request from "@/Utils/request/request"; import { UserBase } from "@/types/user/user"; -import ButtonV2 from "../Common/ButtonV2"; - -interface PasswordForm { - username: string; - old_password: string; - new_password_1: string; - new_password_2: string; -} +const PasswordSchema = z + .object({ + current_password: z + .string() + .min(1, { message: "Please enter current password" }), + new_password_1: z + .string() + .min(8, { message: "New password must be at least 8 characters long" }) + .regex(/\d/, { message: "Password must contain at least one number" }) + .regex(/[a-z]/, { + message: "Password must contain at least one lowercase letter", + }) + .regex(/[A-Z]/, { + message: "Password must contain at least one uppercase letter", + }), + new_password_2: z + .string() + .min(1, { message: "Please confirm your new password" }), + }) + .refine((values) => values.new_password_1 === values.new_password_2, { + message: "New password and confirm password must be the same.", + path: ["new_password_2"], + }); export default function UserResetPassword({ userData, @@ -28,58 +55,25 @@ export default function UserResetPassword({ userData: UserBase; }) { const { t } = useTranslation(); - const [isSubmitting, setisSubmitting] = useState(false); + const [isSubmitting, setIsSubmitting] = useState(false); const [isEditing, setIsEditing] = useState(false); + const [isPasswordFieldFocused, setIsPasswordFieldFocused] = useState(false); + + const resetPasswordForm = useForm({ + resolver: zodResolver(PasswordSchema), + defaultValues: { + current_password: "", + new_password_1: "", + new_password_2: "", + }, + }); + const handleSubmitPassword = async ( + formData: z.infer, + ) => { + setIsSubmitting(true); - const initForm: PasswordForm = { - username: userData.username, - old_password: "", - new_password_1: "", - new_password_2: "", - }; - - const validateNewPassword = (password: string) => { - if ( - password.length < 8 || - !/\d/.test(password) || - password === password.toUpperCase() || - password === password.toLowerCase() - ) { - return false; - } - return true; - }; - - const validateForm = (formData: PasswordForm) => { - const errors: Partial> = {}; - - if (!formData.old_password) { - errors.old_password = t("please_enter_current_password"); - } - - if (!formData.new_password_1) { - errors.new_password_1 = t("please_enter_new_password"); - } else if (!validateNewPassword(formData.new_password_1)) { - errors.new_password_1 = t("new_password_validation"); - } - - if (!formData.new_password_2) { - errors.new_password_2 = t("please_confirm_password"); - } else if (formData.new_password_1 !== formData.new_password_2) { - errors.new_password_2 = t("password_mismatch"); - } - - if (formData.new_password_1 === formData.old_password) { - errors.new_password_1 = t("new_password_same_as_old"); - } - - return errors; - }; - - const handleSubmit = async (formData: PasswordForm) => { - setisSubmitting(true); const form: UpdatePasswordForm = { - old_password: formData.old_password, + old_password: formData.current_password, username: userData.username, new_password: formData.new_password_1, }; @@ -89,119 +83,129 @@ export default function UserResetPassword({ }); if (res?.ok) { - Notification.Success({ msg: data?.message as string }); + Notification.Success({ msg: "Password Updated Successfully" }); } else { Notification.Error({ - msg: error?.message ?? t("password_update_error"), + msg: t("password_update_error"), }); } - setisSubmitting(false); + setIsSubmitting(false); }; - const renderPasswordForm = () => { - return ( - - defaults={initForm} - validate={validateForm} - onSubmit={handleSubmit} - resetFormValsOnCancel - resetFormValsOnSubmit - hideRestoreDraft - noPadding - disabled={isSubmitting} - hideCancelButton - > - {(field) => ( -
- -
- -
- {validateRule( - field("new_password_1").value?.length >= 8, - t("password_length_validation"), - !field("new_password_1").value, + const renderPasswordForm = () => ( +
+ +
+ ( + + {t("current_password")} + + { + field.onChange(value.value); + }} + required + /> + + + + )} + /> + + ( + + {t("new_password")} + + { + field.onChange(value.value); + }} + onFocus={() => setIsPasswordFieldFocused(true)} + onBlur={() => setIsPasswordFieldFocused(false)} + /> + + {isPasswordFieldFocused && ( +
+ {validateRule( + field.value.length >= 8, + t("password_length_validation"), + !field.value, + )} + {validateRule( + /[a-z]/.test(field.value), + t("password_lowercase_validation"), + !field.value, + )} + {validateRule( + /[A-Z]/.test(field.value), + t("password_uppercase_validation"), + !field.value, + )} + {validateRule( + /\d/.test(field.value), + t("password_number_validation"), + !field.value, + )} +
)} - {validateRule( - field("new_password_1").value !== - field("new_password_1").value?.toUpperCase(), - t("password_lowercase_validation"), - !field("new_password_1").value, - )} - {validateRule( - field("new_password_1").value !== - field("new_password_1").value?.toLowerCase(), - t("password_uppercase_validation"), - !field("new_password_1").value, - )} - {validateRule( - /\d/.test(field("new_password_1").value ?? ""), - t("password_number_validation"), - !field("new_password_1").value, - )} -
-
-
- - {field("new_password_2").value?.length > 0 && ( -
- {validateRule( - field("new_password_1").value === - field("new_password_2").value, - t("password_mismatch"), - !field("new_password_2").value, - )} -
- )} -
-
- )} - - ); - }; - + + )} + /> + + ( + + {t("new_password_confirmation")} + + { + field.onChange(value.value); + }} + required + /> + + + + )} + /> +
+ + + + + ); const editButton = () => (
- setIsEditing(!isEditing)} type="button" id="change-edit-password-button" className="flex items-center gap-2 rounded-sm border border-gray-100 bg-white px-3 py-1.5 text-sm text-[#009D48] shadow-sm hover:bg-gray-50" - shadow={false} > {isEditing ? t("cancel") : t("change_password")} - +
); From 553e6dad3535c0072578c5289d926ac8dba8a70c Mon Sep 17 00:00:00 2001 From: AdityaJ2305 Date: Fri, 3 Jan 2025 20:27:29 +0530 Subject: [PATCH 02/83] deleted old form --- src/components/Form/Form.tsx | 167 ----------------------------------- 1 file changed, 167 deletions(-) delete mode 100644 src/components/Form/Form.tsx diff --git a/src/components/Form/Form.tsx b/src/components/Form/Form.tsx deleted file mode 100644 index 97f424f69d7..00000000000 --- a/src/components/Form/Form.tsx +++ /dev/null @@ -1,167 +0,0 @@ -import { useEffect, useMemo, useRef, useState } from "react"; - -import { Cancel, Submit } from "@/components/Common/ButtonV2"; -import { FieldValidator } from "@/components/Form/FieldValidators"; -import { - FormContextValue, - createFormContext, -} from "@/components/Form/FormContext"; -import { FieldChangeEvent } from "@/components/Form/FormFields/Utils"; -import { - FormDetails, - FormErrors, - FormState, - formReducer, -} from "@/components/Form/Utils"; - -import { DraftSection, useAutoSaveReducer } from "@/Utils/AutoSave"; -import * as Notification from "@/Utils/Notifications"; -import { classNames, isEmpty, omitBy } from "@/Utils/utils"; - -type Props = { - className?: string; - defaults: T; - asyncGetDefaults?: (() => Promise) | false; - validate?: (form: T) => FormErrors; - onSubmit: (form: T) => Promise | void>; - onCancel?: () => void; - noPadding?: true; - disabled?: boolean; - submitLabel?: string; - cancelLabel?: string; - onDraftRestore?: (newState: FormState) => void; - children: (props: FormContextValue) => React.ReactNode; - hideRestoreDraft?: boolean; - resetFormValsOnCancel?: boolean; - resetFormValsOnSubmit?: boolean; - hideCancelButton?: boolean; - submitButtonClassName?: string; - hideSubmitButton?: boolean; - disableMarginOnChildren?: boolean; -}; - -const Form = ({ - asyncGetDefaults, - validate, - hideCancelButton = false, - hideSubmitButton = false, - disableMarginOnChildren = false, - ...props -}: Props) => { - const initial = { form: props.defaults, errors: {} }; - const [isLoading, setIsLoading] = useState(!!asyncGetDefaults); - const [state, dispatch] = useAutoSaveReducer(formReducer, initial); - const formVals = useRef(props.defaults); - - useEffect(() => { - if (!asyncGetDefaults) return; - - asyncGetDefaults().then((form) => { - dispatch({ type: "set_form", form }); - setIsLoading(false); - }); - }, [asyncGetDefaults]); - - const handleSubmit = async (event: React.FormEvent) => { - event.preventDefault(); - event.stopPropagation(); - - if (validate) { - const errors = omitBy(validate(state.form), isEmpty) as FormErrors; - - if (Object.keys(errors).length) { - dispatch({ type: "set_errors", errors }); - - if (errors.$all) { - Notification.Error({ msg: errors.$all }); - } - return; - } - } - - const errors = await props.onSubmit(state.form); - if (errors) { - dispatch({ - type: "set_errors", - errors: { ...state.errors, ...errors }, - }); - } else if (props.resetFormValsOnSubmit) { - dispatch({ type: "set_form", form: formVals.current }); - } - }; - - const handleCancel = () => { - if (props.resetFormValsOnCancel) { - dispatch({ type: "set_form", form: formVals.current }); - } - props.onCancel?.(); - }; - - const { Provider, Consumer } = useMemo(() => createFormContext(), []); - const disabled = isLoading || props.disabled; - - return ( -
- ) => { - dispatch({ type: "set_state", state: newState }); - props.onDraftRestore?.(newState); - }} - formData={state.form} - hidden={props.hideRestoreDraft} - > - ) => { - return { - name, - id: name, - onChange: ({ name, value }: FieldChangeEvent) => - dispatch({ - type: "set_field", - name, - value, - error: validate?.(value), - }), - value: state.form[name], - error: state.errors[name], - disabled, - }; - }} - > -
- {props.children} -
- {(!hideCancelButton || !hideSubmitButton) && ( -
- {!hideCancelButton && ( - - )} - {!hideSubmitButton && ( - - )} -
- )} -
-
-
- ); -}; - -export default Form; From d2cfca636a07906b5c73a5e63cc203f4096298d4 Mon Sep 17 00:00:00 2001 From: AdityaJ2305 Date: Fri, 3 Jan 2025 20:31:32 +0530 Subject: [PATCH 03/83] fix linting error --- src/components/Users/UserResetPassword.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Users/UserResetPassword.tsx b/src/components/Users/UserResetPassword.tsx index 91ed393eb4a..c4717c55f3d 100644 --- a/src/components/Users/UserResetPassword.tsx +++ b/src/components/Users/UserResetPassword.tsx @@ -83,10 +83,10 @@ export default function UserResetPassword({ }); if (res?.ok) { - Notification.Success({ msg: "Password Updated Successfully" }); + Notification.Success({ msg: data?.message as string }); } else { Notification.Error({ - msg: t("password_update_error"), + msg: error?.message ?? t("password_update_error"), }); } setIsSubmitting(false); From c170f70841b3a80e4aaa8fdceef20d1f46181740 Mon Sep 17 00:00:00 2001 From: AdityaJ2305 Date: Fri, 3 Jan 2025 21:22:21 +0530 Subject: [PATCH 04/83] update to mutate --- src/components/Users/UserResetPassword.tsx | 50 ++++++++++++++-------- 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/src/components/Users/UserResetPassword.tsx b/src/components/Users/UserResetPassword.tsx index c4717c55f3d..de0c35a0f05 100644 --- a/src/components/Users/UserResetPassword.tsx +++ b/src/components/Users/UserResetPassword.tsx @@ -1,4 +1,5 @@ import { zodResolver } from "@hookform/resolvers/zod"; +import { useMutation } from "@tanstack/react-query"; import { useState } from "react"; import { useForm } from "react-hook-form"; import { useTranslation } from "react-i18next"; @@ -22,7 +23,7 @@ import { UpdatePasswordForm } from "@/components/Users/models"; import * as Notification from "@/Utils/Notifications"; import routes from "@/Utils/request/api"; -import request from "@/Utils/request/request"; +import mutate from "@/Utils/request/mutate"; import { UserBase } from "@/types/user/user"; const PasswordSchema = z @@ -55,7 +56,6 @@ export default function UserResetPassword({ userData: UserBase; }) { const { t } = useTranslation(); - const [isSubmitting, setIsSubmitting] = useState(false); const [isEditing, setIsEditing] = useState(false); const [isPasswordFieldFocused, setIsPasswordFieldFocused] = useState(false); @@ -67,29 +67,37 @@ export default function UserResetPassword({ new_password_2: "", }, }); + const { mutate: resetUserPasswordMutate } = useMutation({ + mutationFn: async (formData: UpdatePasswordForm) => { + const response = await mutate(routes.updatePassword, { silent: true })( + formData, + ); + if ("errors" in response) { + throw response; + } + return response; + }, + onSuccess: (data: any) => { + Notification.Success({ msg: data?.message as string }); + resetPasswordForm.reset(); + }, + onError: (error: any) => { + const errorMessage = + error?.response?.data?.message || t("password_update_error"); + Notification.Error({ msg: errorMessage }); + resetPasswordForm.reset(); + }, + }); + const handleSubmitPassword = async ( formData: z.infer, ) => { - setIsSubmitting(true); - const form: UpdatePasswordForm = { old_password: formData.current_password, username: userData.username, new_password: formData.new_password_1, }; - - const { res, data, error } = await request(routes.updatePassword, { - body: form, - }); - - if (res?.ok) { - Notification.Success({ msg: data?.message as string }); - } else { - Notification.Error({ - msg: error?.message ?? t("password_update_error"), - }); - } - setIsSubmitting(false); + resetUserPasswordMutate(form); }; const renderPasswordForm = () => ( @@ -105,6 +113,7 @@ export default function UserResetPassword({ { field.onChange(value.value); }} @@ -125,6 +134,7 @@ export default function UserResetPassword({ { field.onChange(value.value); }} @@ -172,6 +182,7 @@ export default function UserResetPassword({ { field.onChange(value.value); }} @@ -186,15 +197,16 @@ export default function UserResetPassword({ ); + const editButton = () => (
From ee0f76fd186e16a353ca1abeb858881a67670d49 Mon Sep 17 00:00:00 2001 From: AdityaJ2305 Date: Fri, 3 Jan 2025 21:57:48 +0530 Subject: [PATCH 07/83] update the error --- src/components/Users/UserResetPassword.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Users/UserResetPassword.tsx b/src/components/Users/UserResetPassword.tsx index 1b988a90991..6cc9e743164 100644 --- a/src/components/Users/UserResetPassword.tsx +++ b/src/components/Users/UserResetPassword.tsx @@ -83,7 +83,7 @@ export default function UserResetPassword({ }, onError: (error: any) => { const errorMessage = - error?.response?.data?.message || t("password_update_error"); + error.cause.old_password[0] || t("password_update_error"); Notification.Error({ msg: errorMessage }); }, }); From 0da92f6b419b65aa5e1679e8502f0e96c55a59e7 Mon Sep 17 00:00:00 2001 From: AdityaJ2305 Date: Fri, 3 Jan 2025 22:02:24 +0530 Subject: [PATCH 08/83] fix the usemutate --- src/components/Users/UserResetPassword.tsx | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/components/Users/UserResetPassword.tsx b/src/components/Users/UserResetPassword.tsx index 6cc9e743164..892fbd32842 100644 --- a/src/components/Users/UserResetPassword.tsx +++ b/src/components/Users/UserResetPassword.tsx @@ -68,15 +68,7 @@ export default function UserResetPassword({ }, }); const { mutate: resetUserPasswordMutate, isPending } = useMutation({ - mutationFn: async (formData: UpdatePasswordForm) => { - const response = await mutate(routes.updatePassword, { silent: true })( - formData, - ); - if ("errors" in response) { - throw response; - } - return response; - }, + mutationFn: mutate(routes.updatePassword, { silent: true }), onSuccess: (data: any) => { Notification.Success({ msg: data?.message as string }); resetPasswordForm.reset(); From d56f6d3c40aa64a7c07d83aaae39d8b26fdc1b91 Mon Sep 17 00:00:00 2001 From: AdityaJ2305 Date: Fri, 3 Jan 2025 22:08:58 +0530 Subject: [PATCH 09/83] fix the usemutate --- src/components/Users/UserResetPassword.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Users/UserResetPassword.tsx b/src/components/Users/UserResetPassword.tsx index 892fbd32842..a93fadb7b3a 100644 --- a/src/components/Users/UserResetPassword.tsx +++ b/src/components/Users/UserResetPassword.tsx @@ -75,7 +75,7 @@ export default function UserResetPassword({ }, onError: (error: any) => { const errorMessage = - error.cause.old_password[0] || t("password_update_error"); + error.cause?.old_password?.[0] ?? t("password_update_error"); Notification.Error({ msg: errorMessage }); }, }); From fb93eca9b50083f8d63848a3fd4ea9ec3d0fb3b0 Mon Sep 17 00:00:00 2001 From: AdityaJ2305 Date: Fri, 3 Jan 2025 23:18:53 +0530 Subject: [PATCH 10/83] use of toast and update to use translation --- public/locale/en.json | 1 + src/components/Users/UserResetPassword.tsx | 32 ++++++++++++++-------- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/public/locale/en.json b/public/locale/en.json index 02820172fe4..51da82b5a16 100644 --- a/public/locale/en.json +++ b/public/locale/en.json @@ -1374,6 +1374,7 @@ "please_check_your_messages": "Please check your messages", "please_confirm_password": "Please confirm your new password.", "please_enter_a_reason_for_the_shift": "Please enter a reason for the shift.", + "please_enter_confirm_password": "Please confirm your new password", "please_enter_current_password": "Please enter your current password.", "please_enter_new_password": "Please enter your new password.", "please_enter_username": "Please enter the username", diff --git a/src/components/Users/UserResetPassword.tsx b/src/components/Users/UserResetPassword.tsx index a93fadb7b3a..f408dc982b7 100644 --- a/src/components/Users/UserResetPassword.tsx +++ b/src/components/Users/UserResetPassword.tsx @@ -1,8 +1,10 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { useMutation } from "@tanstack/react-query"; +import { t } from "i18next"; import { useState } from "react"; import { useForm } from "react-hook-form"; import { useTranslation } from "react-i18next"; +import { toast } from "sonner"; import { z } from "zod"; import CareIcon from "@/CAREUI/icons/CareIcon"; @@ -21,7 +23,6 @@ import TextFormField from "@/components/Form/FormFields/TextFormField"; import { validateRule } from "@/components/Users/UserFormValidations"; import { UpdatePasswordForm } from "@/components/Users/models"; -import * as Notification from "@/Utils/Notifications"; import routes from "@/Utils/request/api"; import mutate from "@/Utils/request/mutate"; import { UserBase } from "@/types/user/user"; @@ -30,24 +31,28 @@ const PasswordSchema = z .object({ old_password: z .string() - .min(1, { message: "Please enter current password" }), + .min(1, { message: t("please_enter_current_password") }), new_password_1: z .string() - .min(8, { message: "New password must be at least 8 characters long" }) - .regex(/\d/, { message: "Password must contain at least one number" }) + .min(8, { message: t("password_length_validation") }) + .regex(/\d/, { message: t("password_number_validation") }) .regex(/[a-z]/, { - message: "Password must contain at least one lowercase letter", + message: t("password_lowercase_validation"), }) .regex(/[A-Z]/, { - message: "Password must contain at least one uppercase letter", + message: t("password_uppercase_validation"), }), new_password_2: z .string() - .min(1, { message: "Please confirm your new password" }), + .min(1, { message: t("please_enter_confirm_password") }), }) .refine((values) => values.new_password_1 === values.new_password_2, { - message: "New password and confirm password must be the same.", + message: t("password_mismatch"), path: ["new_password_2"], + }) + .refine((values) => values.new_password_1 !== values.old_password, { + message: t("new_password_same_as_old"), + path: ["new_password_1"], }); export default function UserResetPassword({ @@ -70,13 +75,13 @@ export default function UserResetPassword({ const { mutate: resetUserPasswordMutate, isPending } = useMutation({ mutationFn: mutate(routes.updatePassword, { silent: true }), onSuccess: (data: any) => { - Notification.Success({ msg: data?.message as string }); + toast.success(data?.message as string); resetPasswordForm.reset(); }, onError: (error: any) => { const errorMessage = error.cause?.old_password?.[0] ?? t("password_update_error"); - Notification.Error({ msg: errorMessage }); + toast.error(errorMessage); }, }); @@ -108,7 +113,6 @@ export default function UserResetPassword({ onChange={(value) => { field.onChange(value.value); }} - required /> @@ -133,6 +137,11 @@ export default function UserResetPassword({ onBlur={() => setIsPasswordFieldFocused(false)} /> + + {resetPasswordForm.formState.errors?.new_password_1?.message && + resetPasswordForm.formState.errors?.new_password_1?.message.includes( + t("new_password_same_as_old"), + ) && } {isPasswordFieldFocused && (
{ field.onChange(value.value); }} - required /> From ea545c448f1e88ece8b2f575db309f2fe75b6086 Mon Sep 17 00:00:00 2001 From: AdityaJ2305 Date: Fri, 3 Jan 2025 23:30:20 +0530 Subject: [PATCH 11/83] deleted formcontext --- src/components/Form/FormContext.ts | 19 ------------------- src/pluginTypes.ts | 17 ----------------- 2 files changed, 36 deletions(-) delete mode 100644 src/components/Form/FormContext.ts diff --git a/src/components/Form/FormContext.ts b/src/components/Form/FormContext.ts deleted file mode 100644 index 2be5c12234c..00000000000 --- a/src/components/Form/FormContext.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { createContext } from "react"; - -import { FieldError, FieldValidator } from "@/components/Form/FieldValidators"; -import { FormDetails } from "@/components/Form/Utils"; - -export type FormContextValue = ( - name: keyof T, - validate?: FieldValidator, - excludeFromDraft?: boolean, -) => { - id: keyof T; - name: keyof T; - onChange: any; - value: any; - error: FieldError | undefined; -}; - -export const createFormContext = () => - createContext>(undefined as any); diff --git a/src/pluginTypes.ts b/src/pluginTypes.ts index 28511669887..f61caa14794 100644 --- a/src/pluginTypes.ts +++ b/src/pluginTypes.ts @@ -6,7 +6,6 @@ import { UserAssignedModel } from "@/components/Users/models"; import { EncounterTabProps } from "@/pages/Encounters/EncounterShow"; import { AppRoutes } from "./Routers/AppRouter"; -import { FormContextValue } from "./components/Form/FormContext"; import { PatientMeta } from "./components/Patient/models"; import { pluginMap } from "./pluginMap"; import { PatientModel } from "./types/emr/patient"; @@ -27,21 +26,6 @@ export type ExtendFacilityConfigureComponentType = React.FC<{ facilityId: string; }>; -export type ExtendPatientRegisterFormComponentType = React.FC<{ - facilityId: string; - patientId?: string; - state: { - form: { - [key: string]: any; - }; - errors: { - [key: string]: string; - }; - }; - dispatch: React.Dispatch; - field: FormContextValue; -}>; - // Define supported plugin components export type SupportedPluginComponents = { DoctorConnectButtons: DoctorConnectButtonComponentType; @@ -49,7 +33,6 @@ export type SupportedPluginComponents = { ManageFacilityOptions: ManageFacilityOptionsComponentType; EncounterContextEnabler: React.FC; ExtendFacilityConfigure: ExtendFacilityConfigureComponentType; - ExtendPatientRegisterForm: ExtendPatientRegisterFormComponentType; }; // Create a type for lazy-loaded components From ce9f885cc6b71f24e4fdb1775967d757c95bdcdd Mon Sep 17 00:00:00 2001 From: AdityaJ2305 Date: Sat, 4 Jan 2025 00:19:58 +0530 Subject: [PATCH 12/83] update global error handler --- src/Utils/request/errorHandler.ts | 105 +++++++++++---------- src/components/Users/UserResetPassword.tsx | 7 +- 2 files changed, 56 insertions(+), 56 deletions(-) diff --git a/src/Utils/request/errorHandler.ts b/src/Utils/request/errorHandler.ts index ef2eba8bfe8..a763e4aca9e 100644 --- a/src/Utils/request/errorHandler.ts +++ b/src/Utils/request/errorHandler.ts @@ -32,68 +32,73 @@ export function handleHttpError(error: Error) { if (isBadRequest(error)) { const errs = cause?.errors; - if (isPydanticError(errs)) { + if (errs && isPydanticError(errs)) { handlePydanticErrors(errs); return; } - Notifications.BadRequest({ errs }); + if (cause && typeof cause === "object" && !Array.isArray(cause)) { + const firstKey = Object.keys(cause)[0]; + const firstError = firstKey ? cause[firstKey] : null; + if (firstError && Array.isArray(firstError)) { + Notifications.Error({ msg: firstError[0] }); + return; + } + } + Notifications.BadRequest({ errs: cause }); return; } - Notifications.Error({ - msg: cause?.detail || "Something went wrong...!", - }); -} - -function isSessionExpired(error: HTTPError["cause"]) { - return ( - // If Authorization header is not valid - error?.code === "token_not_valid" || - // If Authorization header is not provided - error?.detail === "Authentication credentials were not provided." - ); -} + function isSessionExpired(error: HTTPError["cause"]) { + return ( + // If Authorization header is not valid + error?.code === "token_not_valid" || + // If Authorization header is not provided + error?.detail === "Authentication credentials were not provided." + ); + } -function handleSessionExpired() { - if (!location.pathname.startsWith("/session-expired")) { - navigate(`/session-expired?redirect=${window.location.href}`); + function handleSessionExpired() { + if (!location.pathname.startsWith("/session-expired")) { + navigate(`/session-expired?redirect=${window.location.href}`); + } } -} -function isBadRequest(error: HTTPError) { - return error.status === 400 || error.status === 406; -} + function isBadRequest(error: HTTPError) { + return error.status === 400 || error.status === 406; + } -function isNotFound(error: HTTPError) { - return error.status === 404; -} + function isNotFound(error: HTTPError) { + return error.status === 404; + } -type PydanticError = { - type: string; - loc: string[]; - msg: string; - input: unknown; - url: string; -}; - -function isPydanticError(errors: unknown): errors is PydanticError[] { - return ( - Array.isArray(errors) && - errors.every( - (error) => typeof error === "object" && error !== null && "type" in error, - ) - ); -} + type PydanticError = { + type: string; + loc: string[]; + msg: string; + input: unknown; + url: string; + }; + + function isPydanticError(errors: unknown): errors is PydanticError[] { + return ( + Array.isArray(errors) && + errors.every( + (error) => + typeof error === "object" && error !== null && "type" in error, + ) + ); + } -function handlePydanticErrors(errors: PydanticError[]) { - errors.map(({ type, loc, msg }) => { - const title = type - .replace("_", " ") - .replace(/\b\w/g, (char) => char.toUpperCase()); + function handlePydanticErrors(errors: PydanticError[]) { + errors.map(({ type, loc, msg }) => { + const title = type + .replace("_", " ") + .replace(/\b\w/g, (char) => char.toUpperCase()); - toast.error(`${title}: '${loc.join(".")}'`, { - description: msg, - duration: 8000, + toast.error(`${title}: '${loc.join(".")}'`, { + description: msg, + duration: 8000, + }); }); - }); + } } diff --git a/src/components/Users/UserResetPassword.tsx b/src/components/Users/UserResetPassword.tsx index f408dc982b7..959bb85ae9b 100644 --- a/src/components/Users/UserResetPassword.tsx +++ b/src/components/Users/UserResetPassword.tsx @@ -73,16 +73,11 @@ export default function UserResetPassword({ }, }); const { mutate: resetUserPasswordMutate, isPending } = useMutation({ - mutationFn: mutate(routes.updatePassword, { silent: true }), + mutationFn: mutate(routes.updatePassword), onSuccess: (data: any) => { toast.success(data?.message as string); resetPasswordForm.reset(); }, - onError: (error: any) => { - const errorMessage = - error.cause?.old_password?.[0] ?? t("password_update_error"); - toast.error(errorMessage); - }, }); const handleSubmitPassword = async ( From 55828977a4af46f0044ea8414aaf85f17b6d2897 Mon Sep 17 00:00:00 2001 From: AdityaJ2305 Date: Sat, 4 Jan 2025 00:32:42 +0530 Subject: [PATCH 13/83] update global error handler --- src/Utils/request/errorHandler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Utils/request/errorHandler.ts b/src/Utils/request/errorHandler.ts index a763e4aca9e..06252a63b62 100644 --- a/src/Utils/request/errorHandler.ts +++ b/src/Utils/request/errorHandler.ts @@ -40,7 +40,7 @@ export function handleHttpError(error: Error) { const firstKey = Object.keys(cause)[0]; const firstError = firstKey ? cause[firstKey] : null; if (firstError && Array.isArray(firstError)) { - Notifications.Error({ msg: firstError[0] }); + toast.error(firstError[0]); return; } } From 1b6279a415be7bd1deec3588109c674d96c9102e Mon Sep 17 00:00:00 2001 From: AdityaJ2305 Date: Sat, 4 Jan 2025 01:22:31 +0530 Subject: [PATCH 14/83] fix bracket --- src/Utils/request/errorHandler.ts | 93 ++++++++++++++++--------------- 1 file changed, 48 insertions(+), 45 deletions(-) diff --git a/src/Utils/request/errorHandler.ts b/src/Utils/request/errorHandler.ts index 06252a63b62..897aaaebbf6 100644 --- a/src/Utils/request/errorHandler.ts +++ b/src/Utils/request/errorHandler.ts @@ -48,57 +48,60 @@ export function handleHttpError(error: Error) { return; } - function isSessionExpired(error: HTTPError["cause"]) { - return ( - // If Authorization header is not valid - error?.code === "token_not_valid" || - // If Authorization header is not provided - error?.detail === "Authentication credentials were not provided." - ); - } + Notifications.Error({ + msg: cause?.detail || "Something went wrong...!", + }); +} - function handleSessionExpired() { - if (!location.pathname.startsWith("/session-expired")) { - navigate(`/session-expired?redirect=${window.location.href}`); - } - } +function isSessionExpired(error: HTTPError["cause"]) { + return ( + // If Authorization header is not valid + error?.code === "token_not_valid" || + // If Authorization header is not provided + error?.detail === "Authentication credentials were not provided." + ); +} - function isBadRequest(error: HTTPError) { - return error.status === 400 || error.status === 406; +function handleSessionExpired() { + if (!location.pathname.startsWith("/session-expired")) { + navigate(`/session-expired?redirect=${window.location.href}`); } +} - function isNotFound(error: HTTPError) { - return error.status === 404; - } +function isBadRequest(error: HTTPError) { + return error.status === 400 || error.status === 406; +} - type PydanticError = { - type: string; - loc: string[]; - msg: string; - input: unknown; - url: string; - }; - - function isPydanticError(errors: unknown): errors is PydanticError[] { - return ( - Array.isArray(errors) && - errors.every( - (error) => - typeof error === "object" && error !== null && "type" in error, - ) - ); - } +function isNotFound(error: HTTPError) { + return error.status === 404; +} - function handlePydanticErrors(errors: PydanticError[]) { - errors.map(({ type, loc, msg }) => { - const title = type - .replace("_", " ") - .replace(/\b\w/g, (char) => char.toUpperCase()); +type PydanticError = { + type: string; + loc: string[]; + msg: string; + input: unknown; + url: string; +}; + +function isPydanticError(errors: unknown): errors is PydanticError[] { + return ( + Array.isArray(errors) && + errors.every( + (error) => typeof error === "object" && error !== null && "type" in error, + ) + ); +} + +function handlePydanticErrors(errors: PydanticError[]) { + errors.map(({ type, loc, msg }) => { + const title = type + .replace("_", " ") + .replace(/\b\w/g, (char) => char.toUpperCase()); - toast.error(`${title}: '${loc.join(".")}'`, { - description: msg, - duration: 8000, - }); + toast.error(`${title}: '${loc.join(".")}'`, { + description: msg, + duration: 8000, }); - } + }); } From 40081615381e8cdc0169a54e5c860c31fdf822f4 Mon Sep 17 00:00:00 2001 From: AdityaJ2305 Date: Sat, 4 Jan 2025 01:30:35 +0530 Subject: [PATCH 15/83] fix bracket --- src/Utils/request/errorHandler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Utils/request/errorHandler.ts b/src/Utils/request/errorHandler.ts index 897aaaebbf6..46fa7a43a16 100644 --- a/src/Utils/request/errorHandler.ts +++ b/src/Utils/request/errorHandler.ts @@ -44,7 +44,7 @@ export function handleHttpError(error: Error) { return; } } - Notifications.BadRequest({ errs: cause }); + Notifications.BadRequest({ errs: errs }); return; } From cea12051020b66bdff5f3b8a19f78319be318fca Mon Sep 17 00:00:00 2001 From: Aditya Jindal Date: Sat, 4 Jan 2025 01:31:34 +0530 Subject: [PATCH 16/83] Update src/Utils/request/errorHandler.ts Co-authored-by: Rithvik Nishad --- src/Utils/request/errorHandler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Utils/request/errorHandler.ts b/src/Utils/request/errorHandler.ts index 46fa7a43a16..dd5fbbf49c0 100644 --- a/src/Utils/request/errorHandler.ts +++ b/src/Utils/request/errorHandler.ts @@ -32,7 +32,7 @@ export function handleHttpError(error: Error) { if (isBadRequest(error)) { const errs = cause?.errors; - if (errs && isPydanticError(errs)) { + if (isPydanticError(errs)) { handlePydanticErrors(errs); return; } From 499ec49104617450b5b0ae4d084cd28f726aaa35 Mon Sep 17 00:00:00 2001 From: AdityaJ2305 Date: Sat, 4 Jan 2025 01:39:42 +0530 Subject: [PATCH 17/83] move to seperate func --- src/Utils/request/errorHandler.ts | 37 +++++++++++++++++++------------ 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/src/Utils/request/errorHandler.ts b/src/Utils/request/errorHandler.ts index dd5fbbf49c0..daad3bf52ed 100644 --- a/src/Utils/request/errorHandler.ts +++ b/src/Utils/request/errorHandler.ts @@ -31,20 +31,7 @@ export function handleHttpError(error: Error) { } if (isBadRequest(error)) { - const errs = cause?.errors; - if (isPydanticError(errs)) { - handlePydanticErrors(errs); - return; - } - if (cause && typeof cause === "object" && !Array.isArray(cause)) { - const firstKey = Object.keys(cause)[0]; - const firstError = firstKey ? cause[firstKey] : null; - if (firstError && Array.isArray(firstError)) { - toast.error(firstError[0]); - return; - } - } - Notifications.BadRequest({ errs: errs }); + handleBadRequest(cause); return; } @@ -105,3 +92,25 @@ function handlePydanticErrors(errors: PydanticError[]) { }); }); } + +function handleBadRequest(cause: any) { + const errs = cause?.errors; + + if (isPydanticError(errs)) { + handlePydanticErrors(errs); + return; + } + + if (cause && typeof cause === "object" && !Array.isArray(cause)) { + Object.entries(cause).forEach(([key, value]) => { + if (Array.isArray(value)) { + value.forEach((err) => { + toast.error(`${err}`); + }); + } + }); + return; + } + + Notifications.BadRequest({ errs: cause }); +} \ No newline at end of file From 812d3415dae40b83a0211b49543abdb6a25c0c18 Mon Sep 17 00:00:00 2001 From: AdityaJ2305 Date: Sat, 4 Jan 2025 01:47:00 +0530 Subject: [PATCH 18/83] lint fixed --- src/Utils/request/errorHandler.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Utils/request/errorHandler.ts b/src/Utils/request/errorHandler.ts index daad3bf52ed..0060aba68fe 100644 --- a/src/Utils/request/errorHandler.ts +++ b/src/Utils/request/errorHandler.ts @@ -102,7 +102,7 @@ function handleBadRequest(cause: any) { } if (cause && typeof cause === "object" && !Array.isArray(cause)) { - Object.entries(cause).forEach(([key, value]) => { + Object.values(cause).forEach((value) => { if (Array.isArray(value)) { value.forEach((err) => { toast.error(`${err}`); @@ -113,4 +113,4 @@ function handleBadRequest(cause: any) { } Notifications.BadRequest({ errs: cause }); -} \ No newline at end of file +} From 5f9432e0f04253c6abb01602541c9ea8f69cb3b2 Mon Sep 17 00:00:00 2001 From: AdityaJ2305 Date: Sun, 5 Jan 2025 19:13:52 +0530 Subject: [PATCH 19/83] fix the types and typo changes and strucutured code --- src/Utils/request/errorHandler.ts | 55 +++-- src/Utils/request/types.ts | 2 +- src/components/Users/UserResetPassword.tsx | 250 ++++++++++----------- 3 files changed, 155 insertions(+), 152 deletions(-) diff --git a/src/Utils/request/errorHandler.ts b/src/Utils/request/errorHandler.ts index 0060aba68fe..2d4ac92aabe 100644 --- a/src/Utils/request/errorHandler.ts +++ b/src/Utils/request/errorHandler.ts @@ -2,7 +2,7 @@ import { navigate } from "raviger"; import { toast } from "sonner"; import * as Notifications from "@/Utils/Notifications"; -import { HTTPError } from "@/Utils/request/types"; +import { HTTPError, HTTPErrorCause } from "@/Utils/request/types"; export function handleHttpError(error: Error) { if (error.name === "AbortError") { @@ -31,7 +31,18 @@ export function handleHttpError(error: Error) { } if (isBadRequest(error)) { - handleBadRequest(cause); + const errs = cause?.errors; + if (isPydanticError(errs)) { + handlePydanticErrors(errs); + return; + } + + if (isStructuredError(cause)) { + handleStructuredErrors(cause); + return; + } + + Notifications.BadRequest({ errs }); return; } @@ -71,6 +82,24 @@ type PydanticError = { url: string; }; +function isStructuredError(cause: HTTPErrorCause): boolean { + return ( + cause !== undefined && typeof cause === "object" && !Array.isArray(cause) + ); +} + +function handleStructuredErrors(cause: HTTPErrorCause) { + if (cause && typeof cause === "object" && !Array.isArray(cause)) { + Object.values(cause).forEach((value) => { + if (Array.isArray(value)) { + value.forEach((err) => { + toast.error(`${err}`); + }); + } + }); + } +} + function isPydanticError(errors: unknown): errors is PydanticError[] { return ( Array.isArray(errors) && @@ -92,25 +121,3 @@ function handlePydanticErrors(errors: PydanticError[]) { }); }); } - -function handleBadRequest(cause: any) { - const errs = cause?.errors; - - if (isPydanticError(errs)) { - handlePydanticErrors(errs); - return; - } - - if (cause && typeof cause === "object" && !Array.isArray(cause)) { - Object.values(cause).forEach((value) => { - if (Array.isArray(value)) { - value.forEach((err) => { - toast.error(`${err}`); - }); - } - }); - return; - } - - Notifications.BadRequest({ errs: cause }); -} diff --git a/src/Utils/request/types.ts b/src/Utils/request/types.ts index bdb1038ed38..89155591743 100644 --- a/src/Utils/request/types.ts +++ b/src/Utils/request/types.ts @@ -51,7 +51,7 @@ export interface APICallOptions { headers?: HeadersInit; } -type HTTPErrorCause = Record | undefined; +export type HTTPErrorCause = Record | undefined; export class HTTPError extends Error { status: number; diff --git a/src/components/Users/UserResetPassword.tsx b/src/components/Users/UserResetPassword.tsx index 959bb85ae9b..5ba8c5cd06f 100644 --- a/src/components/Users/UserResetPassword.tsx +++ b/src/components/Users/UserResetPassword.tsx @@ -64,7 +64,7 @@ export default function UserResetPassword({ const [isEditing, setIsEditing] = useState(false); const [isPasswordFieldFocused, setIsPasswordFieldFocused] = useState(false); - const resetPasswordForm = useForm({ + const form = useForm({ resolver: zodResolver(PasswordSchema), defaultValues: { old_password: "", @@ -72,11 +72,11 @@ export default function UserResetPassword({ new_password_2: "", }, }); - const { mutate: resetUserPasswordMutate, isPending } = useMutation({ + const { mutate: resetPassword, isPending } = useMutation({ mutationFn: mutate(routes.updatePassword), - onSuccess: (data: any) => { + onSuccess: (data: Record) => { toast.success(data?.message as string); - resetPasswordForm.reset(); + form.reset(); }, }); @@ -88,137 +88,133 @@ export default function UserResetPassword({ username: userData.username, new_password: formData.new_password_1, }; - resetUserPasswordMutate(form); + resetPassword(form); }; - const renderPasswordForm = () => ( -
- -
- ( - - {t("old_password")} - - { - field.onChange(value.value); - }} - /> - - - - )} + return ( +
+
+ +
+ {isEditing && ( + + +
+ ( + + {t("old_password")} + + { + field.onChange(value.value); + }} + /> + + + + )} + /> - ( - - {t("new_password")} - - { - field.onChange(value.value); - }} - onFocus={() => setIsPasswordFieldFocused(true)} - onBlur={() => setIsPasswordFieldFocused(false)} - /> - - - {resetPasswordForm.formState.errors?.new_password_1?.message && - resetPasswordForm.formState.errors?.new_password_1?.message.includes( - t("new_password_same_as_old"), - ) && } - {isPasswordFieldFocused && ( -
- {validateRule( - field.value.length >= 8, - t("password_length_validation"), - !field.value, - )} - {validateRule( - /[a-z]/.test(field.value), - t("password_lowercase_validation"), - !field.value, - )} - {validateRule( - /[A-Z]/.test(field.value), - t("password_uppercase_validation"), - !field.value, + ( + + {t("new_password")} + + { + field.onChange(value.value); + }} + onFocus={() => setIsPasswordFieldFocused(true)} + onBlur={() => setIsPasswordFieldFocused(false)} + /> + + {form.formState.errors?.new_password_1?.message && + form.formState.errors?.new_password_1?.message.includes( + t("new_password_same_as_old"), + ) && } + {isPasswordFieldFocused && ( +
+ {validateRule( + field.value.length >= 8, + t("password_length_validation"), + !field.value, + )} + {validateRule( + /[a-z]/.test(field.value), + t("password_lowercase_validation"), + !field.value, + )} + {validateRule( + /[A-Z]/.test(field.value), + t("password_uppercase_validation"), + !field.value, + )} + {validateRule( + /\d/.test(field.value), + t("password_number_validation"), + !field.value, + )} +
)} - {validateRule( - /\d/.test(field.value), - t("password_number_validation"), - !field.value, - )} -
+
)} - - )} - /> + /> - ( - - {t("new_password_confirmation")} - - { - field.onChange(value.value); - }} - /> - - - - )} - /> -
- - - - - ); - - const editButton = () => ( -
- -
- ); + ( + + {t("new_password_confirmation")} + + { + field.onChange(value.value); + }} + /> + + + + )} + /> +
- return ( -
- {editButton()} - {isEditing && renderPasswordForm()} + + + + )}
); } From 9728bbed3c92300996fe811c293e8ad1db947273 Mon Sep 17 00:00:00 2001 From: AdityaJ2305 Date: Sun, 5 Jan 2025 21:52:16 +0530 Subject: [PATCH 20/83] fix the string error hadler --- src/Utils/request/errorHandler.ts | 12 ++++++------ src/Utils/request/types.ts | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Utils/request/errorHandler.ts b/src/Utils/request/errorHandler.ts index 2d4ac92aabe..c3323621ccc 100644 --- a/src/Utils/request/errorHandler.ts +++ b/src/Utils/request/errorHandler.ts @@ -2,7 +2,7 @@ import { navigate } from "raviger"; import { toast } from "sonner"; import * as Notifications from "@/Utils/Notifications"; -import { HTTPError, HTTPErrorCause } from "@/Utils/request/types"; +import { HTTPError } from "@/Utils/request/types"; export function handleHttpError(error: Error) { if (error.name === "AbortError") { @@ -82,19 +82,19 @@ type PydanticError = { url: string; }; -function isStructuredError(cause: HTTPErrorCause): boolean { - return ( - cause !== undefined && typeof cause === "object" && !Array.isArray(cause) - ); +function isStructuredError(cause: HTTPError["cause"]): boolean { + return typeof cause === "object" && !Array.isArray(cause); } -function handleStructuredErrors(cause: HTTPErrorCause) { +function handleStructuredErrors(cause: HTTPError["cause"]) { if (cause && typeof cause === "object" && !Array.isArray(cause)) { Object.values(cause).forEach((value) => { if (Array.isArray(value)) { value.forEach((err) => { toast.error(`${err}`); }); + } else if (typeof value === "string") { + toast.error(value); } }); } diff --git a/src/Utils/request/types.ts b/src/Utils/request/types.ts index 89155591743..bdb1038ed38 100644 --- a/src/Utils/request/types.ts +++ b/src/Utils/request/types.ts @@ -51,7 +51,7 @@ export interface APICallOptions { headers?: HeadersInit; } -export type HTTPErrorCause = Record | undefined; +type HTTPErrorCause = Record | undefined; export class HTTPError extends Error { status: number; From d2250a6175e30bc4aca60ff04a3a77a3c6acae38 Mon Sep 17 00:00:00 2001 From: AdityaJ2305 Date: Sun, 5 Jan 2025 22:07:04 +0530 Subject: [PATCH 21/83] fix the structure of handlers --- src/Utils/request/errorHandler.ts | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Utils/request/errorHandler.ts b/src/Utils/request/errorHandler.ts index c3323621ccc..29b20389ef3 100644 --- a/src/Utils/request/errorHandler.ts +++ b/src/Utils/request/errorHandler.ts @@ -82,21 +82,21 @@ type PydanticError = { url: string; }; -function isStructuredError(cause: HTTPError["cause"]): boolean { +function isStructuredError( + cause: HTTPError["cause"], +): cause is Record { return typeof cause === "object" && !Array.isArray(cause); } - -function handleStructuredErrors(cause: HTTPError["cause"]) { - if (cause && typeof cause === "object" && !Array.isArray(cause)) { - Object.values(cause).forEach((value) => { - if (Array.isArray(value)) { - value.forEach((err) => { - toast.error(`${err}`); - }); - } else if (typeof value === "string") { - toast.error(value); - } - }); +function handleStructuredErrors(cause: Record) { + for (const value of Object.values(cause)) { + if (Array.isArray(value)) { + value.forEach((err) => toast.error(err)); + return; + } + if (typeof value === "string") { + toast.error(value); + return; + } } } From 3b4edfed0dcd21cbace578ddc1ab46f8e54c9fba Mon Sep 17 00:00:00 2001 From: AdityaJ2305 Date: Sun, 5 Jan 2025 22:15:20 +0530 Subject: [PATCH 22/83] Empty-Commit From 86a97b011e67a34e62c8ad5450c8d9991f51be91 Mon Sep 17 00:00:00 2001 From: AdityaJ2305 Date: Sun, 5 Jan 2025 22:22:12 +0530 Subject: [PATCH 23/83] fix merge conflicts --- src/components/Form/Form.tsx | 169 ++++++++++ src/components/Users/UserResetPassword.tsx | 360 ++++++++++----------- 2 files changed, 347 insertions(+), 182 deletions(-) create mode 100644 src/components/Form/Form.tsx diff --git a/src/components/Form/Form.tsx b/src/components/Form/Form.tsx new file mode 100644 index 00000000000..38a4cb0818b --- /dev/null +++ b/src/components/Form/Form.tsx @@ -0,0 +1,169 @@ +import { useEffect, useMemo, useRef, useState } from "react"; + +import { Button } from "@/components/ui/button"; + +import { FieldValidator } from "@/components/Form/FieldValidators"; +import { + FormContextValue, + createFormContext, +} from "@/components/Form/FormContext"; +import { FieldChangeEvent } from "@/components/Form/FormFields/Utils"; +import { + FormDetails, + FormErrors, + FormState, + formReducer, +} from "@/components/Form/Utils"; + +import { DraftSection, useAutoSaveReducer } from "@/Utils/AutoSave"; +import * as Notification from "@/Utils/Notifications"; +import { classNames, isEmpty, omitBy } from "@/Utils/utils"; + +type Props = { + className?: string; + defaults: T; + asyncGetDefaults?: (() => Promise) | false; + validate?: (form: T) => FormErrors; + onSubmit: (form: T) => Promise | void>; + onCancel?: () => void; + noPadding?: true; + disabled?: boolean; + submitLabel?: string; + cancelLabel?: string; + onDraftRestore?: (newState: FormState) => void; + children: (props: FormContextValue) => React.ReactNode; + hideRestoreDraft?: boolean; + resetFormValsOnCancel?: boolean; + resetFormValsOnSubmit?: boolean; + hideCancelButton?: boolean; + submitButtonClassName?: string; + hideSubmitButton?: boolean; + disableMarginOnChildren?: boolean; +}; + +const Form = ({ + asyncGetDefaults, + validate, + hideCancelButton = false, + hideSubmitButton = false, + disableMarginOnChildren = false, + ...props +}: Props) => { + const initial = { form: props.defaults, errors: {} }; + const [isLoading, setIsLoading] = useState(!!asyncGetDefaults); + const [state, dispatch] = useAutoSaveReducer(formReducer, initial); + const formVals = useRef(props.defaults); + + useEffect(() => { + if (!asyncGetDefaults) return; + + asyncGetDefaults().then((form) => { + dispatch({ type: "set_form", form }); + setIsLoading(false); + }); + }, [asyncGetDefaults]); + + const handleSubmit = async (event: React.FormEvent) => { + event.preventDefault(); + event.stopPropagation(); + + if (validate) { + const errors = omitBy(validate(state.form), isEmpty) as FormErrors; + + if (Object.keys(errors).length) { + dispatch({ type: "set_errors", errors }); + + if (errors.$all) { + Notification.Error({ msg: errors.$all }); + } + return; + } + } + + const errors = await props.onSubmit(state.form); + if (errors) { + dispatch({ + type: "set_errors", + errors: { ...state.errors, ...errors }, + }); + } else if (props.resetFormValsOnSubmit) { + dispatch({ type: "set_form", form: formVals.current }); + } + }; + + const handleCancel = () => { + if (props.resetFormValsOnCancel) { + dispatch({ type: "set_form", form: formVals.current }); + } + props.onCancel?.(); + }; + + const { Provider, Consumer } = useMemo(() => createFormContext(), []); + const disabled = isLoading || props.disabled; + + return ( +
+ ) => { + dispatch({ type: "set_state", state: newState }); + props.onDraftRestore?.(newState); + }} + formData={state.form} + hidden={props.hideRestoreDraft} + > + ) => { + return { + name, + id: name, + onChange: ({ name, value }: FieldChangeEvent) => + dispatch({ + type: "set_field", + name, + value, + error: validate?.(value), + }), + value: state.form[name], + error: state.errors[name], + disabled, + }; + }} + > +
+ {props.children} +
+ {(!hideCancelButton || !hideSubmitButton) && ( +
+ {!hideCancelButton && ( + + )} + {!hideSubmitButton && ( + + )} +
+ )} +
+
+
+ ); +}; + +export default Form; diff --git a/src/components/Users/UserResetPassword.tsx b/src/components/Users/UserResetPassword.tsx index 5ba8c5cd06f..2ceae7314ae 100644 --- a/src/components/Users/UserResetPassword.tsx +++ b/src/components/Users/UserResetPassword.tsx @@ -1,59 +1,26 @@ -import { zodResolver } from "@hookform/resolvers/zod"; -import { useMutation } from "@tanstack/react-query"; -import { t } from "i18next"; import { useState } from "react"; -import { useForm } from "react-hook-form"; import { useTranslation } from "react-i18next"; -import { toast } from "sonner"; -import { z } from "zod"; import CareIcon from "@/CAREUI/icons/CareIcon"; import { Button } from "@/components/ui/button"; -import { - Form, - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, -} from "@/components/ui/form"; +import Form from "@/components/Form/Form"; import TextFormField from "@/components/Form/FormFields/TextFormField"; import { validateRule } from "@/components/Users/UserFormValidations"; import { UpdatePasswordForm } from "@/components/Users/models"; +import * as Notification from "@/Utils/Notifications"; import routes from "@/Utils/request/api"; -import mutate from "@/Utils/request/mutate"; +import request from "@/Utils/request/request"; import { UserBase } from "@/types/user/user"; -const PasswordSchema = z - .object({ - old_password: z - .string() - .min(1, { message: t("please_enter_current_password") }), - new_password_1: z - .string() - .min(8, { message: t("password_length_validation") }) - .regex(/\d/, { message: t("password_number_validation") }) - .regex(/[a-z]/, { - message: t("password_lowercase_validation"), - }) - .regex(/[A-Z]/, { - message: t("password_uppercase_validation"), - }), - new_password_2: z - .string() - .min(1, { message: t("please_enter_confirm_password") }), - }) - .refine((values) => values.new_password_1 === values.new_password_2, { - message: t("password_mismatch"), - path: ["new_password_2"], - }) - .refine((values) => values.new_password_1 !== values.old_password, { - message: t("new_password_same_as_old"), - path: ["new_password_1"], - }); +interface PasswordForm { + username: string; + old_password: string; + new_password_1: string; + new_password_2: string; +} export default function UserResetPassword({ userData, @@ -61,160 +28,189 @@ export default function UserResetPassword({ userData: UserBase; }) { const { t } = useTranslation(); + const [isSubmitting, setisSubmitting] = useState(false); const [isEditing, setIsEditing] = useState(false); - const [isPasswordFieldFocused, setIsPasswordFieldFocused] = useState(false); - - const form = useForm({ - resolver: zodResolver(PasswordSchema), - defaultValues: { - old_password: "", - new_password_1: "", - new_password_2: "", - }, - }); - const { mutate: resetPassword, isPending } = useMutation({ - mutationFn: mutate(routes.updatePassword), - onSuccess: (data: Record) => { - toast.success(data?.message as string); - form.reset(); - }, - }); - - const handleSubmitPassword = async ( - formData: z.infer, - ) => { + + const initForm: PasswordForm = { + username: userData.username, + old_password: "", + new_password_1: "", + new_password_2: "", + }; + + const validateNewPassword = (password: string) => { + if ( + password.length < 8 || + !/\d/.test(password) || + password === password.toUpperCase() || + password === password.toLowerCase() + ) { + return false; + } + return true; + }; + + const validateForm = (formData: PasswordForm) => { + const errors: Partial> = {}; + + if (!formData.old_password) { + errors.old_password = t("please_enter_current_password"); + } + + if (!formData.new_password_1) { + errors.new_password_1 = t("please_enter_new_password"); + } else if (!validateNewPassword(formData.new_password_1)) { + errors.new_password_1 = t("new_password_validation"); + } + + if (!formData.new_password_2) { + errors.new_password_2 = t("please_confirm_password"); + } else if (formData.new_password_1 !== formData.new_password_2) { + errors.new_password_2 = t("password_mismatch"); + } + + if (formData.new_password_1 === formData.old_password) { + errors.new_password_1 = t("new_password_same_as_old"); + } + + return errors; + }; + + const handleSubmit = async (formData: PasswordForm) => { + setisSubmitting(true); const form: UpdatePasswordForm = { old_password: formData.old_password, username: userData.username, new_password: formData.new_password_1, }; - resetPassword(form); - }; - return ( -
-
- -
- {isEditing && ( -
- -
- ( - - {t("old_password")} - - { - field.onChange(value.value); - }} - /> - - - - )} - /> + const { res, data, error } = await request(routes.updatePassword, { + body: form, + }); + + if (res?.ok) { + Notification.Success({ msg: data?.message as string }); + } else { + Notification.Error({ + msg: error?.message ?? t("password_update_error"), + }); + } + setisSubmitting(false); + }; - { + return ( + + defaults={initForm} + validate={validateForm} + onSubmit={handleSubmit} + resetFormValsOnCancel + resetFormValsOnSubmit + hideRestoreDraft + noPadding + disabled={isSubmitting} + hideCancelButton + > + {(field) => ( +
+ +
+ ( - - {t("new_password")} - - { - field.onChange(value.value); - }} - onFocus={() => setIsPasswordFieldFocused(true)} - onBlur={() => setIsPasswordFieldFocused(false)} - /> - - {form.formState.errors?.new_password_1?.message && - form.formState.errors?.new_password_1?.message.includes( - t("new_password_same_as_old"), - ) && } - {isPasswordFieldFocused && ( -
- {validateRule( - field.value.length >= 8, - t("password_length_validation"), - !field.value, - )} - {validateRule( - /[a-z]/.test(field.value), - t("password_lowercase_validation"), - !field.value, - )} - {validateRule( - /[A-Z]/.test(field.value), - t("password_uppercase_validation"), - !field.value, - )} - {validateRule( - /\d/.test(field.value), - t("password_number_validation"), - !field.value, - )} -
- )} -
- )} + label={t("new_password")} + type="password" + className="peer col-span-6 sm:col-span-3" + required + aria-label={t("new_password")} /> - - ( - - {t("new_password_confirmation")} - - { - field.onChange(value.value); - }} - /> - - - +
+ {validateRule( + field("new_password_1").value?.length >= 8, + t("password_length_validation"), + !field("new_password_1").value, + )} + {validateRule( + field("new_password_1").value !== + field("new_password_1").value?.toUpperCase(), + t("password_lowercase_validation"), + !field("new_password_1").value, + )} + {validateRule( + field("new_password_1").value !== + field("new_password_1").value?.toLowerCase(), + t("password_uppercase_validation"), + !field("new_password_1").value, + )} + {validateRule( + /\d/.test(field("new_password_1").value ?? ""), + t("password_number_validation"), + !field("new_password_1").value, )} +
+
+
+ + {field("new_password_2").value?.length > 0 && ( +
+ {validateRule( + field("new_password_1").value === + field("new_password_2").value, + t("password_mismatch"), + !field("new_password_2").value, + )} +
+ )}
+
+ )} + + ); + }; + + const editButton = () => ( +
+ +
+ ); - - - - )} + return ( +
+ {editButton()} + {isEditing && renderPasswordForm()}
); } From fa1780c3fb1c08bb3f3fe0a24fa08cf837676fd5 Mon Sep 17 00:00:00 2001 From: AdityaJ2305 Date: Sun, 5 Jan 2025 22:23:37 +0530 Subject: [PATCH 24/83] fix merge conflicts --- src/components/Form/Form.tsx | 169 ---------- src/components/Users/UserResetPassword.tsx | 360 +++++++++++---------- 2 files changed, 182 insertions(+), 347 deletions(-) delete mode 100644 src/components/Form/Form.tsx diff --git a/src/components/Form/Form.tsx b/src/components/Form/Form.tsx deleted file mode 100644 index 38a4cb0818b..00000000000 --- a/src/components/Form/Form.tsx +++ /dev/null @@ -1,169 +0,0 @@ -import { useEffect, useMemo, useRef, useState } from "react"; - -import { Button } from "@/components/ui/button"; - -import { FieldValidator } from "@/components/Form/FieldValidators"; -import { - FormContextValue, - createFormContext, -} from "@/components/Form/FormContext"; -import { FieldChangeEvent } from "@/components/Form/FormFields/Utils"; -import { - FormDetails, - FormErrors, - FormState, - formReducer, -} from "@/components/Form/Utils"; - -import { DraftSection, useAutoSaveReducer } from "@/Utils/AutoSave"; -import * as Notification from "@/Utils/Notifications"; -import { classNames, isEmpty, omitBy } from "@/Utils/utils"; - -type Props = { - className?: string; - defaults: T; - asyncGetDefaults?: (() => Promise) | false; - validate?: (form: T) => FormErrors; - onSubmit: (form: T) => Promise | void>; - onCancel?: () => void; - noPadding?: true; - disabled?: boolean; - submitLabel?: string; - cancelLabel?: string; - onDraftRestore?: (newState: FormState) => void; - children: (props: FormContextValue) => React.ReactNode; - hideRestoreDraft?: boolean; - resetFormValsOnCancel?: boolean; - resetFormValsOnSubmit?: boolean; - hideCancelButton?: boolean; - submitButtonClassName?: string; - hideSubmitButton?: boolean; - disableMarginOnChildren?: boolean; -}; - -const Form = ({ - asyncGetDefaults, - validate, - hideCancelButton = false, - hideSubmitButton = false, - disableMarginOnChildren = false, - ...props -}: Props) => { - const initial = { form: props.defaults, errors: {} }; - const [isLoading, setIsLoading] = useState(!!asyncGetDefaults); - const [state, dispatch] = useAutoSaveReducer(formReducer, initial); - const formVals = useRef(props.defaults); - - useEffect(() => { - if (!asyncGetDefaults) return; - - asyncGetDefaults().then((form) => { - dispatch({ type: "set_form", form }); - setIsLoading(false); - }); - }, [asyncGetDefaults]); - - const handleSubmit = async (event: React.FormEvent) => { - event.preventDefault(); - event.stopPropagation(); - - if (validate) { - const errors = omitBy(validate(state.form), isEmpty) as FormErrors; - - if (Object.keys(errors).length) { - dispatch({ type: "set_errors", errors }); - - if (errors.$all) { - Notification.Error({ msg: errors.$all }); - } - return; - } - } - - const errors = await props.onSubmit(state.form); - if (errors) { - dispatch({ - type: "set_errors", - errors: { ...state.errors, ...errors }, - }); - } else if (props.resetFormValsOnSubmit) { - dispatch({ type: "set_form", form: formVals.current }); - } - }; - - const handleCancel = () => { - if (props.resetFormValsOnCancel) { - dispatch({ type: "set_form", form: formVals.current }); - } - props.onCancel?.(); - }; - - const { Provider, Consumer } = useMemo(() => createFormContext(), []); - const disabled = isLoading || props.disabled; - - return ( -
- ) => { - dispatch({ type: "set_state", state: newState }); - props.onDraftRestore?.(newState); - }} - formData={state.form} - hidden={props.hideRestoreDraft} - > - ) => { - return { - name, - id: name, - onChange: ({ name, value }: FieldChangeEvent) => - dispatch({ - type: "set_field", - name, - value, - error: validate?.(value), - }), - value: state.form[name], - error: state.errors[name], - disabled, - }; - }} - > -
- {props.children} -
- {(!hideCancelButton || !hideSubmitButton) && ( -
- {!hideCancelButton && ( - - )} - {!hideSubmitButton && ( - - )} -
- )} -
-
-
- ); -}; - -export default Form; diff --git a/src/components/Users/UserResetPassword.tsx b/src/components/Users/UserResetPassword.tsx index 2ceae7314ae..5ba8c5cd06f 100644 --- a/src/components/Users/UserResetPassword.tsx +++ b/src/components/Users/UserResetPassword.tsx @@ -1,26 +1,59 @@ +import { zodResolver } from "@hookform/resolvers/zod"; +import { useMutation } from "@tanstack/react-query"; +import { t } from "i18next"; import { useState } from "react"; +import { useForm } from "react-hook-form"; import { useTranslation } from "react-i18next"; +import { toast } from "sonner"; +import { z } from "zod"; import CareIcon from "@/CAREUI/icons/CareIcon"; import { Button } from "@/components/ui/button"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; -import Form from "@/components/Form/Form"; import TextFormField from "@/components/Form/FormFields/TextFormField"; import { validateRule } from "@/components/Users/UserFormValidations"; import { UpdatePasswordForm } from "@/components/Users/models"; -import * as Notification from "@/Utils/Notifications"; import routes from "@/Utils/request/api"; -import request from "@/Utils/request/request"; +import mutate from "@/Utils/request/mutate"; import { UserBase } from "@/types/user/user"; -interface PasswordForm { - username: string; - old_password: string; - new_password_1: string; - new_password_2: string; -} +const PasswordSchema = z + .object({ + old_password: z + .string() + .min(1, { message: t("please_enter_current_password") }), + new_password_1: z + .string() + .min(8, { message: t("password_length_validation") }) + .regex(/\d/, { message: t("password_number_validation") }) + .regex(/[a-z]/, { + message: t("password_lowercase_validation"), + }) + .regex(/[A-Z]/, { + message: t("password_uppercase_validation"), + }), + new_password_2: z + .string() + .min(1, { message: t("please_enter_confirm_password") }), + }) + .refine((values) => values.new_password_1 === values.new_password_2, { + message: t("password_mismatch"), + path: ["new_password_2"], + }) + .refine((values) => values.new_password_1 !== values.old_password, { + message: t("new_password_same_as_old"), + path: ["new_password_1"], + }); export default function UserResetPassword({ userData, @@ -28,189 +61,160 @@ export default function UserResetPassword({ userData: UserBase; }) { const { t } = useTranslation(); - const [isSubmitting, setisSubmitting] = useState(false); const [isEditing, setIsEditing] = useState(false); - - const initForm: PasswordForm = { - username: userData.username, - old_password: "", - new_password_1: "", - new_password_2: "", - }; - - const validateNewPassword = (password: string) => { - if ( - password.length < 8 || - !/\d/.test(password) || - password === password.toUpperCase() || - password === password.toLowerCase() - ) { - return false; - } - return true; - }; - - const validateForm = (formData: PasswordForm) => { - const errors: Partial> = {}; - - if (!formData.old_password) { - errors.old_password = t("please_enter_current_password"); - } - - if (!formData.new_password_1) { - errors.new_password_1 = t("please_enter_new_password"); - } else if (!validateNewPassword(formData.new_password_1)) { - errors.new_password_1 = t("new_password_validation"); - } - - if (!formData.new_password_2) { - errors.new_password_2 = t("please_confirm_password"); - } else if (formData.new_password_1 !== formData.new_password_2) { - errors.new_password_2 = t("password_mismatch"); - } - - if (formData.new_password_1 === formData.old_password) { - errors.new_password_1 = t("new_password_same_as_old"); - } - - return errors; - }; - - const handleSubmit = async (formData: PasswordForm) => { - setisSubmitting(true); + const [isPasswordFieldFocused, setIsPasswordFieldFocused] = useState(false); + + const form = useForm({ + resolver: zodResolver(PasswordSchema), + defaultValues: { + old_password: "", + new_password_1: "", + new_password_2: "", + }, + }); + const { mutate: resetPassword, isPending } = useMutation({ + mutationFn: mutate(routes.updatePassword), + onSuccess: (data: Record) => { + toast.success(data?.message as string); + form.reset(); + }, + }); + + const handleSubmitPassword = async ( + formData: z.infer, + ) => { const form: UpdatePasswordForm = { old_password: formData.old_password, username: userData.username, new_password: formData.new_password_1, }; - - const { res, data, error } = await request(routes.updatePassword, { - body: form, - }); - - if (res?.ok) { - Notification.Success({ msg: data?.message as string }); - } else { - Notification.Error({ - msg: error?.message ?? t("password_update_error"), - }); - } - setisSubmitting(false); + resetPassword(form); }; - const renderPasswordForm = () => { - return ( - - defaults={initForm} - validate={validateForm} - onSubmit={handleSubmit} - resetFormValsOnCancel - resetFormValsOnSubmit - hideRestoreDraft - noPadding - disabled={isSubmitting} - hideCancelButton - > - {(field) => ( -
- -
- -
- {validateRule( - field("new_password_1").value?.length >= 8, - t("password_length_validation"), - !field("new_password_1").value, - )} - {validateRule( - field("new_password_1").value !== - field("new_password_1").value?.toUpperCase(), - t("password_lowercase_validation"), - !field("new_password_1").value, - )} - {validateRule( - field("new_password_1").value !== - field("new_password_1").value?.toLowerCase(), - t("password_uppercase_validation"), - !field("new_password_1").value, + return ( +
+
+ +
+ {isEditing && ( +
+ +
+ ( + + {t("old_password")} + + { + field.onChange(value.value); + }} + /> + + + )} - {validateRule( - /\d/.test(field("new_password_1").value ?? ""), - t("password_number_validation"), - !field("new_password_1").value, + /> + + ( + + {t("new_password")} + + { + field.onChange(value.value); + }} + onFocus={() => setIsPasswordFieldFocused(true)} + onBlur={() => setIsPasswordFieldFocused(false)} + /> + + {form.formState.errors?.new_password_1?.message && + form.formState.errors?.new_password_1?.message.includes( + t("new_password_same_as_old"), + ) && } + {isPasswordFieldFocused && ( +
+ {validateRule( + field.value.length >= 8, + t("password_length_validation"), + !field.value, + )} + {validateRule( + /[a-z]/.test(field.value), + t("password_lowercase_validation"), + !field.value, + )} + {validateRule( + /[A-Z]/.test(field.value), + t("password_uppercase_validation"), + !field.value, + )} + {validateRule( + /\d/.test(field.value), + t("password_number_validation"), + !field.value, + )} +
+ )} +
)} -
-
-
- + + ( + + {t("new_password_confirmation")} + + { + field.onChange(value.value); + }} + /> + + + + )} /> - {field("new_password_2").value?.length > 0 && ( -
- {validateRule( - field("new_password_1").value === - field("new_password_2").value, - t("password_mismatch"), - !field("new_password_2").value, - )} -
- )}
-
- )} - - ); - }; - - const editButton = () => ( -
- -
- ); - return ( -
- {editButton()} - {isEditing && renderPasswordForm()} + + + + )}
); } From 2e5be5718758bbf05730a33030d4df3d7604a0a0 Mon Sep 17 00:00:00 2001 From: rithviknishad Date: Sun, 5 Jan 2025 22:24:56 +0530 Subject: [PATCH 25/83] keep things simpler --- public/locale/en.json | 1 + src/Utils/request/api.tsx | 2 +- src/Utils/request/errorHandler.ts | 11 +++++------ src/Utils/request/types.ts | 4 +++- src/components/Users/UserResetPassword.tsx | 4 ++-- 5 files changed, 12 insertions(+), 10 deletions(-) diff --git a/public/locale/en.json b/public/locale/en.json index e53709ae7ff..cb6952e5ce2 100644 --- a/public/locale/en.json +++ b/public/locale/en.json @@ -1317,6 +1317,7 @@ "password_reset_success": "Password Reset successfully", "password_sent": "Password Reset Email Sent", "password_update_error": "Error while updating password. Try again later.", + "password_updated": "Password updated successfully", "password_uppercase_validation": "Password must contain at least one uppercase letter (A-Z)", "password_validation": "Password must contain at least: 8 characters, 1 uppercase letter (A-Z), 1 lowercase letter (a-z), and 1 number (0-9)", "patient": "Patient", diff --git a/src/Utils/request/api.tsx b/src/Utils/request/api.tsx index a52eb23df38..03236d31f61 100644 --- a/src/Utils/request/api.tsx +++ b/src/Utils/request/api.tsx @@ -171,7 +171,7 @@ const routes = { updatePassword: { path: "/api/v1/password_change/", method: "PUT", - TRes: Type>(), + TRes: Type<{ message: string }>(), TBody: Type(), }, // User Endpoints diff --git a/src/Utils/request/errorHandler.ts b/src/Utils/request/errorHandler.ts index 29b20389ef3..67550af5999 100644 --- a/src/Utils/request/errorHandler.ts +++ b/src/Utils/request/errorHandler.ts @@ -2,7 +2,7 @@ import { navigate } from "raviger"; import { toast } from "sonner"; import * as Notifications from "@/Utils/Notifications"; -import { HTTPError } from "@/Utils/request/types"; +import { HTTPError, StructuredError } from "@/Utils/request/types"; export function handleHttpError(error: Error) { if (error.name === "AbortError") { @@ -82,12 +82,11 @@ type PydanticError = { url: string; }; -function isStructuredError( - cause: HTTPError["cause"], -): cause is Record { - return typeof cause === "object" && !Array.isArray(cause); +function isStructuredError(err: HTTPError["cause"]): err is StructuredError { + return typeof err === "object" && !Array.isArray(err); } -function handleStructuredErrors(cause: Record) { + +function handleStructuredErrors(cause: StructuredError) { for (const value of Object.values(cause)) { if (Array.isArray(value)) { value.forEach((err) => toast.error(err)); diff --git a/src/Utils/request/types.ts b/src/Utils/request/types.ts index bdb1038ed38..e63b5430c60 100644 --- a/src/Utils/request/types.ts +++ b/src/Utils/request/types.ts @@ -51,7 +51,9 @@ export interface APICallOptions { headers?: HeadersInit; } -type HTTPErrorCause = Record | undefined; +export type StructuredError = Record; + +type HTTPErrorCause = StructuredError | Record | undefined; export class HTTPError extends Error { status: number; diff --git a/src/components/Users/UserResetPassword.tsx b/src/components/Users/UserResetPassword.tsx index 5ba8c5cd06f..26322382e0f 100644 --- a/src/components/Users/UserResetPassword.tsx +++ b/src/components/Users/UserResetPassword.tsx @@ -74,8 +74,8 @@ export default function UserResetPassword({ }); const { mutate: resetPassword, isPending } = useMutation({ mutationFn: mutate(routes.updatePassword), - onSuccess: (data: Record) => { - toast.success(data?.message as string); + onSuccess: () => { + toast.success(t("password_updated")); form.reset(); }, }); From 4f3101096b3c9728c0d8781e8442a80e30fc3e9b Mon Sep 17 00:00:00 2001 From: AdityaJ2305 Date: Fri, 3 Jan 2025 20:24:54 +0530 Subject: [PATCH 26/83] update to shadcn form in userresetpassword --- src/components/Users/UserResetPassword.tsx | 314 +++++++++++---------- 1 file changed, 159 insertions(+), 155 deletions(-) diff --git a/src/components/Users/UserResetPassword.tsx b/src/components/Users/UserResetPassword.tsx index 5807ecd129b..91ed393eb4a 100644 --- a/src/components/Users/UserResetPassword.tsx +++ b/src/components/Users/UserResetPassword.tsx @@ -1,9 +1,21 @@ +import { zodResolver } from "@hookform/resolvers/zod"; import { useState } from "react"; +import { useForm } from "react-hook-form"; import { useTranslation } from "react-i18next"; +import { z } from "zod"; import CareIcon from "@/CAREUI/icons/CareIcon"; -import Form from "@/components/Form/Form"; +import { Button } from "@/components/ui/button"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; + import TextFormField from "@/components/Form/FormFields/TextFormField"; import { validateRule } from "@/components/Users/UserFormValidations"; import { UpdatePasswordForm } from "@/components/Users/models"; @@ -13,14 +25,29 @@ import routes from "@/Utils/request/api"; import request from "@/Utils/request/request"; import { UserBase } from "@/types/user/user"; -import ButtonV2 from "../Common/ButtonV2"; - -interface PasswordForm { - username: string; - old_password: string; - new_password_1: string; - new_password_2: string; -} +const PasswordSchema = z + .object({ + current_password: z + .string() + .min(1, { message: "Please enter current password" }), + new_password_1: z + .string() + .min(8, { message: "New password must be at least 8 characters long" }) + .regex(/\d/, { message: "Password must contain at least one number" }) + .regex(/[a-z]/, { + message: "Password must contain at least one lowercase letter", + }) + .regex(/[A-Z]/, { + message: "Password must contain at least one uppercase letter", + }), + new_password_2: z + .string() + .min(1, { message: "Please confirm your new password" }), + }) + .refine((values) => values.new_password_1 === values.new_password_2, { + message: "New password and confirm password must be the same.", + path: ["new_password_2"], + }); export default function UserResetPassword({ userData, @@ -28,58 +55,25 @@ export default function UserResetPassword({ userData: UserBase; }) { const { t } = useTranslation(); - const [isSubmitting, setisSubmitting] = useState(false); + const [isSubmitting, setIsSubmitting] = useState(false); const [isEditing, setIsEditing] = useState(false); + const [isPasswordFieldFocused, setIsPasswordFieldFocused] = useState(false); + + const resetPasswordForm = useForm({ + resolver: zodResolver(PasswordSchema), + defaultValues: { + current_password: "", + new_password_1: "", + new_password_2: "", + }, + }); + const handleSubmitPassword = async ( + formData: z.infer, + ) => { + setIsSubmitting(true); - const initForm: PasswordForm = { - username: userData.username, - old_password: "", - new_password_1: "", - new_password_2: "", - }; - - const validateNewPassword = (password: string) => { - if ( - password.length < 8 || - !/\d/.test(password) || - password === password.toUpperCase() || - password === password.toLowerCase() - ) { - return false; - } - return true; - }; - - const validateForm = (formData: PasswordForm) => { - const errors: Partial> = {}; - - if (!formData.old_password) { - errors.old_password = t("please_enter_current_password"); - } - - if (!formData.new_password_1) { - errors.new_password_1 = t("please_enter_new_password"); - } else if (!validateNewPassword(formData.new_password_1)) { - errors.new_password_1 = t("new_password_validation"); - } - - if (!formData.new_password_2) { - errors.new_password_2 = t("please_confirm_password"); - } else if (formData.new_password_1 !== formData.new_password_2) { - errors.new_password_2 = t("password_mismatch"); - } - - if (formData.new_password_1 === formData.old_password) { - errors.new_password_1 = t("new_password_same_as_old"); - } - - return errors; - }; - - const handleSubmit = async (formData: PasswordForm) => { - setisSubmitting(true); const form: UpdatePasswordForm = { - old_password: formData.old_password, + old_password: formData.current_password, username: userData.username, new_password: formData.new_password_1, }; @@ -89,119 +83,129 @@ export default function UserResetPassword({ }); if (res?.ok) { - Notification.Success({ msg: data?.message as string }); + Notification.Success({ msg: "Password Updated Successfully" }); } else { Notification.Error({ - msg: error?.message ?? t("password_update_error"), + msg: t("password_update_error"), }); } - setisSubmitting(false); + setIsSubmitting(false); }; - const renderPasswordForm = () => { - return ( - - defaults={initForm} - validate={validateForm} - onSubmit={handleSubmit} - resetFormValsOnCancel - resetFormValsOnSubmit - hideRestoreDraft - noPadding - disabled={isSubmitting} - hideCancelButton - > - {(field) => ( -
- -
- -
- {validateRule( - field("new_password_1").value?.length >= 8, - t("password_length_validation"), - !field("new_password_1").value, + const renderPasswordForm = () => ( +
+ +
+ ( + + {t("current_password")} + + { + field.onChange(value.value); + }} + required + /> + + + + )} + /> + + ( + + {t("new_password")} + + { + field.onChange(value.value); + }} + onFocus={() => setIsPasswordFieldFocused(true)} + onBlur={() => setIsPasswordFieldFocused(false)} + /> + + {isPasswordFieldFocused && ( +
+ {validateRule( + field.value.length >= 8, + t("password_length_validation"), + !field.value, + )} + {validateRule( + /[a-z]/.test(field.value), + t("password_lowercase_validation"), + !field.value, + )} + {validateRule( + /[A-Z]/.test(field.value), + t("password_uppercase_validation"), + !field.value, + )} + {validateRule( + /\d/.test(field.value), + t("password_number_validation"), + !field.value, + )} +
)} - {validateRule( - field("new_password_1").value !== - field("new_password_1").value?.toUpperCase(), - t("password_lowercase_validation"), - !field("new_password_1").value, - )} - {validateRule( - field("new_password_1").value !== - field("new_password_1").value?.toLowerCase(), - t("password_uppercase_validation"), - !field("new_password_1").value, - )} - {validateRule( - /\d/.test(field("new_password_1").value ?? ""), - t("password_number_validation"), - !field("new_password_1").value, - )} -
-
-
- - {field("new_password_2").value?.length > 0 && ( -
- {validateRule( - field("new_password_1").value === - field("new_password_2").value, - t("password_mismatch"), - !field("new_password_2").value, - )} -
- )} -
-
- )} - - ); - }; - + + )} + /> + + ( + + {t("new_password_confirmation")} + + { + field.onChange(value.value); + }} + required + /> + + + + )} + /> +
+ + + + + ); const editButton = () => (
- setIsEditing(!isEditing)} type="button" id="change-edit-password-button" className="flex items-center gap-2 rounded-sm border border-gray-100 bg-white px-3 py-1.5 text-sm text-[#009D48] shadow-sm hover:bg-gray-50" - shadow={false} > {isEditing ? t("cancel") : t("change_password")} - +
); From b7805598797b206d82128c7e65476dd8c883476d Mon Sep 17 00:00:00 2001 From: AdityaJ2305 Date: Fri, 3 Jan 2025 20:27:29 +0530 Subject: [PATCH 27/83] deleted old form --- src/components/Form/Form.tsx | 167 ----------------------------------- 1 file changed, 167 deletions(-) delete mode 100644 src/components/Form/Form.tsx diff --git a/src/components/Form/Form.tsx b/src/components/Form/Form.tsx deleted file mode 100644 index 97f424f69d7..00000000000 --- a/src/components/Form/Form.tsx +++ /dev/null @@ -1,167 +0,0 @@ -import { useEffect, useMemo, useRef, useState } from "react"; - -import { Cancel, Submit } from "@/components/Common/ButtonV2"; -import { FieldValidator } from "@/components/Form/FieldValidators"; -import { - FormContextValue, - createFormContext, -} from "@/components/Form/FormContext"; -import { FieldChangeEvent } from "@/components/Form/FormFields/Utils"; -import { - FormDetails, - FormErrors, - FormState, - formReducer, -} from "@/components/Form/Utils"; - -import { DraftSection, useAutoSaveReducer } from "@/Utils/AutoSave"; -import * as Notification from "@/Utils/Notifications"; -import { classNames, isEmpty, omitBy } from "@/Utils/utils"; - -type Props = { - className?: string; - defaults: T; - asyncGetDefaults?: (() => Promise) | false; - validate?: (form: T) => FormErrors; - onSubmit: (form: T) => Promise | void>; - onCancel?: () => void; - noPadding?: true; - disabled?: boolean; - submitLabel?: string; - cancelLabel?: string; - onDraftRestore?: (newState: FormState) => void; - children: (props: FormContextValue) => React.ReactNode; - hideRestoreDraft?: boolean; - resetFormValsOnCancel?: boolean; - resetFormValsOnSubmit?: boolean; - hideCancelButton?: boolean; - submitButtonClassName?: string; - hideSubmitButton?: boolean; - disableMarginOnChildren?: boolean; -}; - -const Form = ({ - asyncGetDefaults, - validate, - hideCancelButton = false, - hideSubmitButton = false, - disableMarginOnChildren = false, - ...props -}: Props) => { - const initial = { form: props.defaults, errors: {} }; - const [isLoading, setIsLoading] = useState(!!asyncGetDefaults); - const [state, dispatch] = useAutoSaveReducer(formReducer, initial); - const formVals = useRef(props.defaults); - - useEffect(() => { - if (!asyncGetDefaults) return; - - asyncGetDefaults().then((form) => { - dispatch({ type: "set_form", form }); - setIsLoading(false); - }); - }, [asyncGetDefaults]); - - const handleSubmit = async (event: React.FormEvent) => { - event.preventDefault(); - event.stopPropagation(); - - if (validate) { - const errors = omitBy(validate(state.form), isEmpty) as FormErrors; - - if (Object.keys(errors).length) { - dispatch({ type: "set_errors", errors }); - - if (errors.$all) { - Notification.Error({ msg: errors.$all }); - } - return; - } - } - - const errors = await props.onSubmit(state.form); - if (errors) { - dispatch({ - type: "set_errors", - errors: { ...state.errors, ...errors }, - }); - } else if (props.resetFormValsOnSubmit) { - dispatch({ type: "set_form", form: formVals.current }); - } - }; - - const handleCancel = () => { - if (props.resetFormValsOnCancel) { - dispatch({ type: "set_form", form: formVals.current }); - } - props.onCancel?.(); - }; - - const { Provider, Consumer } = useMemo(() => createFormContext(), []); - const disabled = isLoading || props.disabled; - - return ( -
- ) => { - dispatch({ type: "set_state", state: newState }); - props.onDraftRestore?.(newState); - }} - formData={state.form} - hidden={props.hideRestoreDraft} - > - ) => { - return { - name, - id: name, - onChange: ({ name, value }: FieldChangeEvent) => - dispatch({ - type: "set_field", - name, - value, - error: validate?.(value), - }), - value: state.form[name], - error: state.errors[name], - disabled, - }; - }} - > -
- {props.children} -
- {(!hideCancelButton || !hideSubmitButton) && ( -
- {!hideCancelButton && ( - - )} - {!hideSubmitButton && ( - - )} -
- )} -
-
-
- ); -}; - -export default Form; From 9ca5d7d26c7b845620df1cfe8addcb64b86a41f3 Mon Sep 17 00:00:00 2001 From: AdityaJ2305 Date: Fri, 3 Jan 2025 20:31:32 +0530 Subject: [PATCH 28/83] fix linting error --- src/components/Users/UserResetPassword.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Users/UserResetPassword.tsx b/src/components/Users/UserResetPassword.tsx index 91ed393eb4a..c4717c55f3d 100644 --- a/src/components/Users/UserResetPassword.tsx +++ b/src/components/Users/UserResetPassword.tsx @@ -83,10 +83,10 @@ export default function UserResetPassword({ }); if (res?.ok) { - Notification.Success({ msg: "Password Updated Successfully" }); + Notification.Success({ msg: data?.message as string }); } else { Notification.Error({ - msg: t("password_update_error"), + msg: error?.message ?? t("password_update_error"), }); } setIsSubmitting(false); From 685e3a923e60375ecc6b16dba8fa5b1dfb0038a7 Mon Sep 17 00:00:00 2001 From: AdityaJ2305 Date: Fri, 3 Jan 2025 21:22:21 +0530 Subject: [PATCH 29/83] update to mutate --- src/components/Users/UserResetPassword.tsx | 50 ++++++++++++++-------- 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/src/components/Users/UserResetPassword.tsx b/src/components/Users/UserResetPassword.tsx index c4717c55f3d..de0c35a0f05 100644 --- a/src/components/Users/UserResetPassword.tsx +++ b/src/components/Users/UserResetPassword.tsx @@ -1,4 +1,5 @@ import { zodResolver } from "@hookform/resolvers/zod"; +import { useMutation } from "@tanstack/react-query"; import { useState } from "react"; import { useForm } from "react-hook-form"; import { useTranslation } from "react-i18next"; @@ -22,7 +23,7 @@ import { UpdatePasswordForm } from "@/components/Users/models"; import * as Notification from "@/Utils/Notifications"; import routes from "@/Utils/request/api"; -import request from "@/Utils/request/request"; +import mutate from "@/Utils/request/mutate"; import { UserBase } from "@/types/user/user"; const PasswordSchema = z @@ -55,7 +56,6 @@ export default function UserResetPassword({ userData: UserBase; }) { const { t } = useTranslation(); - const [isSubmitting, setIsSubmitting] = useState(false); const [isEditing, setIsEditing] = useState(false); const [isPasswordFieldFocused, setIsPasswordFieldFocused] = useState(false); @@ -67,29 +67,37 @@ export default function UserResetPassword({ new_password_2: "", }, }); + const { mutate: resetUserPasswordMutate } = useMutation({ + mutationFn: async (formData: UpdatePasswordForm) => { + const response = await mutate(routes.updatePassword, { silent: true })( + formData, + ); + if ("errors" in response) { + throw response; + } + return response; + }, + onSuccess: (data: any) => { + Notification.Success({ msg: data?.message as string }); + resetPasswordForm.reset(); + }, + onError: (error: any) => { + const errorMessage = + error?.response?.data?.message || t("password_update_error"); + Notification.Error({ msg: errorMessage }); + resetPasswordForm.reset(); + }, + }); + const handleSubmitPassword = async ( formData: z.infer, ) => { - setIsSubmitting(true); - const form: UpdatePasswordForm = { old_password: formData.current_password, username: userData.username, new_password: formData.new_password_1, }; - - const { res, data, error } = await request(routes.updatePassword, { - body: form, - }); - - if (res?.ok) { - Notification.Success({ msg: data?.message as string }); - } else { - Notification.Error({ - msg: error?.message ?? t("password_update_error"), - }); - } - setIsSubmitting(false); + resetUserPasswordMutate(form); }; const renderPasswordForm = () => ( @@ -105,6 +113,7 @@ export default function UserResetPassword({ { field.onChange(value.value); }} @@ -125,6 +134,7 @@ export default function UserResetPassword({ { field.onChange(value.value); }} @@ -172,6 +182,7 @@ export default function UserResetPassword({ { field.onChange(value.value); }} @@ -186,15 +197,16 @@ export default function UserResetPassword({ ); + const editButton = () => (
From 54defa1c14680c146a5134bbdf40d8cf1ec2bd92 Mon Sep 17 00:00:00 2001 From: AdityaJ2305 Date: Fri, 3 Jan 2025 21:57:48 +0530 Subject: [PATCH 32/83] update the error --- src/components/Users/UserResetPassword.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Users/UserResetPassword.tsx b/src/components/Users/UserResetPassword.tsx index 1b988a90991..6cc9e743164 100644 --- a/src/components/Users/UserResetPassword.tsx +++ b/src/components/Users/UserResetPassword.tsx @@ -83,7 +83,7 @@ export default function UserResetPassword({ }, onError: (error: any) => { const errorMessage = - error?.response?.data?.message || t("password_update_error"); + error.cause.old_password[0] || t("password_update_error"); Notification.Error({ msg: errorMessage }); }, }); From 74c445edf8bc9a066dea570533604af2cc575584 Mon Sep 17 00:00:00 2001 From: AdityaJ2305 Date: Fri, 3 Jan 2025 22:02:24 +0530 Subject: [PATCH 33/83] fix the usemutate --- src/components/Users/UserResetPassword.tsx | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/components/Users/UserResetPassword.tsx b/src/components/Users/UserResetPassword.tsx index 6cc9e743164..892fbd32842 100644 --- a/src/components/Users/UserResetPassword.tsx +++ b/src/components/Users/UserResetPassword.tsx @@ -68,15 +68,7 @@ export default function UserResetPassword({ }, }); const { mutate: resetUserPasswordMutate, isPending } = useMutation({ - mutationFn: async (formData: UpdatePasswordForm) => { - const response = await mutate(routes.updatePassword, { silent: true })( - formData, - ); - if ("errors" in response) { - throw response; - } - return response; - }, + mutationFn: mutate(routes.updatePassword, { silent: true }), onSuccess: (data: any) => { Notification.Success({ msg: data?.message as string }); resetPasswordForm.reset(); From c3aa9c62ea043bd73ee630e41e4ad7a11f60c0f2 Mon Sep 17 00:00:00 2001 From: AdityaJ2305 Date: Fri, 3 Jan 2025 22:08:58 +0530 Subject: [PATCH 34/83] fix the usemutate --- src/components/Users/UserResetPassword.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Users/UserResetPassword.tsx b/src/components/Users/UserResetPassword.tsx index 892fbd32842..a93fadb7b3a 100644 --- a/src/components/Users/UserResetPassword.tsx +++ b/src/components/Users/UserResetPassword.tsx @@ -75,7 +75,7 @@ export default function UserResetPassword({ }, onError: (error: any) => { const errorMessage = - error.cause.old_password[0] || t("password_update_error"); + error.cause?.old_password?.[0] ?? t("password_update_error"); Notification.Error({ msg: errorMessage }); }, }); From 96e5c265d05c6f4efc87901e15e18ad16b2b3a46 Mon Sep 17 00:00:00 2001 From: AdityaJ2305 Date: Fri, 3 Jan 2025 23:18:53 +0530 Subject: [PATCH 35/83] use of toast and update to use translation --- public/locale/en.json | 1 + src/components/Users/UserResetPassword.tsx | 32 ++++++++++++++-------- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/public/locale/en.json b/public/locale/en.json index 02820172fe4..51da82b5a16 100644 --- a/public/locale/en.json +++ b/public/locale/en.json @@ -1374,6 +1374,7 @@ "please_check_your_messages": "Please check your messages", "please_confirm_password": "Please confirm your new password.", "please_enter_a_reason_for_the_shift": "Please enter a reason for the shift.", + "please_enter_confirm_password": "Please confirm your new password", "please_enter_current_password": "Please enter your current password.", "please_enter_new_password": "Please enter your new password.", "please_enter_username": "Please enter the username", diff --git a/src/components/Users/UserResetPassword.tsx b/src/components/Users/UserResetPassword.tsx index a93fadb7b3a..f408dc982b7 100644 --- a/src/components/Users/UserResetPassword.tsx +++ b/src/components/Users/UserResetPassword.tsx @@ -1,8 +1,10 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { useMutation } from "@tanstack/react-query"; +import { t } from "i18next"; import { useState } from "react"; import { useForm } from "react-hook-form"; import { useTranslation } from "react-i18next"; +import { toast } from "sonner"; import { z } from "zod"; import CareIcon from "@/CAREUI/icons/CareIcon"; @@ -21,7 +23,6 @@ import TextFormField from "@/components/Form/FormFields/TextFormField"; import { validateRule } from "@/components/Users/UserFormValidations"; import { UpdatePasswordForm } from "@/components/Users/models"; -import * as Notification from "@/Utils/Notifications"; import routes from "@/Utils/request/api"; import mutate from "@/Utils/request/mutate"; import { UserBase } from "@/types/user/user"; @@ -30,24 +31,28 @@ const PasswordSchema = z .object({ old_password: z .string() - .min(1, { message: "Please enter current password" }), + .min(1, { message: t("please_enter_current_password") }), new_password_1: z .string() - .min(8, { message: "New password must be at least 8 characters long" }) - .regex(/\d/, { message: "Password must contain at least one number" }) + .min(8, { message: t("password_length_validation") }) + .regex(/\d/, { message: t("password_number_validation") }) .regex(/[a-z]/, { - message: "Password must contain at least one lowercase letter", + message: t("password_lowercase_validation"), }) .regex(/[A-Z]/, { - message: "Password must contain at least one uppercase letter", + message: t("password_uppercase_validation"), }), new_password_2: z .string() - .min(1, { message: "Please confirm your new password" }), + .min(1, { message: t("please_enter_confirm_password") }), }) .refine((values) => values.new_password_1 === values.new_password_2, { - message: "New password and confirm password must be the same.", + message: t("password_mismatch"), path: ["new_password_2"], + }) + .refine((values) => values.new_password_1 !== values.old_password, { + message: t("new_password_same_as_old"), + path: ["new_password_1"], }); export default function UserResetPassword({ @@ -70,13 +75,13 @@ export default function UserResetPassword({ const { mutate: resetUserPasswordMutate, isPending } = useMutation({ mutationFn: mutate(routes.updatePassword, { silent: true }), onSuccess: (data: any) => { - Notification.Success({ msg: data?.message as string }); + toast.success(data?.message as string); resetPasswordForm.reset(); }, onError: (error: any) => { const errorMessage = error.cause?.old_password?.[0] ?? t("password_update_error"); - Notification.Error({ msg: errorMessage }); + toast.error(errorMessage); }, }); @@ -108,7 +113,6 @@ export default function UserResetPassword({ onChange={(value) => { field.onChange(value.value); }} - required /> @@ -133,6 +137,11 @@ export default function UserResetPassword({ onBlur={() => setIsPasswordFieldFocused(false)} /> + + {resetPasswordForm.formState.errors?.new_password_1?.message && + resetPasswordForm.formState.errors?.new_password_1?.message.includes( + t("new_password_same_as_old"), + ) && } {isPasswordFieldFocused && (
{ field.onChange(value.value); }} - required /> From a84c44c7e9233d55c3648817621c17cc5fe73802 Mon Sep 17 00:00:00 2001 From: AdityaJ2305 Date: Fri, 3 Jan 2025 23:30:20 +0530 Subject: [PATCH 36/83] deleted formcontext --- src/components/Form/FormContext.ts | 19 ------------------- src/pluginTypes.ts | 17 ----------------- 2 files changed, 36 deletions(-) delete mode 100644 src/components/Form/FormContext.ts diff --git a/src/components/Form/FormContext.ts b/src/components/Form/FormContext.ts deleted file mode 100644 index 2be5c12234c..00000000000 --- a/src/components/Form/FormContext.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { createContext } from "react"; - -import { FieldError, FieldValidator } from "@/components/Form/FieldValidators"; -import { FormDetails } from "@/components/Form/Utils"; - -export type FormContextValue = ( - name: keyof T, - validate?: FieldValidator, - excludeFromDraft?: boolean, -) => { - id: keyof T; - name: keyof T; - onChange: any; - value: any; - error: FieldError | undefined; -}; - -export const createFormContext = () => - createContext>(undefined as any); diff --git a/src/pluginTypes.ts b/src/pluginTypes.ts index 28511669887..f61caa14794 100644 --- a/src/pluginTypes.ts +++ b/src/pluginTypes.ts @@ -6,7 +6,6 @@ import { UserAssignedModel } from "@/components/Users/models"; import { EncounterTabProps } from "@/pages/Encounters/EncounterShow"; import { AppRoutes } from "./Routers/AppRouter"; -import { FormContextValue } from "./components/Form/FormContext"; import { PatientMeta } from "./components/Patient/models"; import { pluginMap } from "./pluginMap"; import { PatientModel } from "./types/emr/patient"; @@ -27,21 +26,6 @@ export type ExtendFacilityConfigureComponentType = React.FC<{ facilityId: string; }>; -export type ExtendPatientRegisterFormComponentType = React.FC<{ - facilityId: string; - patientId?: string; - state: { - form: { - [key: string]: any; - }; - errors: { - [key: string]: string; - }; - }; - dispatch: React.Dispatch; - field: FormContextValue; -}>; - // Define supported plugin components export type SupportedPluginComponents = { DoctorConnectButtons: DoctorConnectButtonComponentType; @@ -49,7 +33,6 @@ export type SupportedPluginComponents = { ManageFacilityOptions: ManageFacilityOptionsComponentType; EncounterContextEnabler: React.FC; ExtendFacilityConfigure: ExtendFacilityConfigureComponentType; - ExtendPatientRegisterForm: ExtendPatientRegisterFormComponentType; }; // Create a type for lazy-loaded components From da22f88ad0f6f3f733b0f54db3fa2a4c62ecc619 Mon Sep 17 00:00:00 2001 From: AdityaJ2305 Date: Sat, 4 Jan 2025 00:19:58 +0530 Subject: [PATCH 37/83] update global error handler --- src/Utils/request/errorHandler.ts | 105 +++++++++++---------- src/components/Users/UserResetPassword.tsx | 7 +- 2 files changed, 56 insertions(+), 56 deletions(-) diff --git a/src/Utils/request/errorHandler.ts b/src/Utils/request/errorHandler.ts index ef2eba8bfe8..a763e4aca9e 100644 --- a/src/Utils/request/errorHandler.ts +++ b/src/Utils/request/errorHandler.ts @@ -32,68 +32,73 @@ export function handleHttpError(error: Error) { if (isBadRequest(error)) { const errs = cause?.errors; - if (isPydanticError(errs)) { + if (errs && isPydanticError(errs)) { handlePydanticErrors(errs); return; } - Notifications.BadRequest({ errs }); + if (cause && typeof cause === "object" && !Array.isArray(cause)) { + const firstKey = Object.keys(cause)[0]; + const firstError = firstKey ? cause[firstKey] : null; + if (firstError && Array.isArray(firstError)) { + Notifications.Error({ msg: firstError[0] }); + return; + } + } + Notifications.BadRequest({ errs: cause }); return; } - Notifications.Error({ - msg: cause?.detail || "Something went wrong...!", - }); -} - -function isSessionExpired(error: HTTPError["cause"]) { - return ( - // If Authorization header is not valid - error?.code === "token_not_valid" || - // If Authorization header is not provided - error?.detail === "Authentication credentials were not provided." - ); -} + function isSessionExpired(error: HTTPError["cause"]) { + return ( + // If Authorization header is not valid + error?.code === "token_not_valid" || + // If Authorization header is not provided + error?.detail === "Authentication credentials were not provided." + ); + } -function handleSessionExpired() { - if (!location.pathname.startsWith("/session-expired")) { - navigate(`/session-expired?redirect=${window.location.href}`); + function handleSessionExpired() { + if (!location.pathname.startsWith("/session-expired")) { + navigate(`/session-expired?redirect=${window.location.href}`); + } } -} -function isBadRequest(error: HTTPError) { - return error.status === 400 || error.status === 406; -} + function isBadRequest(error: HTTPError) { + return error.status === 400 || error.status === 406; + } -function isNotFound(error: HTTPError) { - return error.status === 404; -} + function isNotFound(error: HTTPError) { + return error.status === 404; + } -type PydanticError = { - type: string; - loc: string[]; - msg: string; - input: unknown; - url: string; -}; - -function isPydanticError(errors: unknown): errors is PydanticError[] { - return ( - Array.isArray(errors) && - errors.every( - (error) => typeof error === "object" && error !== null && "type" in error, - ) - ); -} + type PydanticError = { + type: string; + loc: string[]; + msg: string; + input: unknown; + url: string; + }; + + function isPydanticError(errors: unknown): errors is PydanticError[] { + return ( + Array.isArray(errors) && + errors.every( + (error) => + typeof error === "object" && error !== null && "type" in error, + ) + ); + } -function handlePydanticErrors(errors: PydanticError[]) { - errors.map(({ type, loc, msg }) => { - const title = type - .replace("_", " ") - .replace(/\b\w/g, (char) => char.toUpperCase()); + function handlePydanticErrors(errors: PydanticError[]) { + errors.map(({ type, loc, msg }) => { + const title = type + .replace("_", " ") + .replace(/\b\w/g, (char) => char.toUpperCase()); - toast.error(`${title}: '${loc.join(".")}'`, { - description: msg, - duration: 8000, + toast.error(`${title}: '${loc.join(".")}'`, { + description: msg, + duration: 8000, + }); }); - }); + } } diff --git a/src/components/Users/UserResetPassword.tsx b/src/components/Users/UserResetPassword.tsx index f408dc982b7..959bb85ae9b 100644 --- a/src/components/Users/UserResetPassword.tsx +++ b/src/components/Users/UserResetPassword.tsx @@ -73,16 +73,11 @@ export default function UserResetPassword({ }, }); const { mutate: resetUserPasswordMutate, isPending } = useMutation({ - mutationFn: mutate(routes.updatePassword, { silent: true }), + mutationFn: mutate(routes.updatePassword), onSuccess: (data: any) => { toast.success(data?.message as string); resetPasswordForm.reset(); }, - onError: (error: any) => { - const errorMessage = - error.cause?.old_password?.[0] ?? t("password_update_error"); - toast.error(errorMessage); - }, }); const handleSubmitPassword = async ( From eb68ff7ecaa23ec83bd18a006285ebfcbf812697 Mon Sep 17 00:00:00 2001 From: AdityaJ2305 Date: Sat, 4 Jan 2025 00:32:42 +0530 Subject: [PATCH 38/83] update global error handler --- src/Utils/request/errorHandler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Utils/request/errorHandler.ts b/src/Utils/request/errorHandler.ts index a763e4aca9e..06252a63b62 100644 --- a/src/Utils/request/errorHandler.ts +++ b/src/Utils/request/errorHandler.ts @@ -40,7 +40,7 @@ export function handleHttpError(error: Error) { const firstKey = Object.keys(cause)[0]; const firstError = firstKey ? cause[firstKey] : null; if (firstError && Array.isArray(firstError)) { - Notifications.Error({ msg: firstError[0] }); + toast.error(firstError[0]); return; } } From 1aa1eb824901a6536c137ca078be13fba6200609 Mon Sep 17 00:00:00 2001 From: AdityaJ2305 Date: Sat, 4 Jan 2025 01:22:31 +0530 Subject: [PATCH 39/83] fix bracket --- src/Utils/request/errorHandler.ts | 93 ++++++++++++++++--------------- 1 file changed, 48 insertions(+), 45 deletions(-) diff --git a/src/Utils/request/errorHandler.ts b/src/Utils/request/errorHandler.ts index 06252a63b62..897aaaebbf6 100644 --- a/src/Utils/request/errorHandler.ts +++ b/src/Utils/request/errorHandler.ts @@ -48,57 +48,60 @@ export function handleHttpError(error: Error) { return; } - function isSessionExpired(error: HTTPError["cause"]) { - return ( - // If Authorization header is not valid - error?.code === "token_not_valid" || - // If Authorization header is not provided - error?.detail === "Authentication credentials were not provided." - ); - } + Notifications.Error({ + msg: cause?.detail || "Something went wrong...!", + }); +} - function handleSessionExpired() { - if (!location.pathname.startsWith("/session-expired")) { - navigate(`/session-expired?redirect=${window.location.href}`); - } - } +function isSessionExpired(error: HTTPError["cause"]) { + return ( + // If Authorization header is not valid + error?.code === "token_not_valid" || + // If Authorization header is not provided + error?.detail === "Authentication credentials were not provided." + ); +} - function isBadRequest(error: HTTPError) { - return error.status === 400 || error.status === 406; +function handleSessionExpired() { + if (!location.pathname.startsWith("/session-expired")) { + navigate(`/session-expired?redirect=${window.location.href}`); } +} - function isNotFound(error: HTTPError) { - return error.status === 404; - } +function isBadRequest(error: HTTPError) { + return error.status === 400 || error.status === 406; +} - type PydanticError = { - type: string; - loc: string[]; - msg: string; - input: unknown; - url: string; - }; - - function isPydanticError(errors: unknown): errors is PydanticError[] { - return ( - Array.isArray(errors) && - errors.every( - (error) => - typeof error === "object" && error !== null && "type" in error, - ) - ); - } +function isNotFound(error: HTTPError) { + return error.status === 404; +} - function handlePydanticErrors(errors: PydanticError[]) { - errors.map(({ type, loc, msg }) => { - const title = type - .replace("_", " ") - .replace(/\b\w/g, (char) => char.toUpperCase()); +type PydanticError = { + type: string; + loc: string[]; + msg: string; + input: unknown; + url: string; +}; + +function isPydanticError(errors: unknown): errors is PydanticError[] { + return ( + Array.isArray(errors) && + errors.every( + (error) => typeof error === "object" && error !== null && "type" in error, + ) + ); +} + +function handlePydanticErrors(errors: PydanticError[]) { + errors.map(({ type, loc, msg }) => { + const title = type + .replace("_", " ") + .replace(/\b\w/g, (char) => char.toUpperCase()); - toast.error(`${title}: '${loc.join(".")}'`, { - description: msg, - duration: 8000, - }); + toast.error(`${title}: '${loc.join(".")}'`, { + description: msg, + duration: 8000, }); - } + }); } From 7be24cdafc81ada287239514f2e06bc7ab2ac205 Mon Sep 17 00:00:00 2001 From: AdityaJ2305 Date: Sat, 4 Jan 2025 01:30:35 +0530 Subject: [PATCH 40/83] fix bracket --- src/Utils/request/errorHandler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Utils/request/errorHandler.ts b/src/Utils/request/errorHandler.ts index 897aaaebbf6..46fa7a43a16 100644 --- a/src/Utils/request/errorHandler.ts +++ b/src/Utils/request/errorHandler.ts @@ -44,7 +44,7 @@ export function handleHttpError(error: Error) { return; } } - Notifications.BadRequest({ errs: cause }); + Notifications.BadRequest({ errs: errs }); return; } From c4b37e0af30edd9c6c825e5a46ab89995798ed14 Mon Sep 17 00:00:00 2001 From: Aditya Jindal Date: Sat, 4 Jan 2025 01:31:34 +0530 Subject: [PATCH 41/83] Update src/Utils/request/errorHandler.ts Co-authored-by: Rithvik Nishad --- src/Utils/request/errorHandler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Utils/request/errorHandler.ts b/src/Utils/request/errorHandler.ts index 46fa7a43a16..dd5fbbf49c0 100644 --- a/src/Utils/request/errorHandler.ts +++ b/src/Utils/request/errorHandler.ts @@ -32,7 +32,7 @@ export function handleHttpError(error: Error) { if (isBadRequest(error)) { const errs = cause?.errors; - if (errs && isPydanticError(errs)) { + if (isPydanticError(errs)) { handlePydanticErrors(errs); return; } From 34321e787394740715391a1a979be5b2bc029bb4 Mon Sep 17 00:00:00 2001 From: AdityaJ2305 Date: Sat, 4 Jan 2025 01:39:42 +0530 Subject: [PATCH 42/83] move to seperate func --- src/Utils/request/errorHandler.ts | 37 +++++++++++++++++++------------ 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/src/Utils/request/errorHandler.ts b/src/Utils/request/errorHandler.ts index dd5fbbf49c0..daad3bf52ed 100644 --- a/src/Utils/request/errorHandler.ts +++ b/src/Utils/request/errorHandler.ts @@ -31,20 +31,7 @@ export function handleHttpError(error: Error) { } if (isBadRequest(error)) { - const errs = cause?.errors; - if (isPydanticError(errs)) { - handlePydanticErrors(errs); - return; - } - if (cause && typeof cause === "object" && !Array.isArray(cause)) { - const firstKey = Object.keys(cause)[0]; - const firstError = firstKey ? cause[firstKey] : null; - if (firstError && Array.isArray(firstError)) { - toast.error(firstError[0]); - return; - } - } - Notifications.BadRequest({ errs: errs }); + handleBadRequest(cause); return; } @@ -105,3 +92,25 @@ function handlePydanticErrors(errors: PydanticError[]) { }); }); } + +function handleBadRequest(cause: any) { + const errs = cause?.errors; + + if (isPydanticError(errs)) { + handlePydanticErrors(errs); + return; + } + + if (cause && typeof cause === "object" && !Array.isArray(cause)) { + Object.entries(cause).forEach(([key, value]) => { + if (Array.isArray(value)) { + value.forEach((err) => { + toast.error(`${err}`); + }); + } + }); + return; + } + + Notifications.BadRequest({ errs: cause }); +} \ No newline at end of file From 0546626c28466cd080181b47eee553c1a9a6c71e Mon Sep 17 00:00:00 2001 From: AdityaJ2305 Date: Sat, 4 Jan 2025 01:47:00 +0530 Subject: [PATCH 43/83] lint fixed --- src/Utils/request/errorHandler.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Utils/request/errorHandler.ts b/src/Utils/request/errorHandler.ts index daad3bf52ed..0060aba68fe 100644 --- a/src/Utils/request/errorHandler.ts +++ b/src/Utils/request/errorHandler.ts @@ -102,7 +102,7 @@ function handleBadRequest(cause: any) { } if (cause && typeof cause === "object" && !Array.isArray(cause)) { - Object.entries(cause).forEach(([key, value]) => { + Object.values(cause).forEach((value) => { if (Array.isArray(value)) { value.forEach((err) => { toast.error(`${err}`); @@ -113,4 +113,4 @@ function handleBadRequest(cause: any) { } Notifications.BadRequest({ errs: cause }); -} \ No newline at end of file +} From 0f1bce7f63d193c8ae5d765c34a4e981c991b346 Mon Sep 17 00:00:00 2001 From: Anvesh Nalimela <151531961+AnveshNalimela@users.noreply.github.com> Date: Sat, 4 Jan 2025 21:06:41 +0530 Subject: [PATCH 44/83] Added no user assingned message (#9666) Co-authored-by: Rithvik Nishad --- public/locale/en.json | 9 + .../PatientDetailsTab/PatientUsers.tsx | 165 ++++++++++-------- 2 files changed, 101 insertions(+), 73 deletions(-) diff --git a/public/locale/en.json b/public/locale/en.json index 51da82b5a16..19762009e66 100644 --- a/public/locale/en.json +++ b/public/locale/en.json @@ -376,6 +376,7 @@ "are_you_still_watching": "Are you still watching?", "are_you_sure_want_to_delete": "Are you sure you want to delete {{name}}?", "are_you_sure_want_to_delete_this_record": "Are you sure want to delete this record?", + "are_you_sure_want_to_remove": "Are you sure you want to remove {{name}} from the patient? This action cannot be undone", "ari": "ARI - Acute Respiratory illness", "arrived": "Arrived", "asset_class": "Asset Class", @@ -388,7 +389,10 @@ "assign": "Assign", "assign_a_volunteer_to": "Assign a volunteer to {{name}}", "assign_bed": "Assign Bed", + "assign_to_patient": "Assign to Patient", "assign_to_volunteer": "Assign to a Volunteer", + "assign_user": "Assign User", + "assign_user_to_patient": "Assign User to Patient", "assigned_doctor": "Assigned Doctor", "assigned_facility": "Facility assigned", "assigned_to": "Assigned to", @@ -1238,6 +1242,7 @@ "no_staff": "No staff found", "no_tests_taken": "No tests taken", "no_treating_physicians_available": "This facility does not have any home facility doctors. Please contact your admin.", + "no_user_assigned": "No User Assigned to this patient", "no_users_found": "No Users Found", "no_vitals_present": "No Vitals Monitor present in this location or facility", "none": "None", @@ -1483,6 +1488,7 @@ "rejected": "Rejected", "reload": "Reload", "remove": "Remove", + "remove_user": "Remove User", "rename": "Rename", "replace_home_facility": "Replace Home Facility", "replace_home_facility_confirm": "Are you sure you want to replace", @@ -1571,6 +1577,8 @@ "search_medication": "Search Medication", "search_patients": "Search Patients", "search_resource": "Search Resource", + "search_user": "Search User", + "search_user_description": "Search for a user and assign a role to add them to the patient.", "searching": "Searching...", "see_attachments": "See Attachments", "select": "Select", @@ -1589,6 +1597,7 @@ "select_policy_to_add_items": "Select a Policy to Add Items", "select_practitioner": "Select Practicioner", "select_register_patient": "Select/Register Patient", + "select_role": "Select Role", "select_seven_day_period": "Select a seven day period", "select_skills": "Select and add some skills", "select_time": "Select time", diff --git a/src/components/Patient/PatientDetailsTab/PatientUsers.tsx b/src/components/Patient/PatientDetailsTab/PatientUsers.tsx index e494b13fe9d..989b3ed0ac3 100644 --- a/src/components/Patient/PatientDetailsTab/PatientUsers.tsx +++ b/src/components/Patient/PatientDetailsTab/PatientUsers.tsx @@ -1,4 +1,5 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { t } from "i18next"; import { useState } from "react"; import { toast } from "sonner"; @@ -38,6 +39,7 @@ import UserSelector from "@/components/Common/UserSelector"; import routes from "@/Utils/request/api"; import mutate from "@/Utils/request/mutate"; import query from "@/Utils/request/query"; +import { formatDisplayName } from "@/Utils/utils"; import { UserBase } from "@/types/user/user"; import { PatientProps } from "."; @@ -103,19 +105,17 @@ function AddUserSheet({ patientId }: AddUserSheetProps) { - Add User to Patient - - Search for a user and assign a role to add them to the patient. - + {t("assign_user_to_patient")} + {t("search_user_description")}
-

Search User

+

{t("search_user")}

- {selectedUser.first_name} {selectedUser.last_name} + {formatDisplayName(selectedUser)} {selectedUser.email} @@ -144,13 +144,17 @@ function AddUserSheet({ patientId }: AddUserSheetProps) {
- Username + + {t("username")} +

{selectedUser.username}

- User Type + + {t("user_type")} +

{selectedUser.user_type}

@@ -159,7 +163,9 @@ function AddUserSheet({ patientId }: AddUserSheetProps) {
- + +
+ {/* Basic Information */} +
+

Basic Information

+
+ ( + + Facility Type + + + + )} + /> + + ( + + Facility Name - - - + - - {FACILITY_TYPES.map((type) => ( - - {type.text} - - ))} - - - - - )} - /> + + + )} + /> +
( - Facility Name + Description - +