@eka-care/abha-stg 0.1.34 → 0.1.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/daemon/7f0c6427972069cb-turbo.log.2025-11-12 +0 -0
- package/README.md +369 -101
- package/{dist/index.html → index.html} +1 -2
- package/package/styles/pds2/border.ts +69 -0
- package/package/styles/pds2/colors.ts +70 -0
- package/package/styles/pds2/spacing.ts +1007 -0
- package/package/tailwind/tailwind.config.ts +124 -0
- package/package.json +9 -8
- package/postcss.config.js +6 -0
- package/scripts/build-purged-css.cjs +70 -0
- package/src/App.css +0 -0
- package/src/App.tsx +43 -0
- package/src/api-queries/aorta-go/v3/get-profile-patient.ts +32 -0
- package/src/api-queries/aorta-go/v3/get-profiles-phr-user.ts +26 -0
- package/src/api-queries/aorta-go/v3/post-auth-init-v2.ts +34 -0
- package/src/api-queries/aorta-go/v3/post-auth-logout-v2.ts +32 -0
- package/src/api-queries/aorta-go/v3/post-auth-verify-v2.ts +38 -0
- package/src/api-queries/aorta-go/v3/post-profile-switch.ts +39 -0
- package/src/api-queries/ndhm/get-abdm-register-suggest.ts +37 -0
- package/src/api-queries/ndhm/get-pincode-details.ts +28 -0
- package/src/api-queries/ndhm/post-abdm-login-init.ts +37 -0
- package/src/api-queries/ndhm/post-abdm-login-phr.ts +37 -0
- package/src/api-queries/ndhm/post-abdm-login-verify.ts +37 -0
- package/src/api-queries/ndhm/post-abdm-profile-eka-link-phr.ts +40 -0
- package/src/api-queries/ndhm/post-abdm-profile-eka.ts +66 -0
- package/src/api-queries/ndhm/post-abdm-register-abha-number-create-phr.ts +37 -0
- package/src/api-queries/ndhm/post-abdm-register-mobile-create-phr.ts +66 -0
- package/src/api-queries/ndhm/post-abdm-register-mobile-resend-otp.ts +32 -0
- package/src/api-queries/ndhm/post-abdm-register-mobile-verify.ts +38 -0
- package/src/api-queries/ndhm/post-abdm-register-phr-check.ts +34 -0
- package/src/api-queries/ndhm/post-register-aadhaar-create-phr.ts +37 -0
- package/src/api-queries/ndhm/post-register-aadhaar-init.ts +34 -0
- package/src/api-queries/ndhm/post-register-aadhaar-mobile-resend-otp.ts +34 -0
- package/src/api-queries/ndhm/post-register-aadhaar-mobile-verify.ts +37 -0
- package/src/api-queries/ndhm/post-register-aadhaar-resend-otp.ts +34 -0
- package/src/api-queries/ndhm/post-register-aadhaar-verify.ts +40 -0
- package/src/api-queries/ndhm/post-register-mobile-init.ts +34 -0
- package/src/api-queries/use-get-profiles-patient.ts +12 -0
- package/src/api-queries/use-get-profiles-phr-user.ts +28 -0
- package/src/api-queries/use-post-abdm-login-verify-v1.ts +26 -0
- package/src/api-queries/use-post-auth-verify-v2.ts +50 -0
- package/src/api-queries/use-post-profile-switch.ts +58 -0
- package/src/api-queries/use-post-register-mobile-create-phr.ts +39 -0
- package/src/api-queries/user-post-abdm-profile-login-phr.ts +26 -0
- package/src/assets/Success.json +1 -0
- package/src/assets/react.svg +1 -0
- package/src/atoms/button/custom-button.tsx +32 -0
- package/src/atoms/button/index.tsx +40 -0
- package/src/atoms/button/types.d.ts +31 -0
- package/src/atoms/header.tsx +25 -0
- package/src/atoms/input-field/index.tsx +63 -0
- package/src/atoms/input-field/patient-input-field.tsx +16 -0
- package/src/atoms/input-field/types.ts +24 -0
- package/src/atoms/pds2-otp-input/index.tsx +35 -0
- package/src/atoms/pds2-otp-input/types.d.ts +3 -0
- package/src/atoms/single-input-chip/index.tsx +32 -0
- package/src/atoms/single-input-chip/types.ts +6 -0
- package/src/atoms/spinner.tsx +33 -0
- package/src/atoms/text-separator.tsx +11 -0
- package/src/atoms/ui/spinner.tsx +75 -0
- package/src/constants/constants.ts +376 -0
- package/src/fetch-client/index.ts +164 -0
- package/src/index.css +152 -0
- package/src/main.tsx +374 -0
- package/src/molecules/abha/bottom-sheet/bottom-sheet-wrapper.tsx +40 -0
- package/src/molecules/abha/bottom-sheet/index.tsx +66 -0
- package/src/molecules/abha/spaced-input-component.tsx +168 -0
- package/src/molecules/copyright-year.tsx +16 -0
- package/src/molecules/exit-popup/index.tsx +101 -0
- package/src/molecules/pds2-otp-component/index.tsx +147 -0
- package/src/organisms/abha/abha-header.tsx +25 -0
- package/src/organisms/abha/abha-stepper.tsx +83 -0
- package/src/organisms/abha/error-bottom-sheet.tsx +27 -0
- package/src/organisms/abha/otp-card.tsx +99 -0
- package/src/organisms/abha/verification-status.tsx +30 -0
- package/src/organisms/choose-language/choose-language.tsx +53 -0
- package/src/organisms/choose-language/types.ts +10 -0
- package/src/organisms/screen-switcher/screen-switcher.tsx +80 -0
- package/src/routes/abha-aadhaar-verification-status-screen.tsx +246 -0
- package/src/routes/abha-created-screen.tsx +45 -0
- package/src/routes/abha-login-otp-verify-screen.tsx +519 -0
- package/src/routes/abha-mobile-linking-status-screen.tsx +267 -0
- package/src/routes/abha-otp-and-mobile-screen.tsx +435 -0
- package/src/routes/abha-phone-number-verification-screen.tsx +388 -0
- package/src/routes/create-abha-address-screen.tsx +928 -0
- package/src/routes/create-abha-with-aadhaar-screen.tsx +986 -0
- package/src/routes/create-eka-profile-screen.tsx +831 -0
- package/src/routes/get-all-profiles-screen.tsx +161 -0
- package/src/routes/login-or-create-abha-address-screen.tsx +1056 -0
- package/src/routes/login-with-abha-screen.tsx +454 -0
- package/src/routes/select-abha-from-list-screen.tsx +792 -0
- package/src/routes/select-eka-profile-screen.tsx +446 -0
- package/src/routes/utils/trackAbhaEvent.ts +41 -0
- package/src/stores/auth-abha-store/index.ts +152 -0
- package/src/stores/auth-abha-store/types.ts +217 -0
- package/src/utils/mock-auth-response.ts +29 -0
- package/src/utils/send-event-utils.ts +76 -0
- package/src/utils/validations.ts +89 -0
- package/src/vite-env.d.ts +1 -0
- package/tailwind.config.ts +9 -0
- package/tsconfig.json +25 -0
- package/tsconfig.node.json +10 -0
- package/tsconfig.node.tsbuildinfo +1 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/vite.config.d.ts +2 -0
- package/vite.config.js +30 -0
- package/vite.config.ts +35 -0
- package/dist/sdk/abha/css/abha.css +0 -1
- package/dist/sdk/abha/js/abha.js +0 -146
- /package/{dist → public}/images/adhaar.webp +0 -0
- /package/{dist → public}/images/at-the-rate.webp +0 -0
- /package/{dist → public}/images/avatar.webp +0 -0
- /package/{dist → public}/images/ayushman-bharat.webp +0 -0
- /package/{dist → public}/images/circle-checkmark.webp +0 -0
- /package/{dist → public}/images/link-abha.webp +0 -0
- /package/{dist → public}/images/national-authority.webp +0 -0
- /package/{dist → public}/images/three-dots.webp +0 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { useRef, useState, useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
type SpacedInputComponentProps = {
|
|
4
|
+
numberOfInputs: number;
|
|
5
|
+
length?: number;
|
|
6
|
+
placeholder?: string;
|
|
7
|
+
setIsValid?: (val: boolean) => void;
|
|
8
|
+
onSubmit: ({ inputVal }: { inputVal: string }) => void;
|
|
9
|
+
error?: string | null;
|
|
10
|
+
containerClassName?: string;
|
|
11
|
+
setError?: (val: string | null) => void;
|
|
12
|
+
initialValues?: string[];
|
|
13
|
+
disabled?: boolean;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const SpacedInputComponent = ({
|
|
17
|
+
numberOfInputs,
|
|
18
|
+
length = 1,
|
|
19
|
+
placeholder = '',
|
|
20
|
+
setIsValid,
|
|
21
|
+
onSubmit,
|
|
22
|
+
error,
|
|
23
|
+
containerClassName,
|
|
24
|
+
setError,
|
|
25
|
+
initialValues,
|
|
26
|
+
disabled = false,
|
|
27
|
+
}: SpacedInputComponentProps) => {
|
|
28
|
+
const inputRefs = useRef<HTMLInputElement[]>([]);
|
|
29
|
+
const [inputValues, setInputValues] = useState<string[]>(Array(numberOfInputs).fill(''));
|
|
30
|
+
|
|
31
|
+
// Prefill input values on mount
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
if (initialValues && initialValues.length === numberOfInputs) {
|
|
34
|
+
setInputValues(initialValues);
|
|
35
|
+
if (initialValues.join('').length === numberOfInputs * length) {
|
|
36
|
+
setIsValid?.(true);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}, [initialValues, numberOfInputs, length, setIsValid]);
|
|
40
|
+
|
|
41
|
+
const handleOnChange = (e: React.ChangeEvent<HTMLInputElement>, index: number) => {
|
|
42
|
+
if (disabled) return; // Don't allow editing if disabled
|
|
43
|
+
if (setError) setError(null);
|
|
44
|
+
const value = e.target.value;
|
|
45
|
+
|
|
46
|
+
const isValueNan = isNaN(Number(value));
|
|
47
|
+
if (isValueNan) return;
|
|
48
|
+
|
|
49
|
+
// To autoread OTP from keyboard clipboard
|
|
50
|
+
if (value.trim().length === numberOfInputs * length) {
|
|
51
|
+
handleClipboardOtp({
|
|
52
|
+
otpValue: value,
|
|
53
|
+
index: 0,
|
|
54
|
+
});
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const currentValues = [...inputValues];
|
|
58
|
+
currentValues[index] = value;
|
|
59
|
+
const totalLength = currentValues.join('').length;
|
|
60
|
+
if (totalLength > numberOfInputs * length) return;
|
|
61
|
+
|
|
62
|
+
// Update current input value
|
|
63
|
+
setInputValues(currentValues);
|
|
64
|
+
|
|
65
|
+
if (totalLength === numberOfInputs * length) {
|
|
66
|
+
setIsValid?.(true);
|
|
67
|
+
onSubmit({ inputVal: currentValues.join('') });
|
|
68
|
+
} else {
|
|
69
|
+
setIsValid?.(false);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Move to next input if current is full
|
|
73
|
+
if (value.length === length && index < numberOfInputs - 1) {
|
|
74
|
+
inputRefs.current[index + 1]?.focus();
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>, index: number) => {
|
|
79
|
+
if (disabled) return;
|
|
80
|
+
if (e.key === 'Backspace' && !inputValues[index] && index > 0) {
|
|
81
|
+
inputRefs.current[index - 1]?.focus();
|
|
82
|
+
setInputValues((prev) => {
|
|
83
|
+
const newValues = [...prev];
|
|
84
|
+
newValues[index - 1] = '';
|
|
85
|
+
return newValues;
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const handlePaste = (e: React.ClipboardEvent<HTMLInputElement>, index: number) => {
|
|
91
|
+
if (disabled) return;
|
|
92
|
+
e.preventDefault();
|
|
93
|
+
const pastedData = e.clipboardData.getData('text').replace(/\D/g, ''); // Only keep digits
|
|
94
|
+
|
|
95
|
+
if (!pastedData) return;
|
|
96
|
+
|
|
97
|
+
const maxLength = numberOfInputs * length;
|
|
98
|
+
const validPastedData = pastedData.slice(0, maxLength);
|
|
99
|
+
handleClipboardOtp({
|
|
100
|
+
otpValue: validPastedData,
|
|
101
|
+
index,
|
|
102
|
+
});
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const handleClipboardOtp = ({ otpValue, index }: { otpValue: string; index: number }) => {
|
|
106
|
+
const chunks = otpValue.match(new RegExp(`.{1,${length}}`, 'g')) || [];
|
|
107
|
+
|
|
108
|
+
const newValues = [...inputValues];
|
|
109
|
+
|
|
110
|
+
chunks.forEach((chunk, i) => {
|
|
111
|
+
if (index + i < numberOfInputs) {
|
|
112
|
+
newValues[index + i] = chunk;
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
setInputValues(newValues);
|
|
117
|
+
|
|
118
|
+
// Focus the next empty input or the last input
|
|
119
|
+
const nextEmptyIndex = newValues.findIndex((val, i) => i >= index && !val);
|
|
120
|
+
if (nextEmptyIndex !== -1) {
|
|
121
|
+
inputRefs.current[nextEmptyIndex]?.focus();
|
|
122
|
+
} else {
|
|
123
|
+
inputRefs.current[numberOfInputs - 1]?.focus();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Hit endpoint if all inputs are filled
|
|
127
|
+
if (newValues.join('').length === numberOfInputs * length) {
|
|
128
|
+
setIsValid?.(true);
|
|
129
|
+
onSubmit({ inputVal: newValues.join('') });
|
|
130
|
+
} else {
|
|
131
|
+
setIsValid?.(false);
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
return (
|
|
136
|
+
<div
|
|
137
|
+
className={`pds2-flex pds2-justify-center pds2-items-center pds2-space-x-8 ${containerClassName}`}
|
|
138
|
+
>
|
|
139
|
+
{Array.from({ length: numberOfInputs }).map((_, index) => (
|
|
140
|
+
<label
|
|
141
|
+
key={index}
|
|
142
|
+
className={`focus-within:pds2-border-border-brand-01 pds2-border ${error ? 'pds2-border-border-error' : 'pds2-border-border-03'} pds2-rounded-12 pds2-bg-bg-white`}
|
|
143
|
+
>
|
|
144
|
+
<input
|
|
145
|
+
autoFocus={index === 0 && !disabled}
|
|
146
|
+
placeholder={placeholder}
|
|
147
|
+
value={inputValues[index]}
|
|
148
|
+
key={index}
|
|
149
|
+
type="text"
|
|
150
|
+
inputMode="numeric"
|
|
151
|
+
pattern="[0-9]*"
|
|
152
|
+
// maxLength={length}
|
|
153
|
+
onPaste={(e) => handlePaste(e, index)}
|
|
154
|
+
ref={(el) => {
|
|
155
|
+
if (el) inputRefs.current[index] = el;
|
|
156
|
+
}}
|
|
157
|
+
onChange={(e) => handleOnChange(e, index)}
|
|
158
|
+
onKeyDown={(e) => handleKeyDown(e, index)}
|
|
159
|
+
disabled={disabled}
|
|
160
|
+
className={`pds2-w-full pds2-text-center pds2-h-56 pds2-rounded-12 pds2-outline-none pds2-bg-transparent disabled:pds2-text-text-04`}
|
|
161
|
+
/>
|
|
162
|
+
</label>
|
|
163
|
+
))}
|
|
164
|
+
</div>
|
|
165
|
+
);
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
export default SpacedInputComponent;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
const CopyrightYearComponent = () => {
|
|
4
|
+
const [year, setYear] = useState<number>(new Date().getFullYear());
|
|
5
|
+
|
|
6
|
+
useEffect(() => {
|
|
7
|
+
setYear(new Date().getFullYear());
|
|
8
|
+
}, []);
|
|
9
|
+
return (
|
|
10
|
+
<div className="pds2-absolute pds2-bottom-5 BodyRegular pds2-text-darwin-neutral-600">
|
|
11
|
+
{`Copyright © ${year} eka.care`}
|
|
12
|
+
</div>
|
|
13
|
+
);
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export default CopyrightYearComponent;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import Pds2CustomButton from '../../atoms/button/custom-button';
|
|
2
|
+
import { ABHA_AUTH_FLOW_METHOD } from '../../constants/constants';
|
|
3
|
+
import useAuthAbhaStore from '../../stores/auth-abha-store';
|
|
4
|
+
import handleSendEvent from '../../utils/send-event-utils';
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
const ExitPopup = ({ open, setOpen }: { open: boolean; setOpen: (val: boolean) => void }) => {
|
|
8
|
+
// State selectors from ABHA auth store
|
|
9
|
+
const goBackLoginScreen = useAuthAbhaStore((state) => state.goBackLoginScreen);
|
|
10
|
+
const clientId = useAuthAbhaStore((state) => state.clientId);
|
|
11
|
+
const isEkaAppLogin = useAuthAbhaStore((state) => state.isEkaAppLogin);
|
|
12
|
+
const isNewLoginOrCreateFlow = useAuthAbhaStore((state) => state.isNewLoginOrCreateFlow);
|
|
13
|
+
const setAbhaAuthFlowMethod = useAuthAbhaStore((state) => state.setAbhaAuthFlowMethod);
|
|
14
|
+
const txnId = useAuthAbhaStore((state) => state.txnId);
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Event tracker for ABHA-related events.
|
|
18
|
+
* Sends data to internal logger and Mixpanel with base context.
|
|
19
|
+
*/
|
|
20
|
+
const trackAbhaEvent = ({ name, data = {} }: { name: string; data?: Record<string, any> }) => {
|
|
21
|
+
const baseProps = {
|
|
22
|
+
login_platform: clientId,
|
|
23
|
+
is_eka_app_login: isEkaAppLogin ? 'true' : 'false',
|
|
24
|
+
is_new_login_or_create_flow: isNewLoginOrCreateFlow ? 'true' : 'false',
|
|
25
|
+
txn_id : txnId || 'missing txnId',
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const eventData = {
|
|
29
|
+
...baseProps,
|
|
30
|
+
...data,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// Mixpanel tracking
|
|
34
|
+
handleSendEvent({
|
|
35
|
+
eventName: name,
|
|
36
|
+
eventData,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
window.curio?.pushToMixpanel?.(name, eventData);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Handles the user's confirmation to exit the ABHA flow.
|
|
44
|
+
* Resets flow to mobile method and navigates back to login.
|
|
45
|
+
*/
|
|
46
|
+
const handleExitConfirmed = () => {
|
|
47
|
+
trackAbhaEvent({
|
|
48
|
+
name: 'abha_exit_popup_confirmed',
|
|
49
|
+
data: {
|
|
50
|
+
action: 'go_back_to_login_screen',
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// Reset flow method to mobile as default
|
|
55
|
+
setAbhaAuthFlowMethod(ABHA_AUTH_FLOW_METHOD.MOBILE);
|
|
56
|
+
// Navigate back to login screen
|
|
57
|
+
goBackLoginScreen();
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<>
|
|
62
|
+
{open ? (
|
|
63
|
+
<>
|
|
64
|
+
<div
|
|
65
|
+
className="pds2-h-full pds2-z-50 pds2-flex pds2-items-center pds2-justify-center pds2-absolute pds2-top-0 pds2-inset-0 pds2-bg-text-black pds2-bg-opacity-60 pds2-transition-opacity pds2-duration-300"
|
|
66
|
+
onClick={() => setOpen(false)}
|
|
67
|
+
>
|
|
68
|
+
<div className="pds2-z-50 pds2-absolute pds2-top pds2-px-24">
|
|
69
|
+
<div className="pds2-flex pds2-flex-col pds2-space-y-16 pds2-bg-bg-white pds2-rounded-16 pds2-w-full pds2-p-24">
|
|
70
|
+
<h2 className="pds2-text-text-black pds2-font-500 pds2-text-18">
|
|
71
|
+
Are you sure you want to exit?
|
|
72
|
+
</h2>
|
|
73
|
+
<p className="pds2-text-text-03 pds2-text-14 pds2-font-400">
|
|
74
|
+
Confirm if you'd like to exit. Any unsaved changes may be lost.
|
|
75
|
+
</p>
|
|
76
|
+
<div className="pds2-flex pds2-justify-end pds2-gap-x-8">
|
|
77
|
+
<Pds2CustomButton
|
|
78
|
+
title="Not yet"
|
|
79
|
+
padding="pds2-px-12 pds2-py-8"
|
|
80
|
+
className="pds2-text-text-error pds2-font-600 pds2-text-16 pds2-border-none"
|
|
81
|
+
width="pds2-w-fit"
|
|
82
|
+
onClick={() => setOpen(false)}
|
|
83
|
+
/>
|
|
84
|
+
<Pds2CustomButton
|
|
85
|
+
title="Yes, I'm done"
|
|
86
|
+
padding="pds2-px-12 pds2-py-8"
|
|
87
|
+
className="pds2-text-text-brand pds2-font-600 pds2-text-16 pds2-border-none"
|
|
88
|
+
width="pds2-w-fit"
|
|
89
|
+
onClick={handleExitConfirmed}
|
|
90
|
+
/>
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
</>
|
|
96
|
+
) : null}
|
|
97
|
+
</>
|
|
98
|
+
);
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
export default ExitPopup;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { CircleExclamationRegularIcon } from '@eka-care/icons';
|
|
2
|
+
import React, { useState } from 'react';
|
|
3
|
+
import ReactOTPInput, { AllowedInputTypes } from 'react-otp-input';
|
|
4
|
+
import Pds2OtpInput from '../../atoms/pds2-otp-input';
|
|
5
|
+
|
|
6
|
+
const OtpErrorComponent = ({ errorMessage }: { errorMessage: string }) => {
|
|
7
|
+
return (
|
|
8
|
+
<div className="pds2-flex pds2-space-x-4 pds2-items-center pds2-text-text-error">
|
|
9
|
+
<div className="pds2-p-8">
|
|
10
|
+
<CircleExclamationRegularIcon className="pds2-w-12 pds2-h-12" />
|
|
11
|
+
</div>
|
|
12
|
+
<div className="Body2Regular">{errorMessage}</div>
|
|
13
|
+
</div>
|
|
14
|
+
);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const ResendCodeComponent = ({ handleResendCode }: { handleResendCode: () => void }) => {
|
|
18
|
+
return (
|
|
19
|
+
<div className="pds2-flex pds2-justify-center pds2-py-16 pds2-text-text-doc">
|
|
20
|
+
<div onClick={handleResendCode} role="button">
|
|
21
|
+
Resend code
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const ResendTimerComponent = ({ secondsRemaining }: { secondsRemaining: string }) => {
|
|
28
|
+
return (
|
|
29
|
+
<div className="BodyRegular pds2-text-text-03 pds2-text-center pds2-py-16">{`Resend in 00:${secondsRemaining}`}</div>
|
|
30
|
+
);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
type TOtpComponentProps = {
|
|
34
|
+
/**
|
|
35
|
+
* Number of fields in the OTP input
|
|
36
|
+
*/
|
|
37
|
+
numberOfFields: number;
|
|
38
|
+
inputType?: AllowedInputTypes;
|
|
39
|
+
/**
|
|
40
|
+
* Error message to be displayed
|
|
41
|
+
*/
|
|
42
|
+
errorMessage?: string | null;
|
|
43
|
+
/**
|
|
44
|
+
* Function to set error message
|
|
45
|
+
*/
|
|
46
|
+
setErrorMessage: (message: string) => void;
|
|
47
|
+
/**
|
|
48
|
+
* Function to handle OTP submission
|
|
49
|
+
*/
|
|
50
|
+
onSubmit: ({ otp }: { otp: string }) => void;
|
|
51
|
+
/**
|
|
52
|
+
* Function to handle resend code
|
|
53
|
+
*/
|
|
54
|
+
onResendCode: () => void;
|
|
55
|
+
/**
|
|
56
|
+
* Function to reset identifier entered by the user
|
|
57
|
+
*/
|
|
58
|
+
onResetIdentifier: () => void;
|
|
59
|
+
/**
|
|
60
|
+
* Function to set the enable/disable state of button
|
|
61
|
+
*/
|
|
62
|
+
setIsValid?: (val: boolean) => void;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const Pds2OtpComponent = ({
|
|
66
|
+
numberOfFields,
|
|
67
|
+
inputType = 'tel',
|
|
68
|
+
errorMessage,
|
|
69
|
+
setErrorMessage,
|
|
70
|
+
onSubmit,
|
|
71
|
+
onResendCode,
|
|
72
|
+
onResetIdentifier,
|
|
73
|
+
setIsValid,
|
|
74
|
+
}: TOtpComponentProps) => {
|
|
75
|
+
const [otp, setOtp] = useState('');
|
|
76
|
+
const [timerSeconds, setTimerSeconds] = useState<number>(30);
|
|
77
|
+
const resendTimerIntervalId = React.useRef<NodeJS.Timeout>();
|
|
78
|
+
const [activeInput, setActiveInput] = useState<number>(-1);
|
|
79
|
+
|
|
80
|
+
const handleOtpChange = (newOtp: string) => {
|
|
81
|
+
setOtp(newOtp);
|
|
82
|
+
setErrorMessage('');
|
|
83
|
+
|
|
84
|
+
if (newOtp.length === numberOfFields) {
|
|
85
|
+
setIsValid?.(true);
|
|
86
|
+
onSubmit({ otp: newOtp });
|
|
87
|
+
// setTimerSeconds(30);
|
|
88
|
+
} else {
|
|
89
|
+
setIsValid?.(false);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
React.useEffect(() => {
|
|
94
|
+
resendTimerIntervalId.current = setInterval(() => {
|
|
95
|
+
setTimerSeconds((timer) => timer - 1);
|
|
96
|
+
}, 1000);
|
|
97
|
+
|
|
98
|
+
return () => {
|
|
99
|
+
clearInterval(resendTimerIntervalId.current);
|
|
100
|
+
};
|
|
101
|
+
}, []);
|
|
102
|
+
|
|
103
|
+
React.useEffect(() => {
|
|
104
|
+
if (timerSeconds < 1) {
|
|
105
|
+
clearInterval(resendTimerIntervalId.current);
|
|
106
|
+
setTimerSeconds(-1);
|
|
107
|
+
}
|
|
108
|
+
}, [timerSeconds]);
|
|
109
|
+
|
|
110
|
+
const handleResendCodeClick = () => {
|
|
111
|
+
setOtp('');
|
|
112
|
+
setTimerSeconds(30);
|
|
113
|
+
resendTimerIntervalId.current = setInterval(() => {
|
|
114
|
+
setTimerSeconds((timer) => timer - 1);
|
|
115
|
+
}, 1000);
|
|
116
|
+
onResendCode();
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
return (
|
|
120
|
+
<div className="pds2-space-y-8">
|
|
121
|
+
<ReactOTPInput
|
|
122
|
+
inputStyle="pds2-rounded-8 pds2-border-1 pds2-p-10 pds2-border-border-03 BodyRegular pds2-w-36 pds2-bg-bg-white pds2-text-text-04 placeholder:pds2-text-text-03 pds2-outline-none pds2-text-center"
|
|
123
|
+
inputType={inputType}
|
|
124
|
+
value={otp}
|
|
125
|
+
onChange={handleOtpChange}
|
|
126
|
+
numInputs={numberOfFields}
|
|
127
|
+
renderInput={(props) => Pds2OtpInput({ ...props, isError: errorMessage ? true : false })}
|
|
128
|
+
skipDefaultStyles={true}
|
|
129
|
+
renderSeparator={() => <div className="pds2-p-4" />}
|
|
130
|
+
shouldAutoFocus={true}
|
|
131
|
+
/>
|
|
132
|
+
{errorMessage || timerSeconds === -1 ? (
|
|
133
|
+
<div className="pds2-flex pds2-flex-col pds2-items-center pds2-space-x-8">
|
|
134
|
+
{errorMessage ? <OtpErrorComponent errorMessage={errorMessage} /> : null}
|
|
135
|
+
{timerSeconds === -1 ? (
|
|
136
|
+
<ResendCodeComponent handleResendCode={handleResendCodeClick} />
|
|
137
|
+
) : null}
|
|
138
|
+
</div>
|
|
139
|
+
) : null}
|
|
140
|
+
{timerSeconds > -1 ? (
|
|
141
|
+
<ResendTimerComponent secondsRemaining={timerSeconds.toString().padStart(2, '0')} />
|
|
142
|
+
) : null}
|
|
143
|
+
</div>
|
|
144
|
+
);
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
export default Pds2OtpComponent;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
const AbhaHeader = ({
|
|
2
|
+
prefixIcon = <div className="pds2-w-24 pds2-h-24" />,
|
|
3
|
+
title,
|
|
4
|
+
suffixIcon = <div className="pds2-w-24 pds2-h-24" />,
|
|
5
|
+
className = '',
|
|
6
|
+
}: {
|
|
7
|
+
prefixIcon?: React.ReactNode;
|
|
8
|
+
title: string;
|
|
9
|
+
suffixIcon?: React.ReactNode;
|
|
10
|
+
className?: string;
|
|
11
|
+
}) => {
|
|
12
|
+
return (
|
|
13
|
+
<div
|
|
14
|
+
className={`pds2-flex pds2-items-center pds2-justify-between pds2-w-full pds2-p-16 pds2-sticky pds2-top-0 pds2-z-50 pds2-bg-bg-white ${className}`}
|
|
15
|
+
>
|
|
16
|
+
{prefixIcon}
|
|
17
|
+
<div className="pds2-flex-1 Heading4Semibold pds2-text-center pds2-text-text-black">
|
|
18
|
+
{title}
|
|
19
|
+
</div>
|
|
20
|
+
{suffixIcon}
|
|
21
|
+
</div>
|
|
22
|
+
);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export default AbhaHeader;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { useEffect } from "react";
|
|
2
|
+
import useAuthAbhaStore from "../../stores/auth-abha-store";
|
|
3
|
+
|
|
4
|
+
const AbhaStepper = () => {
|
|
5
|
+
const activeScreens = useAuthAbhaStore((state) => state.screen);
|
|
6
|
+
const currentStepperIndex = useAuthAbhaStore((state) => state.currentStepperIndex);
|
|
7
|
+
const setCurrentStepperIndex = useAuthAbhaStore((state) => state.setCurrentStepperIndex);
|
|
8
|
+
const currentScreen = activeScreens[activeScreens.length - 1];
|
|
9
|
+
const selectedMethod = useAuthAbhaStore((state) => state.abhaAuthFlowMethod);
|
|
10
|
+
|
|
11
|
+
// Map screenIndex to arrays of screen names
|
|
12
|
+
// these will be used if login flow is through mobile or adhaar only
|
|
13
|
+
const screenMap: { [key: number]: string[] } = {
|
|
14
|
+
1: [
|
|
15
|
+
"LOGIN_WITH_ABHA",
|
|
16
|
+
"LOGIN_OR_CREATE_ABHA","CREATE_ABHA_WITH_AADHAAR"
|
|
17
|
+
],
|
|
18
|
+
2: [
|
|
19
|
+
"VERIFY_OTP_LOGIN_WITH_ABHA",
|
|
20
|
+
"ABHA_OTP_AND_MOBILE"
|
|
21
|
+
],
|
|
22
|
+
3: [
|
|
23
|
+
"ABHA_PHONE_NUMBER_VERIFICATION",
|
|
24
|
+
"SELECT_ABHA_FROM_LIST",
|
|
25
|
+
"SELECT_EKA_PROFILE",
|
|
26
|
+
"CREATE_ABHA_ADDRESS",
|
|
27
|
+
"CREATE_ABHA_WITH_AADHAAR",
|
|
28
|
+
"CREATE_PHR_EKA_PROFILE",
|
|
29
|
+
"CREATE_EKA_PROFILE",
|
|
30
|
+
],
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
const foundPage = Object.entries(screenMap).find(([_, screens]) =>
|
|
35
|
+
screens.includes(currentScreen)
|
|
36
|
+
)?.[0];
|
|
37
|
+
|
|
38
|
+
if (foundPage) {
|
|
39
|
+
// case if login flow is phr address or abha num
|
|
40
|
+
// name screen as last directly
|
|
41
|
+
if(currentScreen === "VERIFY_OTP_LOGIN_WITH_ABHA" && (selectedMethod==="abha_number" || selectedMethod==="phr_address")){
|
|
42
|
+
setCurrentStepperIndex(3);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if(Number(foundPage)>=currentStepperIndex){
|
|
46
|
+
setCurrentStepperIndex(Number(foundPage));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}, [activeScreens]);
|
|
50
|
+
|
|
51
|
+
const currentScreenIndex = Array.from({ length: 4 }, (_, i) => {
|
|
52
|
+
const index = i + 1;
|
|
53
|
+
return (
|
|
54
|
+
<div
|
|
55
|
+
key={index}
|
|
56
|
+
className={`pds2-relative pds2-p-0 pds2-text-center pds2-h-23 pds2-w-23 pds2-rounded-11 pds2-transition-all pds2-ease-in-out pds2-duration-250 pds2-cursor-pointer pds2-text-bg-white ${
|
|
57
|
+
index <= currentStepperIndex ? "pds2-bg-text-brand" : "pds2-bg-bg-secondary"
|
|
58
|
+
}`}
|
|
59
|
+
>
|
|
60
|
+
{index}
|
|
61
|
+
</div>
|
|
62
|
+
);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
const progressValue =
|
|
67
|
+
currentStepperIndex === 1 ? 20 : currentStepperIndex === 2 ? 50 : currentStepperIndex === 3 ? 80 : 100;
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<div className="pds2-bg-bg-01 pds2-flex pds2-w-full pds2-place-items-center pds2-justify-center pds2-relative pds2-px-60 pds2-py-20">
|
|
71
|
+
<div className="pds2-flex pds2-flex-row pds2-justify-between pds2-items-center pds2-gap-5 pds2-h-auto pds2-w-full pds2-z-10">
|
|
72
|
+
{currentScreenIndex}
|
|
73
|
+
</div>
|
|
74
|
+
<progress
|
|
75
|
+
className="abhaStepper pds2-absolute pds2-w-full pds2-h-2 pds2-px-60"
|
|
76
|
+
max={100}
|
|
77
|
+
value={progressValue}
|
|
78
|
+
/>
|
|
79
|
+
</div>
|
|
80
|
+
);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export default AbhaStepper;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import AbhaBottomSheet from '../../molecules/abha/bottom-sheet';
|
|
2
|
+
import useAuthAbhaStore from '../../stores/auth-abha-store';
|
|
3
|
+
type TAbhaErrorBottomSheetProps = {
|
|
4
|
+
onSubmitClick: () => void;
|
|
5
|
+
};
|
|
6
|
+
const AbhaErrorBottomSheet = ({ onSubmitClick }: TAbhaErrorBottomSheetProps) => {
|
|
7
|
+
const bottomsheetErrorInfo = useAuthAbhaStore((state) => state.bottomsheetErrorInfo);
|
|
8
|
+
const setBottomsheetErrorInfo = useAuthAbhaStore((state) => state.setBottomsheetErrorInfo);
|
|
9
|
+
const handleCloseBottomsheet = () => {
|
|
10
|
+
setBottomsheetErrorInfo(null);
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<AbhaBottomSheet
|
|
15
|
+
description={bottomsheetErrorInfo?.title || 'Something went wrong'}
|
|
16
|
+
subText={bottomsheetErrorInfo?.description || 'Something went wrong'}
|
|
17
|
+
submitText={bottomsheetErrorInfo?.cta.title || 'Retry'}
|
|
18
|
+
onSubmitClick={onSubmitClick}
|
|
19
|
+
iconUrl={bottomsheetErrorInfo?.img}
|
|
20
|
+
isBottomSheetOpen={bottomsheetErrorInfo ? true : false}
|
|
21
|
+
onBottomSheetClose={handleCloseBottomsheet}
|
|
22
|
+
shouldCloseOnClickOutside={false}
|
|
23
|
+
/>
|
|
24
|
+
);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export default AbhaErrorBottomSheet;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import SpacedInputComponent from '../../molecules/abha/spaced-input-component';
|
|
3
|
+
|
|
4
|
+
function formatTime(timeLeft: number): React.ReactNode {
|
|
5
|
+
const minutes = Math.floor(timeLeft / 60);
|
|
6
|
+
const seconds = timeLeft % 60;
|
|
7
|
+
return `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
type TResendTimerProps = {
|
|
11
|
+
time: number;
|
|
12
|
+
onResendOtpClick: () => void;
|
|
13
|
+
error?: string | null;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const ResendTimer = ({ time, onResendOtpClick, error }: TResendTimerProps) => {
|
|
17
|
+
const [timeLeft, setTimeLeft] = useState(time);
|
|
18
|
+
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
const interval = setInterval(() => {
|
|
21
|
+
setTimeLeft(timeLeft - 1);
|
|
22
|
+
}, 1000);
|
|
23
|
+
return () => clearInterval(interval);
|
|
24
|
+
}, [timeLeft]);
|
|
25
|
+
|
|
26
|
+
if (timeLeft > 0) {
|
|
27
|
+
return (
|
|
28
|
+
<div className="pds2-flex pds2-justify-between pds2-items-center pds2-w-full">
|
|
29
|
+
<div className="pds2-text-text-04">
|
|
30
|
+
<span className="Body3CapsSemibold">Resend OTP in</span>{' '}
|
|
31
|
+
<span className="Body3CapsSemibold">{formatTime(timeLeft)}</span>
|
|
32
|
+
</div>
|
|
33
|
+
{/* {error && <div className="Body3Semibold pds2-text-text-error">Invalid OTP</div>} */}
|
|
34
|
+
</div>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<div className="pds2-flex pds2-justify-between pds2-items-center pds2-w-full">
|
|
40
|
+
<button
|
|
41
|
+
className="Body3CapsSemibold pds2-text-text-brand cursor-pointer"
|
|
42
|
+
onClick={() => {
|
|
43
|
+
setTimeLeft(time);
|
|
44
|
+
onResendOtpClick();
|
|
45
|
+
}}
|
|
46
|
+
>
|
|
47
|
+
Resend OTP
|
|
48
|
+
</button>
|
|
49
|
+
{/* {error && <div className="Body3Semibold pds2-text-text-error">Invalid OTP</div>} */}
|
|
50
|
+
</div>
|
|
51
|
+
);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
type TAbhaOtpCardProps = {
|
|
55
|
+
otpSentToText: string;
|
|
56
|
+
onOtpCompletion: ({ otp }: { otp: string }) => void;
|
|
57
|
+
onResendOtpClick: () => void;
|
|
58
|
+
setIsValid: (isValid: boolean) => void;
|
|
59
|
+
error?: string | null;
|
|
60
|
+
prefixIcon?: React.ReactNode;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const AbhaOtpCard = ({
|
|
64
|
+
otpSentToText,
|
|
65
|
+
onOtpCompletion,
|
|
66
|
+
onResendOtpClick,
|
|
67
|
+
setIsValid,
|
|
68
|
+
error,
|
|
69
|
+
prefixIcon = (
|
|
70
|
+
<img
|
|
71
|
+
src="https://cdn.eka.care/vagus/cm5mcdm3h00070tfs9fjkepff.webp"
|
|
72
|
+
alt="adhaar"
|
|
73
|
+
className="pds2-w-48"
|
|
74
|
+
/>
|
|
75
|
+
),
|
|
76
|
+
}: TAbhaOtpCardProps) => {
|
|
77
|
+
return (
|
|
78
|
+
<div className="pds2-flex pds2-flex-col pds2-w-full pds2-p-16 pds2-rounded-16 pds2-bg-bg-white pds2-space-y-8">
|
|
79
|
+
<div className="pds2-flex pds2-items-center pds2-space-x-16">
|
|
80
|
+
<div className="Body1Regular pds2-text-text-01 pds2-flex-1">
|
|
81
|
+
{otpSentToText}
|
|
82
|
+
<sup className="pds2-text-text-error Body3Semibold"> *</sup>
|
|
83
|
+
</div>
|
|
84
|
+
{prefixIcon}
|
|
85
|
+
</div>
|
|
86
|
+
<SpacedInputComponent
|
|
87
|
+
numberOfInputs={6}
|
|
88
|
+
length={1}
|
|
89
|
+
onSubmit={({ inputVal }) => onOtpCompletion({ otp: inputVal })}
|
|
90
|
+
setIsValid={setIsValid}
|
|
91
|
+
error={error}
|
|
92
|
+
/>
|
|
93
|
+
<ResendTimer time={60} onResendOtpClick={onResendOtpClick} error={error} />
|
|
94
|
+
{error && <div className="Body3Semibold pds2-text-text-error">{error}</div>}
|
|
95
|
+
</div>
|
|
96
|
+
);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
export default AbhaOtpCard;
|