From e442dcfeb41a5544d2780df4135a01760ce18074 Mon Sep 17 00:00:00 2001 From: Rishith25 Date: Sat, 4 Jan 2025 12:54:43 +0530 Subject: [PATCH 01/10] DOB limit in patient registration form --- .../Patient/PatientRegistration.tsx | 33 +++++++++++-------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/src/components/Patient/PatientRegistration.tsx b/src/components/Patient/PatientRegistration.tsx index 1dc236f5fb3..06fc5315f95 100644 --- a/src/components/Patient/PatientRegistration.tsx +++ b/src/components/Patient/PatientRegistration.tsx @@ -455,12 +455,13 @@ export default function PatientRegistration( maxLength={2} max={31} min={1} - onChange={(e) => + onChange={(e) => { + const value = e.target.value; setForm((f) => ({ ...f, - date_of_birth: `${form.date_of_birth?.split("-")[0] || ""}-${form.date_of_birth?.split("-")[1] || ""}-${e.target.value}`, - })) - } + date_of_birth: `${form.date_of_birth?.split("-")[0] || ""}-${form.date_of_birth?.split("-")[1] || ""}-${value ? Math.min(Math.max(Number(value), 1), 31) : ""}`, + })); + }} /> @@ -473,12 +474,13 @@ export default function PatientRegistration( maxLength={2} max={12} min={1} - onChange={(e) => + onChange={(e) => { + const value = e.target.value; setForm((f) => ({ ...f, - date_of_birth: `${form.date_of_birth?.split("-")[0] || ""}-${e.target.value}-${form.date_of_birth?.split("-")[2] || ""}`, - })) - } + date_of_birth: `${form.date_of_birth?.split("-")[0] || ""}-${value ? Math.min(Math.max(Number(value), 1), 12) : ""}-${form.date_of_birth?.split("-")[2] || ""}`, + })); + }} /> @@ -491,12 +493,15 @@ export default function PatientRegistration( maxLength={4} max={new Date().getFullYear()} min={1900} - onChange={(e) => - setForm((f) => ({ - ...f, - date_of_birth: `${e.target.value}-${form.date_of_birth?.split("-")[1] || ""}-${form.date_of_birth?.split("-")[2] || ""}`, - })) - } + onChange={(e) => { + const value = e.target.value; + if (!value || value.length <= 4) { + setForm((f) => ({ + ...f, + date_of_birth: `${value || ""}-${form.date_of_birth?.split("-")[1] || ""}-${form.date_of_birth?.split("-")[2] || ""}`, + })); + } + }} /> From 4b50bb47a11c81e91c81e0db3fe861da843269d3 Mon Sep 17 00:00:00 2001 From: Rishith25 Date: Mon, 6 Jan 2025 16:59:51 +0530 Subject: [PATCH 02/10] Date of Birth validation --- public/locale/en.json | 5 + .../Patient/PatientRegistration.tsx | 194 ++++++++++++------ 2 files changed, 140 insertions(+), 59 deletions(-) diff --git a/public/locale/en.json b/public/locale/en.json index 53586b56e86..e9ef542fe20 100644 --- a/public/locale/en.json +++ b/public/locale/en.json @@ -29,6 +29,7 @@ "CONSCIOUSNESS_LEVEL__RESPONDS_TO_VOICE": "Responds to Voice", "CONSCIOUSNESS_LEVEL__UNRESPONSIVE": "Unresponsive", "Cancel": "Cancel", + "DATE_NOT_ALLOWED": "Date must be on or before today's date. Please correct the date.", "DAYS_OF_WEEK_SHORT__0": "Mon", "DAYS_OF_WEEK_SHORT__1": "Tue", "DAYS_OF_WEEK_SHORT__2": "Wed", @@ -70,6 +71,10 @@ "INSULIN_INTAKE_FREQUENCY__OD": "Once a day (OD)", "INSULIN_INTAKE_FREQUENCY__TD": "Thrice a day (TD)", "INSULIN_INTAKE_FREQUENCY__UNKNOWN": "Unknown", + "INVALID_DATE": "Invalid day for the selected month and year. Please correct the date.", + "INVALID_DAY": "Invalid day value. Please enter a valid day.", + "INVALID_MONTH": "Invalid month value. Please enter a valid month.", + "INVALID_YEAR": "Year not in range. Please enter a valid year.", "ISOLATION": "Isolation", "LIMB_RESPONSE__EXTENSION": "Extension", "LIMB_RESPONSE__FLEXION": "Flexion", diff --git a/src/components/Patient/PatientRegistration.tsx b/src/components/Patient/PatientRegistration.tsx index 06fc5315f95..ef1b528e454 100644 --- a/src/components/Patient/PatientRegistration.tsx +++ b/src/components/Patient/PatientRegistration.tsx @@ -80,6 +80,9 @@ export default function PatientRegistration( useState(!!patientId); const [debouncedNumber, setDebouncedNumber] = useState(); + const MIN_YEAR = 1900; + const MAX_YEAR = new Date().getFullYear(); + const sidebarItems = [ { label: t("patient__general-info"), id: "general-info" }, // { label: t("social_profile"), id: "social-profile" }, @@ -274,6 +277,93 @@ export default function PatientRegistration( } }; + const isLeapYear = (year: number): boolean => { + return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0); + }; + + const isValidDate = (day: number, month: number, year: number): boolean => { + const monthDays = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; + if (month === 2) { + return day <= (isLeapYear(year) ? 29 : 28); + } + return day <= monthDays[month - 1]; + }; + + const handleDateOfBirth = ( + part: "day" | "month" | "year", + value: string, + min: number, + max: number, + maxLength: number, + ) => { + if (value.length > maxLength) { + value = value.slice(0, maxLength); + } + const numericValue = Number(value.slice(0, maxLength)); + + setForm((prevState) => { + const [currentYear, currentMonth, currentDay] = + prevState.date_of_birth?.split("-") || ["", "", ""]; + + let formattedValue = value; + let errorMessage = ""; + + if (part === "day") { + if (numericValue < min || numericValue > max) { + formattedValue = ""; + errorMessage = t("INVALID_DAY"); + } + } else if (part === "month") { + if (numericValue < min || numericValue > max) { + formattedValue = ""; + errorMessage = t("INVALID_MONTH"); + } + } else if (part === "year") { + if (formattedValue.length === 4) { + const yearValue = Number(formattedValue); + + if (yearValue < min || yearValue > max) { + formattedValue = ""; + errorMessage = t("INVALID_YEAR", { min: MIN_YEAR, max: MAX_YEAR }); + } + } + } + + const updatedDay = part === "day" ? formattedValue : currentDay; + const updatedMonth = part === "month" ? formattedValue : currentMonth; + const updatedYear = part === "year" ? formattedValue : currentYear; + + if (updatedDay && updatedMonth && updatedYear) { + const day = Number(updatedDay); + const month = Number(updatedMonth); + const year = Number(updatedYear); + + if (!isValidDate(day, month, year)) { + errorMessage = t("INVALID_DATE"); + } else { + const today = new Date(); + const selectedDate = new Date(year, month - 1, day); + + if (selectedDate > today) { + errorMessage = t("DATE_NOT_ALLOWED"); + } + } + } + + setFeErrors((errors) => ({ + ...errors, + date_of_birth: errorMessage ? [errorMessage] : [], + })); + + const updatedDate = `${updatedYear}-${updatedMonth}-${updatedDay}`; + + return { + ...prevState, + date_of_birth: updatedDate, + }; + }); + }; + useEffect(() => { const handler = setTimeout(() => { if (!patientId || patientQuery.data?.phone_number !== form.phone_number) { @@ -446,65 +536,51 @@ export default function PatientRegistration(
-
- - { - const value = e.target.value; - setForm((f) => ({ - ...f, - date_of_birth: `${form.date_of_birth?.split("-")[0] || ""}-${form.date_of_birth?.split("-")[1] || ""}-${value ? Math.min(Math.max(Number(value), 1), 31) : ""}`, - })); - }} - /> - -
-
- - { - const value = e.target.value; - setForm((f) => ({ - ...f, - date_of_birth: `${form.date_of_birth?.split("-")[0] || ""}-${value ? Math.min(Math.max(Number(value), 1), 12) : ""}-${form.date_of_birth?.split("-")[2] || ""}`, - })); - }} - /> - -
-
- - { - const value = e.target.value; - if (!value || value.length <= 4) { - setForm((f) => ({ - ...f, - date_of_birth: `${value || ""}-${form.date_of_birth?.split("-")[1] || ""}-${form.date_of_birth?.split("-")[2] || ""}`, - })); - } - }} - /> - -
+ {["day", "month", "year"].map((part) => { + const key = part as "day" | "month" | "year"; + const placeholders = { + day: "DD", + month: "MM", + year: "YYYY", + }; + const maxLengths = { day: 2, month: 2, year: 4 }; + const limits = { + day: { min: 1, max: 31 }, + month: { min: 1, max: 12 }, + year: { min: MIN_YEAR, max: MAX_YEAR }, + }; + + return ( +
+ + { + const value = e.target.value; + if (key !== "year" || value.length <= 4) { + handleDateOfBirth( + key, + value, + limits[key].min, + limits[key].max, + maxLengths[key], + ); + } + }} + /> + +
+ ); + })}
{errors["date_of_birth"] && ( From 7f3d8ccdb30064214f654488c794655b17d51b75 Mon Sep 17 00:00:00 2001 From: Rishith25 Date: Tue, 7 Jan 2025 22:12:30 +0530 Subject: [PATCH 03/10] Username availability check --- public/locale/en.json | 9 +- src/components/Users/CreateUserForm.tsx | 198 ++++++++++++++++++++---- src/types/user/userApi.ts | 5 + 3 files changed, 181 insertions(+), 31 deletions(-) diff --git a/public/locale/en.json b/public/locale/en.json index e9850aa785d..d351abb2454 100644 --- a/public/locale/en.json +++ b/public/locale/en.json @@ -368,6 +368,7 @@ "allow_transfer": "Allow Transfer", "allowed_formats_are": "Allowed formats are", "already_a_member": "Already a member?", + "alternate_phone_number": "Alternate Phone Number", "ambulance_driver_name": "Name of ambulance driver", "ambulance_number": "Ambulance No", "ambulance_phone_number": "Phone number of Ambulance", @@ -651,6 +652,7 @@ "create_position_preset_description": "Creates a new position preset in Care from the current position of the camera for the given name", "create_preset_prerequisite": "To create presets for this bed, you'll need to link the camera to the bed first.", "create_resource_request": "Create Request", + "create_user": "Create User", "created": "Created", "created_by": "Created By", "created_date": "Created Date", @@ -731,6 +733,7 @@ "dob": "DOB", "dob_format": "Please enter date in DD/MM/YYYY format", "doc_will_visit_patient": "will visit the patient at the scheduled time.", + "doctor": "Doctor", "doctor_experience_error": "Please enter a valid number between 0 and 100.", "doctor_experience_required": "Years of experience is required", "doctor_not_found": "Doctor not found", @@ -775,7 +778,7 @@ "eg_mail_example_com": "Eg. mail@example.com", "eg_xyz": "Eg. XYZ", "eligible": "Eligible", - "email": "Email Address", + "email": "Email", "email_address": "Email Address", "email_discharge_summary_description": "Enter your valid email address to receive the discharge summary", "email_success": "We will be sending an email shortly. Please check your inbox.", @@ -1304,6 +1307,7 @@ "number_of_beds_out_of_range_error": "Number of beds cannot be greater than 100", "number_of_chronic_diseased_dependents": "Number Of Chronic Diseased Dependents", "number_of_covid_vaccine_doses": "Number of Covid vaccine doses", + "nurse": "Nurse", "nursing_care": "Nursing Care", "nursing_information": "Nursing Information", "nutrition": "Nutrition", @@ -1705,6 +1709,7 @@ "source": "Source", "spokes": "Spoke Facilities", "srf_id": "SRF ID", + "staff": "Staff", "staff_list": "Staff List", "start_consultation": "Start Consultation", "start_datetime": "Start Date/Time", @@ -1910,6 +1915,7 @@ "vitals_monitor": "Vitals Monitor", "vitals_present": "Vitals Monitor present", "voice_autofill": "Voice Autofill", + "volunteer": "Volunteer", "volunteer_assigned": "Volunteer assigned successfully", "volunteer_contact": "Volunteer Contact", "volunteer_contact_detail": "Provide the name and contact details of a volunteer who can assist the patient in emergencies. This should be someone outside the family.", @@ -1921,6 +1927,7 @@ "we_ve_sent_you_a_code_to": "We've sent you a code to", "weekly_working_hours_error": "Average weekly working hours must be a number between 0 and 168", "what_facility_assign_the_patient_to": "What facility would you like to assign the patient to", + "whataapp_number_same_as_phone_number": "WhatsApp number is same as phone number", "whatsapp_number": "Whatsapp Number", "why_the_asset_is_not_working": "Why the asset is not working?", "width": "Width ({{unit}})", diff --git a/src/components/Users/CreateUserForm.tsx b/src/components/Users/CreateUserForm.tsx index beafb4be950..ad843cf9766 100644 --- a/src/components/Users/CreateUserForm.tsx +++ b/src/components/Users/CreateUserForm.tsx @@ -1,9 +1,11 @@ import { zodResolver } from "@hookform/resolvers/zod"; -import { useEffect } from "react"; +import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { useTranslation } from "react-i18next"; import * as z from "zod"; +import CareIcon from "@/CAREUI/icons/CareIcon"; + import { Button } from "@/components/ui/button"; import { Checkbox } from "@/components/ui/checkbox"; import { @@ -24,9 +26,11 @@ import { } from "@/components/ui/select"; import { GENDER_TYPES } from "@/common/constants"; +import { validateUsername } from "@/common/validation"; import * as Notification from "@/Utils/Notifications"; import request from "@/Utils/request/request"; +import { classNames } from "@/Utils/utils"; import OrganizationSelector from "@/pages/Organization/components/OrganizationSelector"; import { UserBase } from "@/types/user/user"; import UserApi from "@/types/user/userApi"; @@ -92,9 +96,105 @@ interface Props { onSubmitSuccess?: (user: UserBase) => void; } +export const validateRule = ( + condition: boolean, + content: JSX.Element | string, + isInitialState: boolean = false, +) => { + return ( +
+ {isInitialState ? ( + + ) : condition ? ( + + ) : ( + + )}{" "} + + {content} + +
+ ); +}; + export default function CreateUserForm({ onSubmitSuccess }: Props) { const { t } = useTranslation(); + const userExistsEnums = { + idle: 0, + checking: 1, + exists: 2, + available: 3, + }; + + const [usernameExists, setUsernameExists] = useState( + userExistsEnums.idle, + ); + const [usernameInput, setUsernameInput] = useState(""); + + const checkUsername = async (username: string) => { + setUsernameExists(userExistsEnums.checking); + const { res: usernameCheck } = await request(UserApi.checkUsername, { + pathParams: { username }, + silent: true, + }); + if (usernameCheck === undefined || usernameCheck.status === 409) { + setUsernameExists(userExistsEnums.exists); + } else if (usernameCheck.status === 200) { + setUsernameExists(userExistsEnums.available); + } else { + Notification.Error({ + msg: "Some error occurred while checking username availability. Please try again later.", + }); + } + }; + + const renderFeedback = () => { + switch (usernameExists) { + case userExistsEnums.available: + return ( +
+ + + {t("username_available")} + +
+ ); + case userExistsEnums.exists: + return ( +
+ + + {t("username_not_available")} + +
+ ); + default: + return null; + } + }; + + useEffect(() => { + setUsernameExists(userExistsEnums.idle); + const timeout = setTimeout(() => { + if (validateUsername(usernameInput)) { + checkUsername(usernameInput); + } + }, 500); + return () => clearTimeout(timeout); + }, [usernameInput]); + const form = useForm({ resolver: zodResolver(userFormSchema), defaultValues: { @@ -155,7 +255,7 @@ export default function CreateUserForm({ onSubmitSuccess }: Props) { name="user_type" render={({ field }) => ( - User Type + {t("user_type")} @@ -180,9 +280,9 @@ export default function CreateUserForm({ onSubmitSuccess }: Props) { name="first_name" render={({ field }) => ( - First Name + {t("first_name")} - + @@ -194,9 +294,9 @@ export default function CreateUserForm({ onSubmitSuccess }: Props) { name="last_name" render={({ field }) => ( - Last Name + {t("last_name")} - + @@ -209,11 +309,43 @@ export default function CreateUserForm({ onSubmitSuccess }: Props) { name="username" render={({ field }) => ( - Username + {t("username")} - +
+ setUsernameInput(e.target.value)} + /> +
- + {renderFeedback()} + {validateRule( + usernameInput.length >= 4, + "Username must be at least 4 characters", + usernameInput.length === 0, + )} + {validateRule( + usernameInput.length < 16, + "Username must be less than 16 characters", + usernameInput.length === 0, + )} + {validateRule( + /^[a-z0-9._-]*$/.test(usernameInput), + "Username can only contain lowercase letters, numbers, and . _ -", + usernameInput.length === 0, + )} + {validateRule( + /^[a-z0-9].*[a-z0-9]$/.test(usernameInput), + "Username must start and end with a letter or number", + usernameInput.length === 0, + )} + {validateRule( + !usernameInput.match(/(?:[._-]{2,})/), + "Username cannot contain consecutive special characters", + usernameInput.length === 0, + )}
)} /> @@ -224,9 +356,13 @@ export default function CreateUserForm({ onSubmitSuccess }: Props) { name="password" render={({ field }) => ( - Password + {t("password")} - + @@ -238,11 +374,11 @@ export default function CreateUserForm({ onSubmitSuccess }: Props) { name="c_password" render={({ field }) => ( - Confirm Password + {t("confirm_password")} @@ -257,9 +393,9 @@ export default function CreateUserForm({ onSubmitSuccess }: Props) { name="email" render={({ field }) => ( - Email + {t("email")} - + @@ -272,7 +408,7 @@ export default function CreateUserForm({ onSubmitSuccess }: Props) { name="phone_number" render={({ field }) => ( - Phone Number + {t("phone_number")} @@ -286,7 +422,7 @@ export default function CreateUserForm({ onSubmitSuccess }: Props) { name="alt_phone_number" render={({ field }) => ( - Alternative Phone Number + {t("alternate_phone_number")}
- WhatsApp number is same as phone number + + {t("whataapp_number_same_as_phone_number")} +
)} @@ -325,7 +463,7 @@ export default function CreateUserForm({ onSubmitSuccess }: Props) { name="date_of_birth" render={({ field }) => ( - Date of Birth + {t("date_of_birth")} @@ -339,7 +477,7 @@ export default function CreateUserForm({ onSubmitSuccess }: Props) { name="gender" render={({ field }) => ( - Gender + {t("gender")} + @@ -387,11 +525,11 @@ export default function CreateUserForm({ onSubmitSuccess }: Props) { name="doctor_experience_commenced_on" render={({ field }) => ( - Years of Experience + {t("years_of_experience")} @@ -405,10 +543,10 @@ export default function CreateUserForm({ onSubmitSuccess }: Props) { name="doctor_medical_council_registration" render={({ field }) => ( - Medical Council Registration + {t("medical_council_registration")} diff --git a/src/types/user/userApi.ts b/src/types/user/userApi.ts index 61292d5c473..d522b301473 100644 --- a/src/types/user/userApi.ts +++ b/src/types/user/userApi.ts @@ -21,4 +21,9 @@ export default { TRes: Type(), }, + checkUsername: { + path: "/api/v1/users/{username}/check_availability/", + method: HttpMethod.GET, + TRes: Type, + }, }; From 54a5ab7a3ded95d205377d3246d4f57802f902c1 Mon Sep 17 00:00:00 2001 From: Rishith25 Date: Tue, 7 Jan 2025 22:17:00 +0530 Subject: [PATCH 04/10] Username availability check --- .../Patient/PatientRegistration.tsx | 189 +++++------------- 1 file changed, 54 insertions(+), 135 deletions(-) diff --git a/src/components/Patient/PatientRegistration.tsx b/src/components/Patient/PatientRegistration.tsx index ef1b528e454..1dc236f5fb3 100644 --- a/src/components/Patient/PatientRegistration.tsx +++ b/src/components/Patient/PatientRegistration.tsx @@ -80,9 +80,6 @@ export default function PatientRegistration( useState(!!patientId); const [debouncedNumber, setDebouncedNumber] = useState(); - const MIN_YEAR = 1900; - const MAX_YEAR = new Date().getFullYear(); - const sidebarItems = [ { label: t("patient__general-info"), id: "general-info" }, // { label: t("social_profile"), id: "social-profile" }, @@ -277,93 +274,6 @@ export default function PatientRegistration( } }; - const isLeapYear = (year: number): boolean => { - return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0); - }; - - const isValidDate = (day: number, month: number, year: number): boolean => { - const monthDays = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; - if (month === 2) { - return day <= (isLeapYear(year) ? 29 : 28); - } - return day <= monthDays[month - 1]; - }; - - const handleDateOfBirth = ( - part: "day" | "month" | "year", - value: string, - min: number, - max: number, - maxLength: number, - ) => { - if (value.length > maxLength) { - value = value.slice(0, maxLength); - } - const numericValue = Number(value.slice(0, maxLength)); - - setForm((prevState) => { - const [currentYear, currentMonth, currentDay] = - prevState.date_of_birth?.split("-") || ["", "", ""]; - - let formattedValue = value; - let errorMessage = ""; - - if (part === "day") { - if (numericValue < min || numericValue > max) { - formattedValue = ""; - errorMessage = t("INVALID_DAY"); - } - } else if (part === "month") { - if (numericValue < min || numericValue > max) { - formattedValue = ""; - errorMessage = t("INVALID_MONTH"); - } - } else if (part === "year") { - if (formattedValue.length === 4) { - const yearValue = Number(formattedValue); - - if (yearValue < min || yearValue > max) { - formattedValue = ""; - errorMessage = t("INVALID_YEAR", { min: MIN_YEAR, max: MAX_YEAR }); - } - } - } - - const updatedDay = part === "day" ? formattedValue : currentDay; - const updatedMonth = part === "month" ? formattedValue : currentMonth; - const updatedYear = part === "year" ? formattedValue : currentYear; - - if (updatedDay && updatedMonth && updatedYear) { - const day = Number(updatedDay); - const month = Number(updatedMonth); - const year = Number(updatedYear); - - if (!isValidDate(day, month, year)) { - errorMessage = t("INVALID_DATE"); - } else { - const today = new Date(); - const selectedDate = new Date(year, month - 1, day); - - if (selectedDate > today) { - errorMessage = t("DATE_NOT_ALLOWED"); - } - } - } - - setFeErrors((errors) => ({ - ...errors, - date_of_birth: errorMessage ? [errorMessage] : [], - })); - - const updatedDate = `${updatedYear}-${updatedMonth}-${updatedDay}`; - - return { - ...prevState, - date_of_birth: updatedDate, - }; - }); - }; - useEffect(() => { const handler = setTimeout(() => { if (!patientId || patientQuery.data?.phone_number !== form.phone_number) { @@ -536,51 +446,60 @@ export default function PatientRegistration(
- {["day", "month", "year"].map((part) => { - const key = part as "day" | "month" | "year"; - const placeholders = { - day: "DD", - month: "MM", - year: "YYYY", - }; - const maxLengths = { day: 2, month: 2, year: 4 }; - const limits = { - day: { min: 1, max: 31 }, - month: { min: 1, max: 12 }, - year: { min: MIN_YEAR, max: MAX_YEAR }, - }; - - return ( -
- - { - const value = e.target.value; - if (key !== "year" || value.length <= 4) { - handleDateOfBirth( - key, - value, - limits[key].min, - limits[key].max, - maxLengths[key], - ); - } - }} - /> - -
- ); - })} +
+ + + setForm((f) => ({ + ...f, + date_of_birth: `${form.date_of_birth?.split("-")[0] || ""}-${form.date_of_birth?.split("-")[1] || ""}-${e.target.value}`, + })) + } + /> + +
+
+ + + setForm((f) => ({ + ...f, + date_of_birth: `${form.date_of_birth?.split("-")[0] || ""}-${e.target.value}-${form.date_of_birth?.split("-")[2] || ""}`, + })) + } + /> + +
+
+ + + setForm((f) => ({ + ...f, + date_of_birth: `${e.target.value}-${form.date_of_birth?.split("-")[1] || ""}-${form.date_of_birth?.split("-")[2] || ""}`, + })) + } + /> + +
{errors["date_of_birth"] && ( From 3ae92b6dd6d3d6a7165555e08d10f55e422f1371 Mon Sep 17 00:00:00 2001 From: Rishith25 Date: Tue, 7 Jan 2025 22:20:17 +0530 Subject: [PATCH 05/10] clear commit --- public/locale/en.json | 5 ----- 1 file changed, 5 deletions(-) diff --git a/public/locale/en.json b/public/locale/en.json index d351abb2454..2473dc49416 100644 --- a/public/locale/en.json +++ b/public/locale/en.json @@ -29,7 +29,6 @@ "CONSCIOUSNESS_LEVEL__RESPONDS_TO_VOICE": "Responds to Voice", "CONSCIOUSNESS_LEVEL__UNRESPONSIVE": "Unresponsive", "Cancel": "Cancel", - "DATE_NOT_ALLOWED": "Date must be on or before today's date. Please correct the date.", "DAYS_OF_WEEK_SHORT__0": "Mon", "DAYS_OF_WEEK_SHORT__1": "Tue", "DAYS_OF_WEEK_SHORT__2": "Wed", @@ -72,10 +71,6 @@ "INSULIN_INTAKE_FREQUENCY__OD": "Once a day (OD)", "INSULIN_INTAKE_FREQUENCY__TD": "Thrice a day (TD)", "INSULIN_INTAKE_FREQUENCY__UNKNOWN": "Unknown", - "INVALID_DATE": "Invalid day for the selected month and year. Please correct the date.", - "INVALID_DAY": "Invalid day value. Please enter a valid day.", - "INVALID_MONTH": "Invalid month value. Please enter a valid month.", - "INVALID_YEAR": "Year not in range. Please enter a valid year.", "ISOLATION": "Isolation", "LIMB_RESPONSE__EXTENSION": "Extension", "LIMB_RESPONSE__FLEXION": "Flexion", From 35f3cacc55bdac8fb88f4e732a4718e8d4db9f80 Mon Sep 17 00:00:00 2001 From: Rishith25 Date: Wed, 8 Jan 2025 21:15:33 +0530 Subject: [PATCH 06/10] Used useQuery for username_check --- src/components/Users/CreateUserForm.tsx | 76 +++++++++++++++++-------- 1 file changed, 52 insertions(+), 24 deletions(-) diff --git a/src/components/Users/CreateUserForm.tsx b/src/components/Users/CreateUserForm.tsx index ad843cf9766..bfe1dcd7dcb 100644 --- a/src/components/Users/CreateUserForm.tsx +++ b/src/components/Users/CreateUserForm.tsx @@ -1,7 +1,9 @@ import { zodResolver } from "@hookform/resolvers/zod"; +import { useQuery } from "@tanstack/react-query"; import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { useTranslation } from "react-i18next"; +import { toast } from "sonner"; import * as z from "zod"; import CareIcon from "@/CAREUI/icons/CareIcon"; @@ -29,11 +31,13 @@ import { GENDER_TYPES } from "@/common/constants"; import { validateUsername } from "@/common/validation"; import * as Notification from "@/Utils/Notifications"; +import query from "@/Utils/request/query"; import request from "@/Utils/request/request"; import { classNames } from "@/Utils/utils"; import OrganizationSelector from "@/pages/Organization/components/OrganizationSelector"; import { UserBase } from "@/types/user/user"; import UserApi from "@/types/user/userApi"; +import userApi from "@/types/user/userApi"; const userFormSchema = z .object({ @@ -140,25 +144,55 @@ export default function CreateUserForm({ onSubmitSuccess }: Props) { ); const [usernameInput, setUsernameInput] = useState(""); - const checkUsername = async (username: string) => { - setUsernameExists(userExistsEnums.checking); - const { res: usernameCheck } = await request(UserApi.checkUsername, { - pathParams: { username }, + const { data, error, isLoading } = useQuery({ + queryKey: ["checkUsername", usernameInput], + queryFn: query(userApi.checkUsername, { + pathParams: { username: usernameInput }, silent: true, - }); - if (usernameCheck === undefined || usernameCheck.status === 409) { - setUsernameExists(userExistsEnums.exists); - } else if (usernameCheck.status === 200) { - setUsernameExists(userExistsEnums.available); + }), + enabled: validateUsername(usernameInput), + }); + + useEffect(() => { + if (!validateUsername(usernameInput)) { + setUsernameExists(userExistsEnums.idle); + return; + } + + if (isLoading) { + setUsernameExists(userExistsEnums.checking); + } else if (error) { + if (error instanceof Error && "status" in error) { + const status = (error as any).status; + + if (status === 409) { + setUsernameExists(userExistsEnums.exists); + } else if (status === 404) { + toast.error( + "Some error occurred while checking username availability. Please try again later.", + ); + setUsernameExists(userExistsEnums.idle); + } + } else { + toast.error( + "Some error occurred while checking username availability. Please try again later.", + ); + setUsernameExists(userExistsEnums.idle); + } } else { - Notification.Error({ - msg: "Some error occurred while checking username availability. Please try again later.", - }); + setUsernameExists(userExistsEnums.available); } - }; + }, [data, error, isLoading, usernameInput]); const renderFeedback = () => { switch (usernameExists) { + case userExistsEnums.checking: + return ( +
+ + Checking username... +
+ ); case userExistsEnums.available: return (
@@ -185,15 +219,11 @@ export default function CreateUserForm({ onSubmitSuccess }: Props) { } }; - useEffect(() => { + const handleInputChange = (e: React.ChangeEvent) => { + const value = e.target.value; + setUsernameInput(value); setUsernameExists(userExistsEnums.idle); - const timeout = setTimeout(() => { - if (validateUsername(usernameInput)) { - checkUsername(usernameInput); - } - }, 500); - return () => clearTimeout(timeout); - }, [usernameInput]); + }; const form = useForm({ resolver: zodResolver(userFormSchema), @@ -303,7 +333,6 @@ export default function CreateUserForm({ onSubmitSuccess }: Props) { )} />
- setUsernameInput(e.target.value)} + onChange={handleInputChange} /> @@ -557,7 +586,6 @@ export default function CreateUserForm({ onSubmitSuccess }: Props) { )} - Date: Thu, 9 Jan 2025 11:59:30 +0530 Subject: [PATCH 07/10] Username validation optimized --- public/locale/en.json | 1 + src/components/Users/CreateUserForm.tsx | 205 ++++++------------------ 2 files changed, 46 insertions(+), 160 deletions(-) diff --git a/public/locale/en.json b/public/locale/en.json index 358a1e61ec6..e85b72671b8 100644 --- a/public/locale/en.json +++ b/public/locale/en.json @@ -504,6 +504,7 @@ "check_policy_eligibility": "Check Policy Eligibility", "check_status": "Check Status", "checked_in": "Checked-In", + "checking_availability": "Checking Availability", "checking_consent_status": "Consent request status is being checked!", "checking_eligibility": "Checking Eligibility", "checking_for_update": "Checking for update", diff --git a/src/components/Users/CreateUserForm.tsx b/src/components/Users/CreateUserForm.tsx index bfe1dcd7dcb..7633e5fc4ce 100644 --- a/src/components/Users/CreateUserForm.tsx +++ b/src/components/Users/CreateUserForm.tsx @@ -1,9 +1,8 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { useQuery } from "@tanstack/react-query"; -import { useEffect, useState } from "react"; +import { useEffect } from "react"; import { useForm } from "react-hook-form"; import { useTranslation } from "react-i18next"; -import { toast } from "sonner"; import * as z from "zod"; import CareIcon from "@/CAREUI/icons/CareIcon"; @@ -27,13 +26,13 @@ import { SelectValue, } from "@/components/ui/select"; +import { validateRule } from "@/components/Users/UserFormValidations"; + import { GENDER_TYPES } from "@/common/constants"; -import { validateUsername } from "@/common/validation"; import * as Notification from "@/Utils/Notifications"; import query from "@/Utils/request/query"; import request from "@/Utils/request/request"; -import { classNames } from "@/Utils/utils"; import OrganizationSelector from "@/pages/Organization/components/OrganizationSelector"; import { UserBase } from "@/types/user/user"; import UserApi from "@/types/user/userApi"; @@ -100,131 +99,9 @@ interface Props { onSubmitSuccess?: (user: UserBase) => void; } -export const validateRule = ( - condition: boolean, - content: JSX.Element | string, - isInitialState: boolean = false, -) => { - return ( -
- {isInitialState ? ( - - ) : condition ? ( - - ) : ( - - )}{" "} - - {content} - -
- ); -}; - export default function CreateUserForm({ onSubmitSuccess }: Props) { const { t } = useTranslation(); - const userExistsEnums = { - idle: 0, - checking: 1, - exists: 2, - available: 3, - }; - - const [usernameExists, setUsernameExists] = useState( - userExistsEnums.idle, - ); - const [usernameInput, setUsernameInput] = useState(""); - - const { data, error, isLoading } = useQuery({ - queryKey: ["checkUsername", usernameInput], - queryFn: query(userApi.checkUsername, { - pathParams: { username: usernameInput }, - silent: true, - }), - enabled: validateUsername(usernameInput), - }); - - useEffect(() => { - if (!validateUsername(usernameInput)) { - setUsernameExists(userExistsEnums.idle); - return; - } - - if (isLoading) { - setUsernameExists(userExistsEnums.checking); - } else if (error) { - if (error instanceof Error && "status" in error) { - const status = (error as any).status; - - if (status === 409) { - setUsernameExists(userExistsEnums.exists); - } else if (status === 404) { - toast.error( - "Some error occurred while checking username availability. Please try again later.", - ); - setUsernameExists(userExistsEnums.idle); - } - } else { - toast.error( - "Some error occurred while checking username availability. Please try again later.", - ); - setUsernameExists(userExistsEnums.idle); - } - } else { - setUsernameExists(userExistsEnums.available); - } - }, [data, error, isLoading, usernameInput]); - - const renderFeedback = () => { - switch (usernameExists) { - case userExistsEnums.checking: - return ( -
- - Checking username... -
- ); - case userExistsEnums.available: - return ( -
- - - {t("username_available")} - -
- ); - case userExistsEnums.exists: - return ( -
- - - {t("username_not_available")} - -
- ); - default: - return null; - } - }; - - const handleInputChange = (e: React.ChangeEvent) => { - const value = e.target.value; - setUsernameInput(value); - setUsernameExists(userExistsEnums.idle); - }; - const form = useForm({ resolver: zodResolver(userFormSchema), defaultValues: { @@ -237,6 +114,7 @@ export default function CreateUserForm({ onSubmitSuccess }: Props) { }); const userType = form.watch("user_type"); + const usernameInput = form.watch("username"); const phoneNumber = form.watch("phone_number"); const isWhatsApp = form.watch("phone_number_is_whatsapp"); @@ -244,7 +122,44 @@ export default function CreateUserForm({ onSubmitSuccess }: Props) { if (isWhatsApp) { form.setValue("alt_phone_number", phoneNumber); } - }, [phoneNumber, isWhatsApp, form]); + if (usernameInput && usernameInput.length > 0) { + form.trigger("username"); + } + }, [phoneNumber, isWhatsApp, form, usernameInput]); + + const { error, isLoading } = useQuery({ + queryKey: ["checkUsername", usernameInput], + queryFn: query(userApi.checkUsername, { + pathParams: { username: usernameInput }, + silent: true, + }), + enabled: usernameInput?.length >= 4, + }); + + const renderUsernameFeedback = (usernameInput: string) => { + if ( + form.formState.errors.username && + form.formState.errors.username?.message + ) { + return validateRule(false, form.formState.errors.username.message); + } else if (isLoading) { + return ( +
+ + + {t("checking_availability")} + +
+ ); + } else if (error) { + return validateRule(false, <>{t("username_not_available")}); + } else if (usernameInput && !form.formState.errors.username && !isLoading) { + return validateRule(true, <>{t("username_available")}); + } + }; const onSubmit = async (data: UserFormValues) => { try { @@ -341,40 +256,10 @@ export default function CreateUserForm({ onSubmitSuccess }: Props) { {t("username")}
- +
- {renderFeedback()} - {validateRule( - usernameInput.length >= 4, - "Username must be at least 4 characters", - usernameInput.length === 0, - )} - {validateRule( - usernameInput.length < 16, - "Username must be less than 16 characters", - usernameInput.length === 0, - )} - {validateRule( - /^[a-z0-9._-]*$/.test(usernameInput), - "Username can only contain lowercase letters, numbers, and . _ -", - usernameInput.length === 0, - )} - {validateRule( - /^[a-z0-9].*[a-z0-9]$/.test(usernameInput), - "Username must start and end with a letter or number", - usernameInput.length === 0, - )} - {validateRule( - !usernameInput.match(/(?:[._-]{2,})/), - "Username cannot contain consecutive special characters", - usernameInput.length === 0, - )} + {renderUsernameFeedback(usernameInput)}
)} /> @@ -604,7 +489,7 @@ export default function CreateUserForm({ onSubmitSuccess }: Props) { /> From a096c53c29e63938066e8a12082429d1f96655df Mon Sep 17 00:00:00 2001 From: Rishith25 Date: Thu, 9 Jan 2025 12:58:48 +0530 Subject: [PATCH 08/10] i18n correction --- public/locale/en.json | 2 +- src/components/Users/CreateUserForm.tsx | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/public/locale/en.json b/public/locale/en.json index e85b72671b8..622a42974a1 100644 --- a/public/locale/en.json +++ b/public/locale/en.json @@ -1929,8 +1929,8 @@ "we_ve_sent_you_a_code_to": "We've sent you a code to", "weekly_working_hours_error": "Average weekly working hours must be a number between 0 and 168", "what_facility_assign_the_patient_to": "What facility would you like to assign the patient to", - "whataapp_number_same_as_phone_number": "WhatsApp number is same as phone number", "whatsapp_number": "Whatsapp Number", + "whatsapp_number_same_as_phone_number": "WhatsApp number is same as phone number", "why_the_asset_is_not_working": "Why the asset is not working?", "width": "Width ({{unit}})", "with": "with", diff --git a/src/components/Users/CreateUserForm.tsx b/src/components/Users/CreateUserForm.tsx index 7633e5fc4ce..6a9bad607a0 100644 --- a/src/components/Users/CreateUserForm.tsx +++ b/src/components/Users/CreateUserForm.tsx @@ -133,7 +133,7 @@ export default function CreateUserForm({ onSubmitSuccess }: Props) { pathParams: { username: usernameInput }, silent: true, }), - enabled: usernameInput?.length >= 4, + enabled: !form.formState.errors.username, }); const renderUsernameFeedback = (usernameInput: string) => { @@ -156,7 +156,7 @@ export default function CreateUserForm({ onSubmitSuccess }: Props) { ); } else if (error) { return validateRule(false, <>{t("username_not_available")}); - } else if (usernameInput && !form.formState.errors.username && !isLoading) { + } else if (usernameInput) { return validateRule(true, <>{t("username_available")}); } }; @@ -364,7 +364,7 @@ export default function CreateUserForm({ onSubmitSuccess }: Props) {
- {t("whataapp_number_same_as_phone_number")} + {t("whatsapp_number_same_as_phone_number")}
From 46de7b7b3b19e6ebb5e419c6cf7480d3b35e6cb5 Mon Sep 17 00:00:00 2001 From: Rishith25 Date: Thu, 9 Jan 2025 13:09:04 +0530 Subject: [PATCH 09/10] optimizing changes --- src/components/Users/CreateUserForm.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/Users/CreateUserForm.tsx b/src/components/Users/CreateUserForm.tsx index 6a9bad607a0..c6d2905ca5e 100644 --- a/src/components/Users/CreateUserForm.tsx +++ b/src/components/Users/CreateUserForm.tsx @@ -137,11 +137,11 @@ export default function CreateUserForm({ onSubmitSuccess }: Props) { }); const renderUsernameFeedback = (usernameInput: string) => { - if ( - form.formState.errors.username && - form.formState.errors.username?.message - ) { - return validateRule(false, form.formState.errors.username.message); + const { + errors: { username }, + } = form.formState; + if (username?.message) { + return validateRule(false, username.message); } else if (isLoading) { return (
@@ -155,9 +155,9 @@ export default function CreateUserForm({ onSubmitSuccess }: Props) {
); } else if (error) { - return validateRule(false, <>{t("username_not_available")}); + return validateRule(false, t("username_not_available")); } else if (usernameInput) { - return validateRule(true, <>{t("username_available")}); + return validateRule(true, t("username_available")); } }; From d31ab8a0e1807cd7074d4f65bf2d6cded9d3c554 Mon Sep 17 00:00:00 2001 From: Rishith25 Date: Thu, 9 Jan 2025 13:28:16 +0530 Subject: [PATCH 10/10] corrected font size --- src/components/Users/CreateUserForm.tsx | 2 +- src/components/Users/UserFormValidations.tsx | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/Users/CreateUserForm.tsx b/src/components/Users/CreateUserForm.tsx index c6d2905ca5e..6c1e9483b50 100644 --- a/src/components/Users/CreateUserForm.tsx +++ b/src/components/Users/CreateUserForm.tsx @@ -147,7 +147,7 @@ export default function CreateUserForm({ onSubmitSuccess }: Props) {
{t("checking_availability")} diff --git a/src/components/Users/UserFormValidations.tsx b/src/components/Users/UserFormValidations.tsx index f6173024873..9b6e90b92a4 100644 --- a/src/components/Users/UserFormValidations.tsx +++ b/src/components/Users/UserFormValidations.tsx @@ -57,13 +57,13 @@ export const validateRule = ( isInitialState: boolean = false, ) => { return ( -
+
{isInitialState ? ( - + ) : condition ? ( - + ) : ( - + )}{" "}