From a028a6cdceb90b91682cf6b04414ca3a6b6f4d88 Mon Sep 17 00:00:00 2001 From: JavidSumra <112365664+JavidSumra@users.noreply.github.com> Date: Tue, 12 Nov 2024 13:17:30 +0530 Subject: [PATCH 1/4] Refactor Cypress Auth Test to POM and Improve Test Stability (#9024) Co-authored-by: Mohammed Nihal <57055998+nihal467@users.noreply.github.com> --- cypress/e2e/auth_spec/ForgotPassword.cy.ts | 17 ----- cypress/e2e/auth_spec/auth.cy.ts | 23 ------- cypress/e2e/homepage_spec/UserLogin.cy.ts | 48 ++++++++++++++ .../redirect.cy.ts | 0 cypress/pageobject/Login/LoginPage.ts | 64 +++++++++++++++++-- src/components/Auth/Login.tsx | 11 +++- 6 files changed, 117 insertions(+), 46 deletions(-) delete mode 100644 cypress/e2e/auth_spec/ForgotPassword.cy.ts delete mode 100644 cypress/e2e/auth_spec/auth.cy.ts create mode 100644 cypress/e2e/homepage_spec/UserLogin.cy.ts rename cypress/e2e/{auth_spec => homepage_spec}/redirect.cy.ts (100%) diff --git a/cypress/e2e/auth_spec/ForgotPassword.cy.ts b/cypress/e2e/auth_spec/ForgotPassword.cy.ts deleted file mode 100644 index c1b1db7ecad..00000000000 --- a/cypress/e2e/auth_spec/ForgotPassword.cy.ts +++ /dev/null @@ -1,17 +0,0 @@ -describe("Forgot Password", () => { - beforeEach(() => { - cy.awaitUrl("/", true); - cy.get("button").contains("Forgot password?").click().wait(100); - }); - - it("Send Password Reset Link", () => { - cy.get("input[id='forgot_username']").type("dummy_user_1"); - cy.contains("Send Reset Link").click().wait(1000); - cy.contains("Password Reset Email Sent").should("exist"); - }); - - it("Go to Login page", () => { - cy.get("button").contains("Back to login").click(); - cy.url().should("contain", "/"); - }); -}); diff --git a/cypress/e2e/auth_spec/auth.cy.ts b/cypress/e2e/auth_spec/auth.cy.ts deleted file mode 100644 index b2bd7b634c0..00000000000 --- a/cypress/e2e/auth_spec/auth.cy.ts +++ /dev/null @@ -1,23 +0,0 @@ -describe("Authorisation/Authentication", () => { - beforeEach(() => { - cy.awaitUrl("/", true); - }); - - it("Try login as admin with correct password", () => { - cy.loginByApi("devdistrictadmin", "Coronasafe@123"); - cy.awaitUrl("/facility"); - cy.get("#user-profile-name").click(); - cy.get("#sign-out-button").contains("Sign Out").click(); - cy.url().should("include", "/"); - }); - - it("Try login as admin with incorrect password", () => { - cy.log("Logging in the user: devdistrictadmin:Coronasafe@123"); - - cy.awaitUrl("/", true); - cy.get("input[id='username']").type("devdistrictadmin"); - cy.get("input[id='password']").type("coronasafe@123"); - cy.get("button").contains("Login").click(); - cy.contains("No active account").should("exist"); - }); -}); diff --git a/cypress/e2e/homepage_spec/UserLogin.cy.ts b/cypress/e2e/homepage_spec/UserLogin.cy.ts new file mode 100644 index 00000000000..ab00bd80ff1 --- /dev/null +++ b/cypress/e2e/homepage_spec/UserLogin.cy.ts @@ -0,0 +1,48 @@ +import LoginPage from "pageobject/Login/LoginPage"; + +const loginPage = new LoginPage(); +const userName = "dummy_user_1"; +const forgotPasswordHeading = "Forgot password?"; + +describe("User login workflow with correct and incorrect passwords", () => { + beforeEach(() => { + cy.awaitUrl("/", true); + }); + + it("Log in as admin with correct password", () => { + loginPage.loginManuallyAsDistrictAdmin(); + loginPage.interceptFacilityReq(); + loginPage.verifyFacilityReq(); + loginPage.ensureLoggedIn(); + loginPage.clickSignOutBtn(); + loginPage.verifyLoginPageUrl(); + }); + + it("Display an error when logging in as admin with incorrect password", () => { + loginPage.interceptLoginReq(); + loginPage.loginManuallyAsDistrictAdmin(false); + loginPage.verifyLoginReq(); + cy.verifyNotification("No active account found with the given credentials"); + cy.closeNotification(); + }); +}); + +describe("Reset user's password using email", () => { + beforeEach(() => { + cy.awaitUrl("/", true); + }); + + it("Send a password reset link and navigate back to the login page", () => { + loginPage.clickForgotPasswordButton(forgotPasswordHeading); + loginPage.verifyForgotPasswordHeading([forgotPasswordHeading]); + loginPage.fillUserNameInForgotPasswordForm(userName); + loginPage.interceptResetLinkReq(); + loginPage.clickSendResetLinkBtn(); + loginPage.verifyResetLinkReq(); + cy.verifyNotification("Password Reset Email Sent"); + cy.closeNotification(); + loginPage.clickBackButton(); + loginPage.verifyLoginPageUrl(); + loginPage.verifyLoginButtonPresence(); + }); +}); diff --git a/cypress/e2e/auth_spec/redirect.cy.ts b/cypress/e2e/homepage_spec/redirect.cy.ts similarity index 100% rename from cypress/e2e/auth_spec/redirect.cy.ts rename to cypress/e2e/homepage_spec/redirect.cy.ts diff --git a/cypress/pageobject/Login/LoginPage.ts b/cypress/pageobject/Login/LoginPage.ts index 38b8aeee2af..07eb4486d17 100644 --- a/cypress/pageobject/Login/LoginPage.ts +++ b/cypress/pageobject/Login/LoginPage.ts @@ -13,16 +13,20 @@ class LoginPage { cy.loginByApi("staffdev", "Coronasafe@123"); } - loginManuallyAsDistrictAdmin(): void { + loginManuallyAsDistrictAdmin(isCorrectCredentials: boolean = true): void { cy.get("input[id='username']").type("devdistrictadmin"); - cy.get("input[id='password']").type("Coronasafe@123"); - cy.get("button").contains("Login").click(); + if (isCorrectCredentials) { + cy.get("input[id='password']").type("Coronasafe@123"); + } else { + cy.get("input[id='password']").type("Corona"); + } + cy.submitButton("Login"); } loginManuallyAsNurse(): void { cy.get("input[id='username']").click().type("dummynurse1"); cy.get("input[id='password']").click().type("Coronasafe@123"); - cy.get("button").contains("Login").click(); + cy.submitButton("Login"); } login(username: string, password: string): void { @@ -38,6 +42,58 @@ class LoginPage { clickSignOutBtn(): void { cy.verifyAndClickElement("#sign-out-button", "Sign Out"); } + + fillUserNameInForgotPasswordForm(userName: string): void { + cy.get("#forgot_username").type(userName); + } + + clickSendResetLinkBtn(): void { + cy.verifyAndClickElement("#send-reset-link-btn", "Send Reset Link"); + } + + verifyLoginPageUrl(): void { + cy.url().should("include", "/"); + } + + clickBackButton(): void { + cy.verifyAndClickElement("#back-to-login-btn", "Back to login"); + } + + clickForgotPasswordButton(text: string): void { + cy.verifyAndClickElement("#forgot-pass-btn", text); + } + + interceptFacilityReq(): void { + cy.intercept("GET", "**/api/v1/facility/**").as("getFacilities"); + } + + verifyFacilityReq(): void { + cy.wait("@getFacilities").its("response.statusCode").should("eq", 200); + } + + interceptLoginReq(): void { + cy.intercept("POST", "**/api/v1/auth/login").as("userLogin"); + } + + verifyLoginReq(): void { + cy.wait("@userLogin").its("response.statusCode").should("eq", 401); + } + + interceptResetLinkReq(): void { + cy.intercept("POST", "**/api/v1/password_reset").as("resetLink"); + } + + verifyResetLinkReq(): void { + cy.wait("@resetLink").its("response.statusCode").should("eq", 200); + } + + verifyLoginButtonPresence(): void { + cy.verifyContentPresence("#login-button", ["Login"]); + } + + verifyForgotPasswordHeading(text: string[]): void { + cy.verifyContentPresence("#forgot-password-heading", text); + } } export default LoginPage; diff --git a/src/components/Auth/Login.tsx b/src/components/Auth/Login.tsx index bcf0d4e3415..aab5f8df179 100644 --- a/src/components/Auth/Login.tsx +++ b/src/components/Auth/Login.tsx @@ -335,6 +335,7 @@ const Login = (props: { forgot?: boolean }) => { setForgotPassword(true); }} type="button" + id="forgot-pass-btn" className="text-sm text-primary-400 hover:text-primary-500" > {t("forget_password")} @@ -347,6 +348,7 @@ const Login = (props: { forgot?: boolean }) => { ) : ( -
+
{t("forget_password")}
-
+
{t("forget_password_instruction")} { ) : (
- } - show={show} - onClose={onClose} - className="max-w-[500px]" - fixedWidth={false} - > -
-
- -
-
- {[ - { - label: t("full_name"), - value: - abha?.name || - `${abha?.first_name} ${abha?.middle_name} ${abha?.last_name}`, - }, - { label: t("date_of_birth"), value: abha?.date_of_birth }, - { label: t("gender"), value: abha?.gender }, - { label: t("abha_number"), value: abha?.abha_number }, - { - label: t("abha_address"), - value: abha?.health_id?.split("@")[0], - }, - { label: t("email"), value: abha?.email }, - ].map((item, index) => - item.value ? ( -
-
{item.label}
-
{item.value}
-
- ) : null, - )} -
-
- -
- {abha?.created_date && ( -
- {t("created_on")}: - {formatDateTime(abha.created_date)} -
- )} - {abha?.modified_date && ( -
- {t("modified_on")}: - {formatDateTime(abha.modified_date)} -
- )} -
- - ); -}; - -export default ABHAProfileModal; diff --git a/src/components/ABDM/ConfigureHealthFacility.tsx b/src/components/ABDM/ConfigureHealthFacility.tsx deleted file mode 100644 index ba3eb325dec..00000000000 --- a/src/components/ABDM/ConfigureHealthFacility.tsx +++ /dev/null @@ -1,243 +0,0 @@ -import { navigate } from "raviger"; -import { useReducer, useState } from "react"; -import { useTranslation } from "react-i18next"; - -import { IHealthFacility } from "@/components/ABDM/types/health-facility"; -import { Submit } from "@/components/Common/ButtonV2"; -import Loading from "@/components/Common/Loading"; -import TextFormField from "@/components/Form/FormFields/TextFormField"; -import { FieldChangeEvent } from "@/components/Form/FormFields/Utils"; - -import * as Notification from "@/Utils/Notifications"; -import routes from "@/Utils/request/api"; -import request from "@/Utils/request/request"; -import useQuery from "@/Utils/request/useQuery"; -import { classNames } from "@/Utils/utils"; - -const initForm = { - health_facility: null as IHealthFacility | null, - hf_id: "", -}; - -const initialState = { - form: { ...initForm }, - errors: {} as Partial>, -}; - -const FormReducer = ( - state = initialState, - action: - | { - type: "set_form"; - form: typeof initialState.form; - } - | { - type: "set_error"; - errors: typeof initialState.errors; - }, -) => { - switch (action.type) { - case "set_form": { - return { - ...state, - form: action.form, - }; - } - case "set_error": { - return { - ...state, - errors: action.errors, - }; - } - default: - return state; - } -}; - -export interface IConfigureHealthFacilityProps { - facilityId: string; -} - -export const ConfigureHealthFacility = ( - props: IConfigureHealthFacilityProps, -) => { - const { t } = useTranslation(); - - const [state, dispatch] = useReducer(FormReducer, initialState); - const { facilityId } = props; - const [isLoading, setIsLoading] = useState(false); - - const { loading } = useQuery(routes.abdm.healthFacility.get, { - pathParams: { facility_id: facilityId }, - silent: true, - onResponse(res) { - if (res.data) { - dispatch({ - type: "set_form", - form: { - ...state.form, - health_facility: res.data, - hf_id: res.data.hf_id, - }, - }); - } - }, - }); - - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - setIsLoading(true); - - if (!state.form.hf_id) { - dispatch({ - type: "set_error", - errors: { hf_id: t("health_facility__validation__hf_id_required") }, - }); - setIsLoading(false); - return; - } - - let response = null; - let responseData = null; - if (state.form.hf_id === state.form.health_facility?.hf_id) { - const { res, data } = await request( - routes.abdm.healthFacility.registerAsService, - { - pathParams: { - facility_id: facilityId, - }, - }, - ); - response = res; - responseData = data; - } else if (state.form.health_facility) { - const { res, data } = await request( - routes.abdm.healthFacility.partialUpdate, - { - pathParams: { - facility_id: facilityId, - }, - body: { - hf_id: state.form.hf_id, - }, - }, - ); - response = res; - responseData = data; - } else { - const { res, data } = await request(routes.abdm.healthFacility.create, { - body: { - facility: facilityId, - hf_id: state.form.hf_id, - }, - silent: true, - }); - response = res; - responseData = data; - } - - if (response?.ok && responseData?.registered) { - Notification.Success({ - msg: t("health_facility__config_update_success"), - }); - navigate(`/facility/${facilityId}`); - } else { - if (responseData?.registered === false) { - Notification.Warn({ - msg: - responseData?.detail || - t("health_facility__config_registration_error"), - }); - navigate(`/facility/${facilityId}`); - } else { - Notification.Error({ - msg: - responseData?.detail || t("health_facility__config_update_error"), - }); - } - } - setIsLoading(false); - }; - - const handleChange = (e: FieldChangeEvent) => { - dispatch({ - type: "set_form", - form: { ...state.form, [e.name]: e.value }, - }); - }; - - if (loading || isLoading) { - return ; - } - - return ( -
- -
-
- - {state.form.health_facility?.registered ? ( - <> -
- - {t("health_facility__registered_1.1")}{" "} - - {t("health_facility__registered_1.2")} - - - - {t("health_facility__registered_2")} - -
- {t("health_facility__registered_3")} - - ) : ( - <> -
- - {t("health_facility__not_registered_1.1")}{" "} - - {t("health_facility__not_registered_1.2")} - - - - {t("health_facility__not_registered_2")} - - {t("health_facility__not_registered_3")} -
- - )} -

- } - required - value={state.form.hf_id} - onChange={handleChange} - error={state.errors?.hf_id} - /> -
-
-
- -
- -
- ); -}; diff --git a/src/components/ABDM/FetchRecordsModal.tsx b/src/components/ABDM/FetchRecordsModal.tsx deleted file mode 100644 index 9433374f931..00000000000 --- a/src/components/ABDM/FetchRecordsModal.tsx +++ /dev/null @@ -1,246 +0,0 @@ -import dayjs from "dayjs"; -import { navigate } from "raviger"; -import { useState } from "react"; -import { useTranslation } from "react-i18next"; - -import { AbhaNumberModel } from "@/components/ABDM/types/abha"; -import { ConsentHIType, ConsentPurpose } from "@/components/ABDM/types/consent"; -import ButtonV2 from "@/components/Common/ButtonV2"; -import DialogModal from "@/components/Common/Dialog"; -import DateFormField from "@/components/Form/FormFields/DateFormField"; -import DateRangeFormField from "@/components/Form/FormFields/DateRangeFormField"; -import { - MultiSelectFormField, - SelectFormField, -} from "@/components/Form/FormFields/SelectFormField"; -import TextFormField from "@/components/Form/FormFields/TextFormField"; - -import { useMessageListener } from "@/hooks/useMessageListener"; - -import { ABDM_CONSENT_PURPOSE, ABDM_HI_TYPE } from "@/common/constants"; - -import * as Notification from "@/Utils/Notifications"; -import routes from "@/Utils/request/api"; -import request from "@/Utils/request/request"; - -const getDate = (value: string | Date) => - (value && dayjs(value).isValid() && dayjs(value).toDate()) || undefined; - -interface IProps { - abha?: AbhaNumberModel; - show: boolean; - onClose: () => void; -} - -export default function FetchRecordsModal({ abha, show, onClose }: IProps) { - const { t } = useTranslation(); - - const [idVerificationStatus, setIdVerificationStatus] = useState< - "pending" | "in-progress" | "verified" | "failed" - >("verified"); - const [purpose, setPurpose] = useState("CAREMGT"); - const [fromDate, setFromDate] = useState( - dayjs().subtract(30, "day").toDate(), - ); - const [toDate, setToDate] = useState(dayjs().toDate()); - const [isMakingConsentRequest, setIsMakingConsentRequest] = useState(false); - const [hiTypes, setHiTypes] = useState([]); - const [expiryDate, setExpiryDate] = useState( - dayjs().add(30, "day").toDate(), - ); - const [errors, setErrors] = useState>({}); - // const notificationSubscriptionState = useNotificationSubscriptionState([ - // show, - // ]); - - useMessageListener((data) => { - if (data.type === "MESSAGE" && data.from === "patients/on_find") { - if (data.message?.patient?.id === abha?.health_id) { - setIdVerificationStatus("verified"); - setErrors({ - ...errors, - health_id: "", - }); - } - } - }); - - return ( - - {/* {["unsubscribed", "subscribed_on_other_device"].includes( - notificationSubscriptionState, - ) && ( -

- {" "} - Notifications needs to be enabled on this device to verify the - patient. -

- )} */} - -
- null} - disabled - label={t("consent_request__patient_identifier")} - name="health_id" - error={errors.health_id} - className="flex-1" - /> - - {/* { - const { res } = await request(routes.abha.findPatient, { - body: { - id: abha?.health_id, - }, - reattempts: 0, - }); - - if (res?.status) { - setIdVerificationStatus("in-progress"); - } - }} - loading={idVerificationStatus === "in-progress"} - ghost={idVerificationStatus === "verified"} - disabled={ - idVerificationStatus === "verified" || - ["unsubscribed", "subscribed_on_other_device"].includes( - notificationSubscriptionState, - ) - } - className={classNames( - "mt-1.5 !py-3", - idVerificationStatus === "verified" && - "disabled:cursor-auto disabled:bg-transparent disabled:text-primary-600", - )} - > - {idVerificationStatus === "in-progress" && ( - - )} - {idVerificationStatus === "verified" && } - { - { - pending: "Verify Patient", - "in-progress": "Verifying", - verified: "Verified", - failed: "Retry", - }[idVerificationStatus] - } - */} -
- t(`consent__purpose__${o}`)} - optionValue={(o) => o} - value={purpose} - onChange={({ value }) => setPurpose(value)} - required - /> - - { - setFromDate(e.value.start!); - setToDate(e.value.end!); - }} - label={t("consent_request__date_range")} - required - /> - - { - setHiTypes(ABDM_HI_TYPE); - }} - > - {t("select_all")} - - ) - } - value={hiTypes} - optionLabel={(option) => t(`consent__hi_type__${option}`)} - optionValue={(option) => option} - onChange={(e) => setHiTypes(e.value)} - required - /> - - setExpiryDate(e.value!)} - label={t("consent_request__expiry")} - required - disablePast - /> - -
- { - if (idVerificationStatus !== "verified") { - setErrors({ - ...errors, - health_id: t("verify_patient_identifier"), - }); - - return; - } - - setIsMakingConsentRequest(true); - const { res } = await request(routes.abdm.consent.create, { - body: { - patient_abha: abha?.health_id as string, - hi_types: hiTypes, - purpose, - from_time: fromDate, - to_time: toDate, - expiry: expiryDate, - }, - }); - - if (res?.status === 201) { - Notification.Success({ - msg: t("consent_requested_successfully"), - }); - - navigate( - `/facility/${abha?.patient_object?.facility}/abdm`, - // ?? `/facility/${abha?.patient_object?.facility}/patient/${abha?.patient_object?.id}/consultation/${abha?.patient_object?.last_consultation?.id}/abdm`, - ); - } - setIsMakingConsentRequest(false); - onClose(); - }} - disabled={idVerificationStatus !== "verified"} - loading={isMakingConsentRequest} - > - {t("request_consent")} - -
-
- ); -} diff --git a/src/components/ABDM/HealthInformation.tsx b/src/components/ABDM/HealthInformation.tsx deleted file mode 100644 index e147f050131..00000000000 --- a/src/components/ABDM/HealthInformation.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import { HIProfile } from "hi-profiles"; -import { useTranslation } from "react-i18next"; - -import Loading from "@/components/Common/Loading"; -import Page from "@/components/Common/Page"; - -import routes from "@/Utils/request/api"; -import useQuery from "@/Utils/request/useQuery"; - -interface IProps { - artefactId: string; -} - -export default function HealthInformation({ artefactId }: IProps) { - const { t } = useTranslation(); - - const { data, loading, error } = useQuery(routes.abdm.healthInformation.get, { - pathParams: { artefactId }, - silent: true, - }); - - if (loading) { - return ; - } - - const parseData = (data: string) => { - try { - return JSON.parse(data); - } catch (e) { - return JSON.parse( - data.replace(/"/g, '\\"').replace(/'/g, '"'), // eslint-disable-line - ); - } - }; - - return ( - -
- {!!error?.is_archived && ( - <> -

- {t("hi__record_archived__title")} -

-
- {t("hi__record_archived_description")} -
-

- {t("hi__record_archived_on")}{" "} - {new Date(error?.archived_time as string).toLocaleString()} -{" "} - {error?.archived_reason as string} -

- - )} - {error && !error?.is_archived && ( - <> -

- {t("hi__record_not_fetched_title")} -

-
- {t("hi__record_not_fetched_description")} -
-

- {t("hi__waiting_for_record")} -

- - )} - {data?.data.map((item) => ( - - ))} -
-
- ); -} diff --git a/src/components/ABDM/LinkAbhaNumber/CreateWithAadhaar.tsx b/src/components/ABDM/LinkAbhaNumber/CreateWithAadhaar.tsx deleted file mode 100644 index d2697633bea..00000000000 --- a/src/components/ABDM/LinkAbhaNumber/CreateWithAadhaar.tsx +++ /dev/null @@ -1,740 +0,0 @@ -import { useEffect, useState } from "react"; -import { useTranslation } from "react-i18next"; - -import useMultiStepForm, { - InjectedStepProps, -} from "@/components/ABDM/LinkAbhaNumber/useMultiStepForm"; -import { AbhaNumberModel } from "@/components/ABDM/types/abha"; -import ButtonV2, { ButtonWithTimer } from "@/components/Common/ButtonV2"; -import CheckBoxFormField from "@/components/Form/FormFields/CheckBoxFormField"; -import OtpFormField from "@/components/Form/FormFields/OtpFormField"; -import PhoneNumberFormField from "@/components/Form/FormFields/PhoneNumberFormField"; -import TextFormField from "@/components/Form/FormFields/TextFormField"; -import { validateRule } from "@/components/Users/UserAdd"; - -import * as Notify from "@/Utils/Notifications"; -import routes from "@/Utils/request/api"; -import request from "@/Utils/request/request"; -import { classNames } from "@/Utils/utils"; - -const MAX_OTP_RESEND_ALLOWED = 2; - -type ICreateWithAadhaarProps = { - onSuccess: (abhaNumber: AbhaNumberModel) => void; -}; - -type Memory = { - aadhaarNumber: string; - mobileNumber: string; - - isLoading: boolean; - validationError: string; - - transactionId: string; - abhaNumber: AbhaNumberModel | null; - - resendOtpCount: number; -}; - -export default function CreateWithAadhaar({ - onSuccess, -}: ICreateWithAadhaarProps) { - const { currentStep } = useMultiStepForm( - [ - , - , - , - , - , - , - ], - { - aadhaarNumber: "", - mobileNumber: "+91", - isLoading: false, - validationError: "", - transactionId: "", - abhaNumber: null, - resendOtpCount: 0, - }, - ); - - return
{currentStep}
; -} - -type IEnterAadhaarProps = InjectedStepProps; - -function EnterAadhaar({ memory, setMemory, next }: IEnterAadhaarProps) { - const { t } = useTranslation(); - const [disclaimerAccepted, setDisclaimerAccepted] = useState([ - false, - false, - false, - false, - ]); - - const validateAadhaar = () => { - if ( - memory?.aadhaarNumber.length !== 12 && - memory?.aadhaarNumber.length !== 16 - ) { - setMemory((prev) => ({ - ...prev, - validationError: t("aadhaar_validation_length_error"), - })); - return false; - } - - if (memory?.aadhaarNumber.includes(" ")) { - setMemory((prev) => ({ - ...prev, - validationError: t("aadhaar_validation_space_error"), - })); - return false; - } - - return true; - }; - - const handleSubmit = async () => { - if (!validateAadhaar()) return; - - setMemory((prev) => ({ ...prev, isLoading: true })); - - const { res, data } = await request( - routes.abdm.healthId.abhaCreateSendAadhaarOtp, - { - body: { - aadhaar: memory!.aadhaarNumber, - }, - }, - ); - - if (res?.status === 200 && data) { - setMemory((prev) => ({ ...prev, transactionId: data.transaction_id })); - Notify.Success({ - msg: data.detail ?? t("aadhaar_otp_send_success"), - }); - next(); - } - - setMemory((prev) => ({ ...prev, isLoading: false })); - }; - - return ( -
-
- - setMemory((prev) => ({ ...prev, aadhaarNumber: value })) - } - error={memory?.validationError} - /> - - {t("aadhaar_number_will_not_be_stored")} - -
- -
- {disclaimerAccepted.map((isAccepted, i) => ( - { - setDisclaimerAccepted( - disclaimerAccepted.map((v, j) => (j === i ? e.value : v)), - ); - }} - className="mr-2 rounded border-gray-700" - labelClassName="text-xs text-gray-800" - errorClassName="hidden" - /> - ))} -
- -
- !v) || - memory?.aadhaarNumber.length === 0 - } - onClick={handleSubmit} - > - {t("send_otp")} - -
-
- ); -} - -type IVerifyAadhaarProps = InjectedStepProps; - -function VerifyAadhaar({ memory, setMemory, next }: IVerifyAadhaarProps) { - const { t } = useTranslation(); - const [otp, setOtp] = useState(""); - - const validateMobileNumber = () => { - const phone = memory?.mobileNumber.replace("+91", "").replace(/ /g, ""); - if (phone?.length !== 10) { - setMemory((prev) => ({ - ...prev, - validationError: t("mobile_number_validation_error"), - })); - return false; - } - - return true; - }; - - const handleSubmit = async () => { - if (!validateMobileNumber()) return; - - setMemory((prev) => ({ ...prev, isLoading: true })); - - const { res, data } = await request( - routes.abdm.healthId.abhaCreateVerifyAadhaarOtp, - { - body: { - otp: otp, - transaction_id: memory?.transactionId, - mobile: memory?.mobileNumber.replace("+91", "").replace(/ /g, ""), - }, - }, - ); - - if (res?.status === 200 && data) { - setMemory((prev) => ({ - ...prev, - transactionId: data.transaction_id, - abhaNumber: data.abha_number, - resendOtpCount: 0, - })); - Notify.Success({ - msg: data.detail ?? t("otp_verification_success"), - }); - next(); - } - - setMemory((prev) => ({ ...prev, isLoading: false })); - }; - - const handleResendOtp = async () => { - setMemory((prev) => ({ ...prev, isLoading: true })); - - const { res, data } = await request( - routes.abdm.healthId.abhaCreateSendAadhaarOtp, - { - body: { - aadhaar: memory!.aadhaarNumber, - // transaction_id: memory?.transactionId, - }, - silent: true, - }, - ); - - if (res?.status === 200 && data) { - setMemory((prev) => ({ - ...prev, - transactionId: data.transaction_id, - resendOtpCount: prev.resendOtpCount + 1, - })); - Notify.Success({ - msg: data.detail ?? t("aadhaar_otp_send_success"), - }); - } else { - setMemory((prev) => ({ - ...prev, - resendOtpCount: Infinity, - })); - Notify.Success({ - msg: t("aadhaar_otp_send_error"), - }); - } - - setMemory((prev) => ({ ...prev, isLoading: false })); - }; - - return ( -
-
- - setMemory((prev) => ({ ...prev, aadhaarNumber: value })) - } - /> - - {t("aadhaar_number_will_not_be_stored")} - -
- -
- setOtp(value as string)} - value={otp} - label={t("enter_aadhaar_otp")} - disabled={memory?.isLoading} - /> -
- -
- } - name="mobile_number" - value={memory?.mobileNumber} - onChange={(e) => { - if (!memory?.mobileNumber.startsWith("+91")) { - setMemory((prev) => ({ - ...prev, - validationError: t("only_indian_mobile_numbers_supported"), - })); - return; - } - - setMemory((prev) => ({ ...prev, mobileNumber: e.value })); - }} - error={memory?.validationError} - errorClassName="text-xs text-red-500" - types={["mobile"]} - /> -
- -
- 6 || memory?.mobileNumber.length === 0} - onClick={handleSubmit} - > - {t("verify_otp")} - - - {(memory?.resendOtpCount ?? 0) < MAX_OTP_RESEND_ALLOWED && ( - - {t("resend_otp")} - - )} -
-
- ); -} - -type IHandleExistingAbhaNumberProps = InjectedStepProps & { - onSuccess: (abhaNumber: AbhaNumberModel) => void; -}; - -function HandleExistingAbhaNumber({ - memory, - onSuccess, - next, -}: IHandleExistingAbhaNumberProps) { - const { t } = useTranslation(); - - // skip this step for new abha number - useEffect(() => { - if (memory?.abhaNumber?.new) { - next(); - } - }, [memory?.abhaNumber, memory?.mobileNumber]); // eslint-disable-line - - return ( -
-

- {t("abha_number_exists")} -

-

- {t("abha_number_exists_description")} -

-
- - {t("create_new_abha_address")} - - onSuccess(memory?.abhaNumber as AbhaNumberModel)} - > - {t("use_existing_abha_address")} - -

- {memory?.abhaNumber?.health_id} -

-
-
- ); -} - -type ILinkMobileNumberProps = InjectedStepProps; - -function LinkMobileNumber({ - memory, - goTo, - setMemory, - next, -}: ILinkMobileNumberProps) { - const { t } = useTranslation(); - - useEffect(() => { - if ( - memory?.abhaNumber?.mobile === - memory?.mobileNumber.replace("+91", "").replace(/ /g, "") - ) { - goTo(5); // skip linking mobile number - } - }, [memory?.abhaNumber, memory?.mobileNumber]); // eslint-disable-line - - const handleSubmit = async () => { - setMemory((prev) => ({ ...prev, isLoading: true })); - - const { res, data } = await request( - routes.abdm.healthId.abhaCreateLinkMobileNumber, - { - body: { - mobile: memory?.mobileNumber.replace("+91", "").replace(/ /g, ""), - transaction_id: memory?.transactionId, - }, - }, - ); - - if (res?.status === 200 && data) { - setMemory((prev) => ({ - ...prev, - transactionId: data.transaction_id, - })); - Notify.Success({ - msg: data.detail ?? t("mobile_otp_send_success"), - }); - next(); - } - - setMemory((prev) => ({ ...prev, isLoading: false })); - }; - - return ( -
-
- } - name="mobile_number" - value={memory?.mobileNumber} - disabled={true} - onChange={() => null} - types={["mobile"]} - /> -
- -

- {t("mobile_number_different_from_aadhaar_mobile_number")} -

- -
- - {t("send_otp")} - -
-
- ); -} - -type IVerifyMobileNumberProps = InjectedStepProps; - -function VerifyMobileNumber({ - memory, - setMemory, - next, -}: IVerifyMobileNumberProps) { - const { t } = useTranslation(); - const [otp, setOtp] = useState(""); - - const handleSubmit = async () => { - setMemory((prev) => ({ ...prev, isLoading: true })); - - const { res, data } = await request( - routes.abdm.healthId.abhaCreateVerifyMobileNumber, - { - body: { - transaction_id: memory?.transactionId, - otp: otp, - }, - }, - ); - - if (res?.status === 200 && data) { - setMemory((prev) => ({ - ...prev, - transactionId: data.transaction_id, - resendOtpCount: 0, - })); - Notify.Success({ - msg: data.detail ?? t("mobile_otp_verify_success"), - }); - next(); - } - - setMemory((prev) => ({ ...prev, isLoading: false })); - }; - - const handleResendOtp = async () => { - setMemory((prev) => ({ ...prev, isLoading: true })); - - const { res, data } = await request( - routes.abdm.healthId.abhaCreateLinkMobileNumber, - { - body: { - mobile: memory?.mobileNumber.replace("+91", "").replace(/ /g, ""), - transaction_id: memory?.transactionId, - }, - }, - ); - - if (res?.status === 200 && data) { - setMemory((prev) => ({ - ...prev, - transactionId: data.transaction_id, - resendOtpCount: prev.resendOtpCount + 1, - })); - Notify.Success({ - msg: data.detail ?? t("mobile_otp_send_success"), - }); - } else { - setMemory((prev) => ({ - ...prev, - resendOtpCount: Infinity, - })); - Notify.Success({ - msg: t("mobile_otp_send_error"), - }); - } - - setMemory((prev) => ({ ...prev, isLoading: false })); - }; - - return ( -
-
- } - name="mobile_number" - value={memory?.mobileNumber} - disabled={true} - onChange={() => null} - types={["mobile"]} - /> -
- -
- setOtp(value as string)} - value={otp} - label={t("enter_mobile_otp")} - disabled={memory?.isLoading} - /> -
- -
- - {t("verify_otp")} - - - {(memory?.resendOtpCount ?? 0) < MAX_OTP_RESEND_ALLOWED && ( - - {t("resend_otp")} - - )} -
-
- ); -} - -type IChooseAbhaAddressProps = InjectedStepProps & { - onSuccess: (abhaNumber: AbhaNumberModel) => void; -}; - -function ChooseAbhaAddress({ - memory, - setMemory, - onSuccess, -}: IChooseAbhaAddressProps) { - const { t } = useTranslation(); - const [healthId, setHealthId] = useState(""); - const [suggestions, setSuggestions] = useState([]); - - useEffect(() => { - const fetchSuggestions = async () => { - const { res, data } = await request( - routes.abdm.healthId.abhaCreateAbhaAddressSuggestion, - { - body: { - transaction_id: memory?.transactionId, - }, - }, - ); - - if (res?.status === 200 && data) { - setMemory((prev) => ({ ...prev, transactionId: data.transaction_id })); - setSuggestions(data.abha_addresses); - } - }; - - fetchSuggestions(); - }, [healthId, memory?.transactionId, setMemory]); - - const handleSubmit = async () => { - setMemory((prev) => ({ ...prev, isLoading: true })); - - const { res, data } = await request( - routes.abdm.healthId.abhaCreateEnrolAbhaAddress, - { - body: { - abha_address: healthId, - transaction_id: memory?.transactionId, - }, - }, - ); - - if (res?.status === 200 && data) { - setMemory((prev) => ({ - ...prev, - transactionId: data.transaction_id, - abhaNumber: data.abha_number, - })); - Notify.Success({ - msg: data.detail ?? t("abha_address_created_success"), - }); - onSuccess(data.abha_number); - } - - setMemory((prev) => ({ ...prev, isLoading: false })); - }; - - return ( -
- { - setHealthId(value); - }} - /> - -
- {validateRule( - healthId.length >= 4, - t("abha_address_validation_length_error"), - false, - )} - {validateRule( - Number.isNaN(Number(healthId[0])) && healthId[0] !== ".", - t("abha_address_validation_start_error"), - false, - )} - {validateRule( - healthId[healthId.length - 1] !== ".", - t("abha_address_validation_end_error"), - false, - )} - {validateRule( - /^[0-9a-zA-Z._]+$/.test(healthId), - t("abha_address_validation_character_error"), - false, - )} -
- - {suggestions.length > 0 && ( -
-

- {t("abha_address_suggestions")} -

-
- {suggestions - .filter((suggestion) => suggestion !== healthId) - .map((suggestion) => ( -

setHealthId(suggestion)} - className="cursor-pointer rounded-md bg-primary-400 px-2.5 py-1 text-xs text-white" - > - {suggestion} -

- ))} -
-
- )} - -
- - {t("create_abha_address")} - -
-
- ); -} diff --git a/src/components/ABDM/LinkAbhaNumber/LinkWithOtp.tsx b/src/components/ABDM/LinkAbhaNumber/LinkWithOtp.tsx deleted file mode 100644 index 9b8150dee86..00000000000 --- a/src/components/ABDM/LinkAbhaNumber/LinkWithOtp.tsx +++ /dev/null @@ -1,350 +0,0 @@ -import { useMemo, useState } from "react"; -import { useTranslation } from "react-i18next"; - -import useMultiStepForm, { - InjectedStepProps, -} from "@/components/ABDM/LinkAbhaNumber/useMultiStepForm"; -import { AbhaNumberModel } from "@/components/ABDM/types/abha"; -import ButtonV2, { ButtonWithTimer } from "@/components/Common/ButtonV2"; -import Dropdown, { DropdownItem } from "@/components/Common/Menu"; -import CheckBoxFormField from "@/components/Form/FormFields/CheckBoxFormField"; -import OtpFormField from "@/components/Form/FormFields/OtpFormField"; -import TextFormField from "@/components/Form/FormFields/TextFormField"; - -import * as Notify from "@/Utils/Notifications"; -import routes from "@/Utils/request/api"; -import request from "@/Utils/request/request"; -import { classNames } from "@/Utils/utils"; - -const MAX_OTP_RESEND_ALLOWED = 2; - -type ILoginWithOtpProps = { - onSuccess: (abhaNumber: AbhaNumberModel) => void; -}; - -type Memory = { - id: string; - - isLoading: boolean; - validationError: string; - - transactionId: string; - type: "aadhaar" | "mobile" | "abha-number" | "abha-address"; - otp_system: "abdm" | "aadhaar"; - abhaNumber: AbhaNumberModel | null; - - resendOtpCount: number; -}; - -export default function LinkWithOtp({ onSuccess }: ILoginWithOtpProps) { - const { currentStep } = useMultiStepForm( - [ - , - , - ], - { - id: "", - isLoading: false, - validationError: "", - transactionId: "", - type: "aadhaar", - otp_system: "aadhaar", - abhaNumber: null, - resendOtpCount: 0, - }, - ); - - return
{currentStep}
; -} - -type IEnterIdProps = InjectedStepProps; - -const supportedAuthMethods = ["AADHAAR_OTP", "MOBILE_OTP"]; - -function EnterId({ memory, setMemory, next }: IEnterIdProps) { - const { t } = useTranslation(); - const [disclaimerAccepted, setDisclaimerAccepted] = useState([ - false, - false, - false, - ]); - const [authMethods, setAuthMethods] = useState([]); - - const valueType = useMemo(() => { - const id = memory?.id; - const isNumeric = !isNaN(Number(id?.trim())); - - if (isNumeric && (id?.length === 12 || id?.length === 16)) { - return "aadhaar"; - } else if (isNumeric && id?.length === 10) { - return "mobile"; - } else if (isNumeric && id?.length === 14) { - return "abha-number"; - } else { - return "abha-address"; - } - }, [memory?.id]); - - const handleGetAuthMethods = async () => { - setMemory((prev) => ({ ...prev, isLoading: true })); - - if (valueType === "aadhaar") { - setAuthMethods(["AADHAAR_OTP"]); - } else if (valueType === "mobile") { - setAuthMethods(["MOBILE_OTP"]); - } else { - const { res, data, error } = await request( - routes.abdm.healthId.abhaLoginCheckAuthMethods, - { - body: { - abha_address: memory?.id.replace(/-/g, "").replace(/ /g, ""), - }, - silent: true, - }, - ); - - if (res?.status === 200 && data) { - const methods = data.auth_methods.filter((method: string) => - supportedAuthMethods.find((supported) => supported === method), - ); - - if (methods.length === 0) { - Notify.Warn({ msg: t("get_auth_mode_error") }); - } - } else { - Notify.Error({ msg: error?.message ?? t("get_auth_mode_error") }); - } - } - - setMemory((prev) => ({ ...prev, isLoading: false })); - }; - - const handleSendOtp = async (authMethod: string) => { - if (!supportedAuthMethods.includes(authMethod)) { - Notify.Warn({ msg: t("auth_method_unsupported") }); - return; - } - - const otp_system: "aadhaar" | "abdm" = - authMethod === "AADHAAR_OTP" ? "aadhaar" : "abdm"; - - setMemory((prev) => ({ - ...prev, - isLoading: true, - type: valueType, - otp_system, - })); - - const { res, data } = await request(routes.abdm.healthId.abhaLoginSendOtp, { - body: { - value: memory?.id, - type: valueType, - otp_system, - }, - }); - - if (res?.status === 200 && data) { - setMemory((prev) => ({ - ...prev, - transactionId: data.transaction_id, - })); - Notify.Success({ msg: data.detail ?? t("send_otp_success") }); - next(); - } - - setMemory((prev) => ({ ...prev, isLoading: false })); - }; - - return ( -
-
- { - setMemory((prev) => ({ ...prev, id: value })); - setAuthMethods([]); - }} - error={memory?.validationError} - /> - - {t("any_id_description")} - -
- -
- {disclaimerAccepted.map((isAccepted, i) => ( - { - setDisclaimerAccepted( - disclaimerAccepted.map((v, j) => (j === i ? e.value : v)), - ); - }} - className="mr-2 rounded border-gray-700" - labelClassName="text-xs text-gray-800" - errorClassName="hidden" - /> - ))} -
- -
- {authMethods.length === 0 ? ( - !v) || memory?.id.length === 0 - } - onClick={handleGetAuthMethods} - > - {t("get_auth_methods")} - - ) : ( - - {authMethods.map((method) => ( - handleSendOtp(method)}> - {t(`abha__auth_method__${method}`)} - - ))} - - )} -
-
- ); -} - -type IVerifyIdProps = InjectedStepProps & { - onSuccess: (abhaNumber: AbhaNumberModel) => void; -}; - -function VerifyId({ memory, setMemory, onSuccess }: IVerifyIdProps) { - const { t } = useTranslation(); - const [otp, setOtp] = useState(""); - - const handleSubmit = async () => { - setMemory((prev) => ({ ...prev, isLoading: true })); - - const { res, data } = await request( - routes.abdm.healthId.abhaLoginVerifyOtp, - { - body: { - type: memory?.type, - transaction_id: memory?.transactionId, - otp, - otp_system: memory?.otp_system, - }, - }, - ); - - if (res?.status === 200 && data) { - Notify.Success({ msg: t("verify_otp_success") }); - onSuccess(data.abha_number); - } - - setMemory((prev) => ({ ...prev, isLoading: false })); - }; - - const handleResendOtp = async () => { - setMemory((prev) => ({ ...prev, isLoading: true })); - - const { res, data } = await request(routes.abdm.healthId.abhaLoginSendOtp, { - body: { - value: memory?.id, - type: memory?.type, - otp_system: memory?.otp_system, - }, - }); - - if (res?.status === 200 && data) { - setMemory((prev) => ({ - ...prev, - transactionId: data.transaction_id, - resendOtpCount: (prev.resendOtpCount ?? 0) + 1, - })); - Notify.Success({ msg: data.detail ?? t("send_otp_success") }); - } else { - setMemory((prev) => ({ - ...prev, - resendOtpCount: Infinity, - })); - Notify.Error({ msg: t("send_otp_error") }); - } - - setMemory((prev) => ({ ...prev, isLoading: false })); - }; - - return ( -
-
- null} - /> - - {t("any_id_description")} - -
- -
- setOtp(value as string)} - value={otp} - label={t("enter_otp")} - disabled={memory?.isLoading} - /> -
- -
- - {t("verify_and_link")} - - - {(memory?.resendOtpCount ?? 0) < MAX_OTP_RESEND_ALLOWED && ( - - {t("resend_otp")} - - )} -
-
- ); -} diff --git a/src/components/ABDM/LinkAbhaNumber/LinkWithQr.tsx b/src/components/ABDM/LinkAbhaNumber/LinkWithQr.tsx deleted file mode 100644 index 712d28943b4..00000000000 --- a/src/components/ABDM/LinkAbhaNumber/LinkWithQr.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import { IDetectedBarcode, Scanner } from "@yudiel/react-qr-scanner"; -import { useState } from "react"; -import { useTranslation } from "react-i18next"; - -import { ABHAQRContent, AbhaNumberModel } from "@/components/ABDM/types/abha"; - -import * as Notification from "@/Utils/Notifications"; -import routes from "@/Utils/request/api"; -import request from "@/Utils/request/request"; - -type ILoginWithQrProps = { - onSuccess: (abhaNumber: AbhaNumberModel) => void; -}; - -export default function LinkWithQr({ onSuccess }: ILoginWithQrProps) { - const { t } = useTranslation(); - const [isLoading, setIsLoading] = useState(false); - - return ( -
- { - if (detectedCodes.length === 0) return; - - const scannedValue = detectedCodes[0].rawValue; - if (!scannedValue || isLoading) return; - - try { - const qrData = JSON.parse(scannedValue) as ABHAQRContent; - - setIsLoading(true); - const { res, data } = await request(routes.abdm.abhaNumber.create, { - body: { - abha_number: qrData.hidn, - health_id: qrData.hid || qrData.phr, - name: qrData.name, - gender: qrData.gender, - date_of_birth: qrData.dob, - address: qrData.address, - district: qrData.district_name || qrData["dist name"], - state: qrData.state_name || qrData["state name"], - mobile: qrData.mobile, - }, - }); - - if (res?.status === 201 && data) { - onSuccess(data); - } - setIsLoading(false); - } catch (e) { - Notification.Error({ - msg: t("abha__qr_scanning_error"), - }); - } - }} - onError={(e: unknown) => { - const errorMessage = e instanceof Error ? e.message : "Unknown error"; - Notification.Error({ - msg: errorMessage, - }); - }} - scanDelay={3000} - constraints={{ - facingMode: "environment", - }} - /> -
- ); -} diff --git a/src/components/ABDM/LinkAbhaNumber/index.tsx b/src/components/ABDM/LinkAbhaNumber/index.tsx deleted file mode 100644 index 45cd0b34d02..00000000000 --- a/src/components/ABDM/LinkAbhaNumber/index.tsx +++ /dev/null @@ -1,142 +0,0 @@ -import { useState } from "react"; -import { useTranslation } from "react-i18next"; - -import CreateWithAadhaar from "@/components/ABDM/LinkAbhaNumber/CreateWithAadhaar"; -import LinkWithOtp from "@/components/ABDM/LinkAbhaNumber/LinkWithOtp"; -import LinkWithQr from "@/components/ABDM/LinkAbhaNumber/LinkWithQr"; -import { AbhaNumberModel } from "@/components/ABDM/types/abha"; -import ButtonV2 from "@/components/Common/ButtonV2"; -import DialogModal from "@/components/Common/Dialog"; - -import { classNames } from "@/Utils/utils"; - -interface ILinkAbhaNumberProps { - show: boolean; - onClose: () => void; - onSuccess: (abhaNumber: AbhaNumberModel) => void; -} - -const ABHA_LINK_OPTIONS = { - create_with_aadhaar: { - title: "abha_link_options__create_with_aadhaar__title", - description: "abha_link_options__create_with_aadhaar__description", - disabled: false, - value: "create_with_aadhaar", - create: true, - }, - link_with_otp: { - title: "abha_link_options__link_with_otp__title", - description: "abha_link_options__link_with_otp__description", - disabled: false, - value: "link_with_otp", - create: false, - }, - create_with_driving_license: { - title: "abha_link_options__create_with_driving_license__title", - description: "abha_link_options__create_with_driving_license__description", - disabled: true, - value: "create_with_driving_license", - create: true, - }, - link_with_demographics: { - title: "abha_link_options__link_with_demographics__title", - description: "abha_link_options__link_with_demographics__description", - disabled: true, - value: "link_with_demographics", - create: false, - }, - link_with_qr: { - title: "abha_link_options__link_with_qr__title", - description: "abha_link_options__link_with_qr__description", - disabled: false, - value: "link_with_qr", - create: false, - }, -}; - -export default function LinkAbhaNumber({ - show, - onClose, - onSuccess, -}: ILinkAbhaNumberProps) { - const { t } = useTranslation(); - const [currentAbhaLinkOption, setCurrentAbhaLinkOption] = useState< - keyof typeof ABHA_LINK_OPTIONS - >("create_with_aadhaar"); - - return ( - - {currentAbhaLinkOption === "create_with_aadhaar" && ( - - )} - - {currentAbhaLinkOption === "link_with_otp" && ( - - )} - - {currentAbhaLinkOption === "link_with_qr" && ( - - )} - -
-

- setCurrentAbhaLinkOption( - ABHA_LINK_OPTIONS[currentAbhaLinkOption].create - ? "link_with_otp" - : "create_with_aadhaar", - ) - } - className="cursor-pointer text-center text-sm text-blue-800" - > - {ABHA_LINK_OPTIONS[currentAbhaLinkOption].create - ? t("link_existing_abha_profile") - : t("create_new_abha_profile")} -

-
- -
-

- {t("try_different_abha_linking_option")} -

-
- {Object.values(ABHA_LINK_OPTIONS) - .filter( - (option) => - option.value !== currentAbhaLinkOption && - ABHA_LINK_OPTIONS[currentAbhaLinkOption]?.create === - option.create, - ) - .sort((a) => (a.disabled ? 1 : -1)) - .map((option) => ( - - setCurrentAbhaLinkOption( - option.value as keyof typeof ABHA_LINK_OPTIONS, - ) - } - ghost - tooltip={ - option.disabled - ? t("abha_link_options__disabled_tooltip") - : t(option.description) - } - disabled={option.disabled} - tooltipClassName="top-full mt-1" - className={classNames( - "w-full border border-gray-400 text-secondary-800", - !option.disabled && "hover:border-primary-100", - )} - > - {t(option.title)} - - ))} -
-
-
- ); -} diff --git a/src/components/ABDM/LinkAbhaNumber/useMultiStepForm.ts b/src/components/ABDM/LinkAbhaNumber/useMultiStepForm.ts deleted file mode 100644 index 79bc9ec71c6..00000000000 --- a/src/components/ABDM/LinkAbhaNumber/useMultiStepForm.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { - Dispatch, - ReactElement, - SetStateAction, - cloneElement, - useCallback, - useMemo, - useState, -} from "react"; - -export interface InjectedStepProps { - currentStepIndex: number; - isFirstStep: boolean; - isLastStep: boolean; - next: () => void; - prev: () => void; - goTo: (step: number) => void; - memory: T | null; - setMemory: Dispatch>; -} - -export default function useMultiStepForm( - steps: ReactElement[], - initialValues?: T, -) { - const [currentStepIndex, setCurrentStepIndex] = useState(0); - const [memory, setMemory] = useState(initialValues as T); - - const next = useCallback( - () => - setCurrentStepIndex((prev) => - steps.length - 1 > prev ? prev + 1 : prev, - ), - [steps.length], - ); - - const prev = useCallback( - () => setCurrentStepIndex((prev) => (prev > 0 ? prev - 1 : prev)), - [], - ); - - const goTo = useCallback( - (step: number) => - setCurrentStepIndex((prev) => - step >= 0 && step <= steps.length - 1 ? step : prev, - ), - [steps.length], - ); - - const options = useMemo( - () => ({ - currentStepIndex, - isFirstStep: currentStepIndex === 0, - isLastStep: currentStepIndex === steps.length - 1, - next, - prev, - goTo, - memory, - setMemory, - }), - [currentStepIndex, memory, next, prev, goTo, steps.length], - ); - - const currentStep = cloneElement(steps[currentStepIndex], { - ...options, - ...steps[currentStepIndex].props, - }); - - return { currentStep, ...options }; -} diff --git a/src/components/ABDM/types/abha.ts b/src/components/ABDM/types/abha.ts deleted file mode 100644 index f189bd111cb..00000000000 --- a/src/components/ABDM/types/abha.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { PatientModel } from "@/components/Patient/models"; - -export type AbhaNumberModel = { - id: number; - external_id: string; - created_date: string; - modified_date: string; - abha_number: string; - health_id: string; - name: string; - first_name: string | null; - middle_name: string | null; - last_name: string | null; - gender: "F" | "M" | "O"; - date_of_birth: string | null; - address: string | null; - district: string | null; - state: string | null; - pincode: string | null; - mobile: string | null; - email: string | null; - profile_photo: string | null; - new: boolean; - patient: string | null; - patient_object: PatientModel | null; -}; - -export type ABHAQRContent = { - hidn: string; - name: string; - gender: "M" | "F" | "O"; - dob: string; - mobile: string; - address: string; - distlgd: string; - statelgd: string; -} & ({ hid: string; phr?: never } | { phr: string; hid?: never }) & - ( - | { district_name: string; "dist name"?: never } - | { "dist name": string; district_name?: never } - ) & - ( - | { state_name: string; "state name"?: never } - | { "state name": string; state_name?: never } - ); diff --git a/src/components/ABDM/types/consent.ts b/src/components/ABDM/types/consent.ts deleted file mode 100644 index a5041f5767d..00000000000 --- a/src/components/ABDM/types/consent.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { AbhaNumberModel } from "@/components/ABDM/types/abha"; -import { UserBaseModel } from "@/components/Users/models"; - -export type ConsentPurpose = - | "CAREMGT" - | "BTG" - | "PUBHLTH" - | "HPAYMT" - | "DSRCH" - | "PATRQT"; - -export type ConsentStatus = - | "REQUESTED" - | "GRANTED" - | "DENIED" - | "EXPIRED" - | "REVOKED"; - -export type ConsentHIType = - | "Prescription" - | "DiagnosticReport" - | "OPConsultation" - | "DischargeSummary" - | "ImmunizationRecord" - | "HealthDocumentRecord" - | "WellnessRecord"; - -export type ConsentAccessMode = "VIEW" | "STORE" | "QUERY" | "STREAM"; - -export type ConsentFrequencyUnit = "HOUR" | "DAY" | "WEEK" | "MONTH" | "YEAR"; - -export type ConsentCareContext = { - patientReference: string; - careContextReference: string; -}; - -export type ConsentModel = { - id: string; - consent_id: null | string; - - patient_abha: string; - care_contexts: ConsentCareContext[]; - - status: ConsentStatus; - purpose: ConsentPurpose; - hi_types: ConsentHIType[]; - - access_mode: ConsentAccessMode; - from_time: string; - to_time: string; - expiry: string; - - frequency_unit: ConsentFrequencyUnit; - frequency_value: number; - frequency_repeats: number; - - hip: null | string; - hiu: null | string; - - created_date: string; - modified_date: string; -}; - -export type CreateConsentTBody = { - patient_abha: string; - hi_types: ConsentHIType[]; - purpose: ConsentPurpose; - from_time: Date | string; - to_time: Date | string; - expiry: Date | string; - - access_mode?: ConsentAccessMode; - frequency_unit?: ConsentFrequencyUnit; - frequency_value?: number; - frequency_repeats?: number; - hip?: null | string; -}; - -export type ConsentArtefactModel = { - consent_request: string; - - cm: null | string; -} & ConsentModel; - -export type ConsentRequestModel = { - requester: UserBaseModel; - patient_abha_object: AbhaNumberModel; - consent_artefacts: ConsentArtefactModel[]; -} & ConsentModel; diff --git a/src/components/ABDM/types/health-facility.ts b/src/components/ABDM/types/health-facility.ts deleted file mode 100644 index 419003dbf8e..00000000000 --- a/src/components/ABDM/types/health-facility.ts +++ /dev/null @@ -1,19 +0,0 @@ -export interface IHealthFacility { - id: string; - registered: boolean; - external_id: string; - created_date: string; - modified_date: string; - hf_id: string; - facility: string; - detail?: string; -} - -export interface IcreateHealthFacilityTBody { - facility: string; - hf_id: string; -} - -export interface IpartialUpdateHealthFacilityTBody { - hf_id: string; -} diff --git a/src/components/ABDM/types/health-information.ts b/src/components/ABDM/types/health-information.ts deleted file mode 100644 index eeb1c53a4fb..00000000000 --- a/src/components/ABDM/types/health-information.ts +++ /dev/null @@ -1,6 +0,0 @@ -export type HealthInformationModel = { - data: { - content: string; - care_context_reference: string; - }[]; -}; diff --git a/src/components/Facility/ConsultationDetails/ConsultationContext.tsx b/src/components/Facility/ConsultationDetails/ConsultationContext.tsx new file mode 100644 index 00000000000..174b20536a5 --- /dev/null +++ b/src/components/Facility/ConsultationDetails/ConsultationContext.tsx @@ -0,0 +1,73 @@ +import { ReactNode, createContext, useContext, useState } from "react"; + +import { ConsultationModel } from "@/components/Facility/models"; +import { PatientModel } from "@/components/Patient/models"; + +import { PLUGIN_Component } from "@/PluginEngine"; + +interface ConsultationContextBase { + consultation?: ConsultationModel; + patient?: PatientModel; +} + +type ConsultationContextType = ConsultationContextBase & + T & { + setValue: ( + key: K, + value: (ConsultationContextBase & T)[K], + ) => void; + }; + +const ConsultationContext = createContext< + ConsultationContextType | undefined +>(undefined); + +export const useConsultation = () => { + const context = useContext(ConsultationContext); + + if (!context) { + throw new Error( + "'useConsultation' must be used within 'ConsultationProvider' only", + ); + } + + return context as ConsultationContextType; +}; + +interface ConsultationProviderProps { + children: ReactNode; + initialContext?: Partial; +} + +export const ConsultationProvider = ({ + children, + initialContext = {}, +}: ConsultationProviderProps) => { + const [state, setState] = useState( + initialContext as ConsultationContextBase & T, + ); + + const setValue = ( + key: K, + value: (ConsultationContextBase & T)[K], + ) => { + setState((prevState) => ({ + ...prevState, + [key]: value, + })); + }; + + return ( + + } + > + + {children} + + ); +}; diff --git a/src/components/Facility/ConsultationDetails/index.tsx b/src/components/Facility/ConsultationDetails/index.tsx index 496fd8f5d02..ec26dfe1a1d 100644 --- a/src/components/Facility/ConsultationDetails/index.tsx +++ b/src/components/Facility/ConsultationDetails/index.tsx @@ -1,9 +1,7 @@ -import { Link, navigate, useQueryParams } from "raviger"; +import { Link, navigate } from "raviger"; import { useCallback, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; -import ABDMRecordsTab from "@/components/ABDM/ABDMRecordsTab"; -import { AbhaNumberModel } from "@/components/ABDM/types/abha"; import Loading from "@/components/Common/Loading"; import PageTitle from "@/components/Common/PageTitle"; import RelativeDateUserMention from "@/components/Common/RelativeDateUserMention"; @@ -29,6 +27,7 @@ import PatientInfoCard from "@/components/Patient/PatientInfoCard"; import { PatientModel } from "@/components/Patient/models"; import useAuthUser from "@/hooks/useAuthUser"; +import { useCareAppConsultationTabs } from "@/hooks/useCareApps"; import { GENDER_TYPES } from "@/common/constants"; @@ -44,6 +43,8 @@ import { relativeTime, } from "@/Utils/utils"; +import { ConsultationProvider } from "./ConsultationContext"; + export interface ConsultationTabProps { consultationId: string; facilityId: string; @@ -52,7 +53,7 @@ export interface ConsultationTabProps { patientData: PatientModel; } -const TABS = { +const defaultTabs = { UPDATES: ConsultationUpdatesTab, FEED: ConsultationFeedTab, SUMMARY: ConsultationSummaryTab, @@ -66,20 +67,27 @@ const TABS = { NUTRITION: ConsultationNutritionTab, PRESSURE_SORE: ConsultationPressureSoreTab, DIALYSIS: ConsultationDialysisTab, - ABDM: ABDMRecordsTab, -}; +} as Record>; export const ConsultationDetails = (props: any) => { const { facilityId, patientId, consultationId } = props; const { t } = useTranslation(); + const [tabs, setTabs] = + useState>>(defaultTabs); + const pluginTabs = useCareAppConsultationTabs(); + + useEffect(() => { + if (pluginTabs) { + setTabs((prev) => ({ ...prev, ...pluginTabs })); + } + }, [pluginTabs]); + let tab = undefined; - if (Object.keys(TABS).includes(props.tab.toUpperCase())) { - tab = props.tab.toUpperCase() as keyof typeof TABS; + if (Object.keys(tabs).includes(props.tab.toUpperCase())) { + tab = props.tab.toUpperCase(); } const [showDoctors, setShowDoctors] = useState(false); - const [qParams, _] = useQueryParams(); const [patientData, setPatientData] = useState(); - const [abhaNumberData, setAbhaNumberData] = useState(); const [activeShiftingData, setActiveShiftingData] = useState>([]); const getPatientGender = (patientData: any) => @@ -144,16 +152,6 @@ export const ConsultationDetails = (props: any) => { const fetchData = useCallback( async (id: string) => { - // Get abha number data - const { data: abhaNumberData } = await request( - routes.abdm.abhaNumber.get, - { - pathParams: { abhaNumberId: id ?? "" }, - silent: true, - }, - ); - setAbhaNumberData(abhaNumberData); - // Get shifting data const shiftRequestsQuery = await request(routes.listShiftRequests, { query: { patient: id }, @@ -194,7 +192,7 @@ export const ConsultationDetails = (props: any) => { return ; } - const SelectedTab = TABS[tab]; + const SelectedTab = tabs[tab]; const tabButtonClasses = (selected: boolean) => `capitalize min-w-max-content cursor-pointer font-bold whitespace-nowrap ${ @@ -204,217 +202,222 @@ export const ConsultationDetails = (props: any) => { }`; return ( -
+
- -
-
- { - consultationQuery.refetch(); - patientDataQuery.refetch(); +
+ +
+
+ { + consultationQuery.refetch(); + patientDataQuery.refetch(); + }} + consultationId={consultationId} + activeShiftingData={activeShiftingData} + /> -
- {consultationData.admitted_to && ( -
-
- Patient - {consultationData.discharge_date - ? " Discharged from" - : " Admitted to"} - - {consultationData.admitted_to} - -
- {(consultationData.discharge_date ?? - consultationData.encounter_date) && ( -
- {relativeTime( - consultationData.discharge_date - ? consultationData.discharge_date - : consultationData.encounter_date, - )} +
+ {consultationData.admitted_to && ( +
+
+ Patient + {consultationData.discharge_date + ? " Discharged from" + : " Admitted to"} + + {consultationData.admitted_to} + +
+ {(consultationData.discharge_date ?? + consultationData.encounter_date) && ( +
+ {relativeTime( + consultationData.discharge_date + ? consultationData.discharge_date + : consultationData.encounter_date, + )} +
+ )} +
+ {consultationData.encounter_date && + formatDateTime(consultationData.encounter_date)} + {consultationData.discharge_date && + ` - ${formatDateTime(consultationData.discharge_date)}`}
- )} -
- {consultationData.encounter_date && - formatDateTime(consultationData.encounter_date)} - {consultationData.discharge_date && - ` - ${formatDateTime(consultationData.discharge_date)}`}
-
- )} -
-
-
-
- Created:   - -
+ )}
-
-
- Last Modified: -   - +
+
+
+ Created:   + +
+
+
+
+ Last Modified: +   + +
-
- {!!consultationData.diagnoses?.length && ( -
-
- + {!!consultationData.diagnoses?.length && ( +
+
+ +
-
- )} -
-
-
- +
+
- -
- - - {showPatientNotesPopup && ( - - )} -
+ + {showPatientNotesPopup && ( + + )} +
+ ); }; diff --git a/src/components/Facility/FacilityConfigure.tsx b/src/components/Facility/FacilityConfigure.tsx index 78d2edd3058..1cb7c7d69cf 100644 --- a/src/components/Facility/FacilityConfigure.tsx +++ b/src/components/Facility/FacilityConfigure.tsx @@ -2,13 +2,13 @@ import { t } from "i18next"; import { navigate } from "raviger"; import { useReducer, useState } from "react"; -import { ConfigureHealthFacility } from "@/components/ABDM/ConfigureHealthFacility"; import { Submit } from "@/components/Common/ButtonV2"; import Loading from "@/components/Common/Loading"; import Page from "@/components/Common/Page"; import TextFormField from "@/components/Form/FormFields/TextFormField"; import { FieldChangeEvent } from "@/components/Form/FormFields/Utils"; +import { PLUGIN_Component } from "@/PluginEngine"; import * as Notification from "@/Utils/Notifications"; import routes from "@/Utils/request/api"; import request from "@/Utils/request/request"; @@ -163,7 +163,10 @@ export const FacilityConfigure = (props: IProps) => {
- +
); diff --git a/src/components/Facility/FacilityHome.tsx b/src/components/Facility/FacilityHome.tsx index 843b958c67b..02c3f5386cb 100644 --- a/src/components/Facility/FacilityHome.tsx +++ b/src/components/Facility/FacilityHome.tsx @@ -38,6 +38,7 @@ import { USER_TYPES, } from "@/common/constants"; +import { PLUGIN_Component } from "@/PluginEngine"; import { NonReadOnlyUsers } from "@/Utils/AuthorizeFor"; import * as Notification from "@/Utils/Notifications"; import { CameraFeedPermittedUserTypes } from "@/Utils/permissions"; @@ -423,13 +424,10 @@ export const FacilityHome = ({ facilityId }: Props) => { > {t("view_users")} - navigate(`/facility/${facilityId}/abdm`)} - icon={} - > - {t("view_abdm_records")} - + {hasPermissionToDeleteFacility ? ( { return `${skills[0]}, ${skills[1]} and ${skills.length - 2} other skills...`; }; -export default function PatientInfoCard(props: { +export interface PatientInfoCardProps { patient: PatientModel; consultation?: ConsultationModel; - abhaNumber?: AbhaNumberModel; fetchPatientData?: (state: { aborted: boolean }) => void; activeShiftingData: any; consultationId: string; - showAbhaProfile?: boolean; -}) { +} + +export default function PatientInfoCard(props: PatientInfoCardProps) { const authUser = useAuthUser(); const { t } = useTranslation(); const [open, setOpen] = useState(false); - const [showLinkABHANumber, setShowLinkABHANumber] = useState(false); - const [showABHAProfile, setShowABHAProfile] = useState( - !!props.showAbhaProfile, - ); - const [showFetchABDMRecords, setShowFetchABDMRecords] = useState(false); const [openDischargeSummaryDialog, setOpenDischargeSummaryDialog] = useState(false); const [openDischargeDialog, setOpenDischargeDialog] = useState(false); @@ -149,11 +130,6 @@ export default function PatientInfoCard(props: { prefetch: !!consultation?.treating_physician_object?.username, }); - const { data: healthFacility } = useQuery(routes.abdm.healthFacility.get, { - pathParams: { facility_id: patient.facility ?? "" }, - silent: true, - }); - return ( <> -
- {careConfig.abdm.enabled && - (props.abhaNumber ? ( - <> - - {({ close }) => ( - <> -
{ - close(); - setShowABHAProfile(true); - triggerGoal("Patient Card Button Clicked", { - buttonName: t("show_abha_profile"), - consultationId: consultation?.id, - userId: authUser?.id, - }); - }} - > - - {t("show_abha_profile")} -
-
{ - close(); - setShowFetchABDMRecords(true); - triggerGoal("Patient Card Button Clicked", { - buttonName: t("hi__fetch_records"), - consultationId: consultation?.id, - userId: authUser?.id, - }); - }} - > - - {t("hi__fetch_records")} -
- - )} -
- - ) : ( - - - - - {({ close, disabled }) => ( -
{ - close(); - setShowLinkABHANumber(true); - }} - > - - -

{t("generate_link_abha")}

-
-
- )} -
-
- - {!healthFacility && ( - - {t("abha_disabled_due_to_no_health_facility")} - - )} -
-
- ))} -
{!consultation?.discharge_date && ( @@ -966,46 +858,8 @@ export default function PatientInfoCard(props: {
- setShowLinkABHANumber(false)} - onSuccess={async (abhaProfile) => { - const { res, data } = await request( - routes.abdm.healthId.linkAbhaNumberAndPatient, - { - body: { - patient: patient.id, - abha_number: abhaProfile.external_id, - }, - }, - ); - - if (res?.status === 200 && data) { - Notification.Success({ - msg: t("abha_number_linked_successfully"), - }); - props.fetchPatientData?.({ aborted: false }); - setShowLinkABHANumber(false); - setShowABHAProfile(true); - } else { - Notification.Error({ - msg: t("failed_to_link_abha_number"), - }); - } - }} - /> - setShowABHAProfile(false)} - /> - setShowFetchABDMRecords(false)} - /> + ); } diff --git a/src/components/Patient/PatientRegister.tsx b/src/components/Patient/PatientRegister.tsx index 4dc8b4965bc..5cc958d44de 100644 --- a/src/components/Patient/PatientRegister.tsx +++ b/src/components/Patient/PatientRegister.tsx @@ -7,16 +7,6 @@ import { useTranslation } from "react-i18next"; import CareIcon from "@/CAREUI/icons/CareIcon"; -import { Button } from "@/components/ui/button"; -import { - Tooltip, - TooltipContent, - TooltipProvider, - TooltipTrigger, -} from "@/components/ui/tooltip"; - -import LinkAbhaNumber from "@/components/ABDM/LinkAbhaNumber/index"; -import { AbhaNumberModel } from "@/components/ABDM/types/abha"; import AccordionV2 from "@/components/Common/AccordionV2"; import ButtonV2 from "@/components/Common/ButtonV2"; import CollapseV2 from "@/components/Common/CollapseV2"; @@ -40,7 +30,6 @@ import { RequiredFieldValidator, } from "@/components/Form/FieldValidators"; import Form from "@/components/Form/Form"; -import { FormContextValue } from "@/components/Form/FormContext"; import AutocompleteFormField from "@/components/Form/FormFields/Autocomplete"; import CheckBoxFormField from "@/components/Form/FormFields/CheckBoxFormField"; import DateFormField from "@/components/Form/FormFields/DateFormField"; @@ -80,8 +69,10 @@ import countryList from "@/common/static/countries.json"; import { statusType, useAbortableEffect } from "@/common/utils"; import { validatePincode } from "@/common/validation"; +import { PLUGIN_Component } from "@/PluginEngine"; import { RestoreDraftButton } from "@/Utils/AutoSave"; import * as Notification from "@/Utils/Notifications"; +import { usePubSub } from "@/Utils/pubsubContext"; import routes from "@/Utils/request/api"; import request from "@/Utils/request/request"; import useQuery from "@/Utils/request/useQuery"; @@ -94,7 +85,7 @@ import { scrollTo, } from "@/Utils/utils"; -type PatientForm = PatientModel & +export type PatientForm = PatientModel & PatientMeta & { age?: number; is_postpartum?: boolean }; interface PatientRegisterProps extends PatientModel { @@ -156,7 +147,6 @@ const initForm: any = { number_of_doses: "0", vaccine_name: null, last_vaccinated_date: null, - abha_number: null, ...medicalHistoryChoices, ration_card_category: null, }; @@ -226,7 +216,6 @@ export const PatientRegister = (props: PatientRegisterProps) => { patientList: Array; }>({ patientList: [] }); const [patientName, setPatientName] = useState(""); - const [showLinkAbhaNumberModal, setShowLinkAbhaNumberModal] = useState(false); const [showAutoFilledPincode, setShowAutoFilledPincode] = useState(false); const [insuranceDetails, setInsuranceDetails] = useState( [], @@ -236,6 +225,8 @@ export const PatientRegister = (props: PatientRegisterProps) => { const [insuranceDetailsError, setInsuranceDetailsError] = useState(); + const { publish } = usePubSub(); + const headerText = !id ? "Add Details of Patient" : "Update Patient Details"; const buttonText = !id ? "Add Patient" : "Save Details"; @@ -287,13 +278,6 @@ export const PatientRegister = (props: PatientRegisterProps) => { const { res, data } = await request(routes.getPatient, { pathParams: { id: id ? id : 0 }, }); - const { data: abhaNumberData } = await request( - routes.abdm.abhaNumber.get, - { - pathParams: { abhaNumberId: id ?? "" }, - silent: true, - }, - ); if (!status.aborted) { if (res?.ok && data) { @@ -306,8 +290,6 @@ export const PatientRegister = (props: PatientRegisterProps) => { age: data.year_of_birth ? new Date().getFullYear() - data.year_of_birth : "", - health_id_number: abhaNumberData?.abha_number || "", - health_id: abhaNumberData?.health_id || "", nationality: data.nationality ? data.nationality : "India", gender: data.gender ? data.gender : undefined, state: data.state ? data.state : "", @@ -392,11 +374,6 @@ export const PatientRegister = (props: PatientRegisterProps) => { [id], ); - const { data: healthFacility } = useQuery(routes.abdm.healthFacility.get, { - pathParams: { facility_id: facilityId }, - silent: true, - }); - useQuery(routes.hcx.policies.list, { query: { patient: id, @@ -708,27 +685,7 @@ export const PatientRegister = (props: PatientRegisterProps) => { controllerRef: submitController, }); if (res?.ok && requestData) { - if (state.form.abha_number) { - const { res, data } = await request( - routes.abdm.healthId.linkAbhaNumberAndPatient, - { - body: { - patient: requestData.id, - abha_number: state.form.abha_number, - }, - }, - ); - - if (res?.status === 200 && data) { - Notification.Success({ - msg: t("abha_number_linked_successfully"), - }); - } else { - Notification.Error({ - msg: t("failed_to_link_abha_number"), - }); - } - } + publish("patient:upsert", requestData); await Promise.all( insuranceDetails.map(async (obj) => { @@ -769,68 +726,6 @@ export const PatientRegister = (props: PatientRegisterProps) => { setIsLoading(false); }; - const populateAbhaValues = ( - abhaProfile: AbhaNumberModel, - field: FormContextValue, - ) => { - const values = { - abha_number: abhaProfile.external_id, - health_id_number: abhaProfile.abha_number, - health_id: abhaProfile.health_id, - }; - - if (abhaProfile.name) - field("name").onChange({ - name: "name", - value: abhaProfile.name, - }); - - if (abhaProfile.mobile) { - field("phone_number").onChange({ - name: "phone_number", - value: parsePhoneNumber(abhaProfile.mobile, "IN"), - }); - - field("emergency_phone_number").onChange({ - name: "emergency_phone_number", - value: parsePhoneNumber(abhaProfile.mobile, "IN"), - }); - } - - if (abhaProfile.gender) - field("gender").onChange({ - name: "gender", - value: { M: "1", F: "2", O: "3" }[abhaProfile.gender], - }); - - if (abhaProfile.date_of_birth) - field("date_of_birth").onChange({ - name: "date_of_birth", - value: new Date(abhaProfile.date_of_birth), - }); - - if (abhaProfile.pincode) - field("pincode").onChange({ - name: "pincode", - value: abhaProfile.pincode, - }); - - if (abhaProfile.address) { - field("address").onChange({ - name: "address", - value: abhaProfile.address, - }); - - field("permanent_address").onChange({ - name: "permanent_address", - value: abhaProfile.address, - }); - } - - dispatch({ type: "set_form", form: { ...state.form, ...values } }); - setShowLinkAbhaNumberModal(false); - }; - const handleMedicalCheckboxChange = (e: any, id: number, field: any) => { const values = field("medical_history").value ?? []; if (e.value) { @@ -1038,32 +933,6 @@ export const PatientRegister = (props: PatientRegisterProps) => { result in duplication of patient records.

- {!state.form.abha_number && ( -
- - - - - - {!healthFacility && ( - - {t("abha_disabled_due_to_no_health_facility")} - - )} - - -
- )} {showAlertMessage.show && ( { show /> )} - {careConfig.abdm.enabled && ( -
- {showLinkAbhaNumberModal && ( - setShowLinkAbhaNumberModal(false)} - onSuccess={(data) => { - if (id) { - Notification.Warn({ - msg: "To link Abha Number, please save the patient details", - }); - } - - populateAbhaValues(data, field); - }} - /> - )} - {state.form.abha_number && ( -
-
- null} - disabled={true} - error="" - /> -
-
- {state.form.health_id ? ( - null} - disabled={true} - error="" - /> - ) : ( -
- No Abha Address Associated with this ABHA Number -
- )} -
-
- )} -
- )} +

Personal Details diff --git a/src/components/Users/UserAdd.tsx b/src/components/Users/UserAdd.tsx index b9f5d5fda7c..4c6c5edde2a 100644 --- a/src/components/Users/UserAdd.tsx +++ b/src/components/Users/UserAdd.tsx @@ -150,7 +150,7 @@ const getDate = (value: any) => export const validateRule = ( condition: boolean, content: JSX.Element | string, - isInitialState: boolean, + isInitialState: boolean = false, ) => { return (
diff --git a/src/hooks/useCareApps.ts b/src/hooks/useCareApps.ts index 5d0de6f3b9f..6870736861a 100644 --- a/src/hooks/useCareApps.ts +++ b/src/hooks/useCareApps.ts @@ -24,6 +24,14 @@ export const useCareAppNavItems = () => { return navItems; }; +export const useCareAppConsultationTabs = () => { + const careApps = useCareApps(); + + return careApps.reduce((acc, app) => { + return { ...acc, ...(app.consultationTabs ?? {}) }; + }, {}); +}; + // If required; Reduce plugin.routes to a single pluginRoutes object of type Record JSX.Element> export function usePluginRoutes() { const careApps = useCareApps(); diff --git a/src/pluginTypes.ts b/src/pluginTypes.ts index 13886b5b7d5..6a10784926e 100644 --- a/src/pluginTypes.ts +++ b/src/pluginTypes.ts @@ -1,12 +1,16 @@ import { LazyExoticComponent } from "react"; import { INavItem } from "@/components/Common/Sidebar/Sidebar"; -import { ConsultationModel } from "@/components/Facility/models"; -import { PatientModel } from "@/components/Patient/models"; +import { ConsultationModel, FacilityModel } from "@/components/Facility/models"; import { UserAssignedModel } from "@/components/Users/models"; -import { AppRoutes } from "@/Routers/AppRouter"; -import { pluginMap } from "@/pluginMap"; +import { AppRoutes } from "./Routers/AppRouter"; +import { ConsultationTabProps } from "./components/Facility/ConsultationDetails"; +import { FormContextValue } from "./components/Form/FormContext"; +import { PatientInfoCardProps } from "./components/Patient/PatientInfoCard"; +import { PatientForm } from "./components/Patient/PatientRegister"; +import { PatientModel } from "./components/Patient/models"; +import { pluginMap } from "./pluginMap"; // Define the available plugins export type AvailablePlugin = "@apps/care_livekit_fe" | "@apps/care_hcx_fe"; @@ -19,6 +23,8 @@ export type DoctorConnectButtonComponentType = React.FC<{ user: UserAssignedModel; }>; +export type ExtendPatientInfoCardComponentType = React.FC; + export type ManagePatientOptionsComponentType = React.FC<{ consultation: ConsultationModel | undefined; patient: PatientModel; @@ -28,11 +34,39 @@ export type AdditionalDischargeProceduresComponentType = React.FC<{ consultation: ConsultationModel; }>; +export type ManageFacilityOptionsComponentType = React.FC<{ + facility?: FacilityModel; +}>; + +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; + ExtendPatientInfoCard: ExtendPatientInfoCardComponentType; ManagePatientOptions: ManagePatientOptionsComponentType; AdditionalDischargeProcedures: AdditionalDischargeProceduresComponentType; + ManageFacilityOptions: ManageFacilityOptionsComponentType; + ConsultationContextEnabler: React.FC; + ExtendFacilityConfigure: ExtendFacilityConfigureComponentType; + ExtendPatientRegisterForm: ExtendPatientRegisterFormComponentType; }; // Create a type for lazy-loaded components @@ -56,6 +90,10 @@ export type PluginManifest = { extends: SupportedPluginExtensions[]; components: PluginComponentMap; navItems: INavItem[]; + consultationTabs?: Record< + string, + LazyComponent> + >; }; // Create a type that ensures only available plugins can be used