@kenyaemr/esm-express-workflow-app 5.4.2-pre.2425 → 5.4.2-pre.2430
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/turbo-build.log +15 -15
- package/dist/197.js +1 -1
- package/dist/294.js +1 -1
- package/dist/300.js +1 -1
- package/dist/663.js +1 -1
- package/dist/663.js.map +1 -1
- package/dist/716.js +1 -1
- package/dist/716.js.map +1 -1
- package/dist/86.js +1 -0
- package/dist/86.js.map +1 -0
- package/dist/932.js +1 -1
- package/dist/98.js +1 -1
- package/dist/kenyaemr-esm-express-workflow-app.js.buildmanifest.json +45 -21
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/routes.json +1 -1
- package/package.json +2 -1
- package/src/index.ts +1 -0
- package/src/routes.json +10 -5
- package/src/shared-components/otp-verification/index.ts +65 -0
- package/src/shared-components/otp-verification/otp-verification.modal.tsx +163 -0
- package/src/shared-components/otp-verification/otp-verification.scss +29 -0
- package/src/shared-components/pin-put/pinput.component.tsx +31 -0
- package/src/shared-components/pin-put/pinput.scss +22 -0
- package/translations/am.json +24 -0
- package/translations/en.json +24 -0
- package/translations/sw.json +24 -0
package/dist/routes.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"fhir2":">=1.2","webservices.rest":"^2.24.0"},"pages":[{"component":"root","route":"express-workflow"}],"extensions":[{"component":"accountingLeftPanelLink","name":"accounting-dashboard-link","slot":"express-workflow-left-panel-slot","order":10,"meta":{"name":"accounting","slot":"accounting-dashboard-slot","title":"Accounting"}},{"component":"accountingDashboard","name":"accounting-dashboard","slot":"accounting-dashboard-slot"},{"component":"admissionsLeftPanelLink","name":"admissions-dashboard-link","slot":"express-workflow-left-panel-slot","order":9,"meta":{"name":"admissions","slot":"admissions-dashboard-slot","title":"Admissions"}},{"component":"admissionsDashboard","name":"admissions-dashboard","slot":"admissions-dashboard-slot"},{"component":"consultationLeftPanelLink","name":"consultation-dashboard-link","slot":"express-workflow-left-panel-slot","order":3,"meta":{"name":"consultation","slot":"consultation-dashboard-slot","title":"Consultation"}},{"component":"consultationDashboard","name":"consultation-dashboard","slot":"consultation-dashboard-slot"},{"component":"facilityLeftPanelLink","name":"facility-dashboard-link","slot":"express-workflow-left-panel-slot","order":0,"meta":{"name":"dashboard","slot":"facility-dashboard-slot","title":"Dashboard"}},{"component":"facilityDashboard","name":"facility-dashboard","slot":"facility-dashboard-slot"},{"component":"laboratoryLeftPanelLink","name":"lab-dashboard-link","slot":"express-workflow-left-panel-slot","order":5,"meta":{"name":"
|
|
1
|
+
{"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"fhir2":">=1.2","webservices.rest":"^2.24.0"},"pages":[{"component":"root","route":"express-workflow"}],"extensions":[{"component":"accountingLeftPanelLink","name":"accounting-dashboard-link","slot":"express-workflow-left-panel-slot","order":10,"meta":{"name":"accounting","slot":"accounting-dashboard-slot","title":"Accounting"}},{"component":"accountingDashboard","name":"accounting-dashboard","slot":"accounting-dashboard-slot"},{"component":"admissionsLeftPanelLink","name":"admissions-dashboard-link","slot":"express-workflow-left-panel-slot","order":9,"meta":{"name":"admissions","slot":"admissions-dashboard-slot","title":"Admissions"}},{"component":"admissionsDashboard","name":"admissions-dashboard","slot":"admissions-dashboard-slot"},{"component":"consultationLeftPanelLink","name":"consultation-dashboard-link","slot":"express-workflow-left-panel-slot","order":3,"meta":{"name":"consultation","slot":"consultation-dashboard-slot","title":"Consultation"}},{"component":"consultationDashboard","name":"consultation-dashboard","slot":"consultation-dashboard-slot"},{"component":"facilityLeftPanelLink","name":"facility-dashboard-link","slot":"express-workflow-left-panel-slot","order":0,"meta":{"name":"dashboard","slot":"facility-dashboard-slot","title":"Dashboard"}},{"component":"facilityDashboard","name":"facility-dashboard","slot":"facility-dashboard-slot"},{"component":"laboratoryLeftPanelLink","name":"lab-dashboard-link","slot":"express-workflow-left-panel-slot","order":5,"meta":{"name":"laboratory","slot":"laboratory-dashboard-slot","title":"Lab"}},{"component":"laboratoryDashboard","name":"lab-dashboard","slot":"laboratory-dashboard-slot"},{"component":"mchLeftPanelLink","name":"mch-dashboard-link","slot":"express-workflow-left-panel-slot","order":4,"meta":{"name":"mch","slot":"mch-dashboard-slot","title":"MCH"}},{"component":"mchDashboard","name":"mch-dashboard","slot":"mch-dashboard-slot"},{"component":"pharmacyLeftPanelLink","name":"pharmacy-dashboard-link","slot":"express-workflow-left-panel-slot","order":7,"meta":{"name":"pharmacy","slot":"pharmacy-dashboard-slot","title":"Pharmacy"}},{"component":"pharmacyDashboard","name":"pharmacy-dashboard","slot":"pharmacy-dashboard-slot"},{"component":"proceduresLeftPanelLink","name":"procedures-dashboard-link","slot":"express-workflow-left-panel-slot","order":8,"meta":{"name":"procedures","slot":"procedures-dashboard-slot","title":"Procedures"}},{"component":"proceduresDashboard","name":"procedures-dashboard","slot":"procedures-dashboard-slot"},{"component":"radiologyLeftPanelLink","name":"radiology-dashboard-link","slot":"express-workflow-left-panel-slot","order":6,"meta":{"name":"radiology","slot":"radiology-dashboard-slot","title":"Radiology"}},{"component":"radiologyDashboard","name":"radiology-dashboard","slot":"radiology-dashboard-slot"},{"component":"registrationLeftPanelLink","name":"registration-dashboard-link","slot":"express-workflow-left-panel-slot","order":1,"meta":{"name":"registration","slot":"registration-dashboard-slot","title":"Registration"}},{"component":"registrationDashboard","name":"registration-dashboard","slot":"registration-dashboard-slot"},{"component":"reportsLeftPanelLink","name":"reports-dashboard-link","slot":"express-workflow-left-panel-slot","order":11,"meta":{"name":"reports","slot":"reports-dashboard-slot","title":"Reports"}},{"component":"reportsDashboard","name":"reports-dashboard","slot":"reports-dashboard-slot"},{"component":"triageLeftPanelLink","name":"triage-dashboard-link","slot":"express-workflow-left-panel-slot","order":2,"meta":{"name":"triage","slot":"triage-dashboard-slot","title":"Triage"}},{"component":"triageDashboard","name":"triage-dashboard","slot":"triage-dashboard-slot"},{"component":"visitFormBanner","name":"visit-form-banner","slot":"visit-form-header-slot","order":0}],"workspaces":[{"name":"express-workflow-workspace","component":"expressWorkflowWorkspace","title":"Express Workflow Workspace","type":"workspace","canMaximize":true,"canHide":true}],"modals":[{"component":"otpVerificationModal","name":"otp-verification-modal"}],"workspaceGroups":[{"name":"express-workflow","members":["add-drug-order","order-basket","add-lab-order","express-workflow-workspace"]}],"version":"5.4.2-pre.2430"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kenyaemr/esm-express-workflow-app",
|
|
3
|
-
"version": "5.4.2-pre.
|
|
3
|
+
"version": "5.4.2-pre.2430",
|
|
4
4
|
"description": "Express workflow app for OpenMRS 3",
|
|
5
5
|
"browser": "dist/kenyaemr-esm-express-workflow-app.js",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
"classnames": "^2.3.2",
|
|
44
44
|
"lodash-es": "^4.17.15",
|
|
45
45
|
"react-hook-form": "^7.54.0",
|
|
46
|
+
"react-otp-input": "^3.1.1",
|
|
46
47
|
"zod": "^3.24.1"
|
|
47
48
|
},
|
|
48
49
|
"peerDependencies": {
|
package/src/index.ts
CHANGED
|
@@ -29,6 +29,7 @@ export * from './components/reports';
|
|
|
29
29
|
export * from './shared/express-workflow-workspace';
|
|
30
30
|
|
|
31
31
|
export const root = getAsyncLifecycle(() => import('./root.component'), options);
|
|
32
|
+
export const otpVerificationModal = getAsyncLifecycle(() => import('./shared-components/otp-verification'), options);
|
|
32
33
|
|
|
33
34
|
// t('visitFormBanner', 'Visit Form Banner')
|
|
34
35
|
export const visitFormBanner = getAsyncLifecycle(
|
package/src/routes.json
CHANGED
|
@@ -81,15 +81,15 @@
|
|
|
81
81
|
"slot": "express-workflow-left-panel-slot",
|
|
82
82
|
"order": 5,
|
|
83
83
|
"meta": {
|
|
84
|
-
"name": "
|
|
85
|
-
"slot": "
|
|
84
|
+
"name": "laboratory",
|
|
85
|
+
"slot": "laboratory-dashboard-slot",
|
|
86
86
|
"title": "Lab"
|
|
87
87
|
}
|
|
88
88
|
},
|
|
89
89
|
{
|
|
90
|
-
"component": "
|
|
90
|
+
"component": "laboratoryDashboard",
|
|
91
91
|
"name": "lab-dashboard",
|
|
92
|
-
"slot": "
|
|
92
|
+
"slot": "laboratory-dashboard-slot"
|
|
93
93
|
},
|
|
94
94
|
{
|
|
95
95
|
"component": "mchLeftPanelLink",
|
|
@@ -220,7 +220,12 @@
|
|
|
220
220
|
"canHide": true
|
|
221
221
|
}
|
|
222
222
|
],
|
|
223
|
-
"modals": [
|
|
223
|
+
"modals": [
|
|
224
|
+
{
|
|
225
|
+
"component": "otpVerificationModal",
|
|
226
|
+
"name": "otp-verification-modal"
|
|
227
|
+
}
|
|
228
|
+
],
|
|
224
229
|
"workspaceGroups": [
|
|
225
230
|
{
|
|
226
231
|
"name": "express-workflow",
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { showModal } from '@openmrs/esm-framework';
|
|
2
|
+
import { default as otpverificationModal } from './otp-verification.modal';
|
|
3
|
+
/**
|
|
4
|
+
* Options for configuring the OTP Verification modal.
|
|
5
|
+
*
|
|
6
|
+
* @property {number} [otpLength] - The number of digits required for the OTP. Defaults to implementation-specific value.
|
|
7
|
+
* @property {(otp: string) => Promise<void>} [onVerify] - Callback invoked when the user submits the OTP. Should return a promise that resolves on successful verification.
|
|
8
|
+
* @property {boolean} [obsecureText] - If true, the OTP input will be obscured (e.g., password-style input).
|
|
9
|
+
* @property {boolean} [centerBoxes] - If true, OTP input boxes will be centered in the modal.
|
|
10
|
+
* @property {string} phoneNumber - The phone number to which the OTP will be sent.
|
|
11
|
+
* @property {(phoneNumber: string) => React.ReactNode} renderOtpTrigger - Function to render a custom component for triggering OTP requests.
|
|
12
|
+
* @property {(phoneNumber: string) => Promise<void>} [onRequestOtp] - Callback invoked to request a new OTP for the given phone number. Should return a promise.
|
|
13
|
+
* @property {() => void} [onVerificationSuccess] - Callback invoked when OTP verification succeeds.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* <Button
|
|
17
|
+
* onClick={() =>
|
|
18
|
+
* lauchOtpVerificationModal({
|
|
19
|
+
* otpLength: 4,
|
|
20
|
+
* obsecureText: false,
|
|
21
|
+
* phoneNumber: '254700000000',
|
|
22
|
+
* renderOtpTrigger: (p) => (
|
|
23
|
+
* <>
|
|
24
|
+
* <p>Any customcomponents here {p}</p>
|
|
25
|
+
* </>
|
|
26
|
+
* ),
|
|
27
|
+
* onRequestOtp: (phone) =>
|
|
28
|
+
* new Promise((resolve, reject) => {
|
|
29
|
+
* const success = false;
|
|
30
|
+
* setTimeout(() => (success ? resolve() : reject(new Error('Some error'))), 3000);
|
|
31
|
+
* }),
|
|
32
|
+
* onVerify: async (otp) =>
|
|
33
|
+
* new Promise((resolve, reject) => {
|
|
34
|
+
* const success = false;
|
|
35
|
+
* setTimeout(() => (success ? resolve() : reject(new Error('Some error'))), 3000);
|
|
36
|
+
* }),
|
|
37
|
+
* })
|
|
38
|
+
* }
|
|
39
|
+
* >
|
|
40
|
+
* Launch Otp verification Modal
|
|
41
|
+
* </Button>
|
|
42
|
+
*/
|
|
43
|
+
export type OTPVerificationmodalOptions = {
|
|
44
|
+
otpLength?: number;
|
|
45
|
+
onVerify?: (otp: string) => Promise<void>;
|
|
46
|
+
obsecureText?: boolean;
|
|
47
|
+
centerBoxes?: boolean;
|
|
48
|
+
phoneNumber: string;
|
|
49
|
+
renderOtpTrigger: (phoneNumber: string) => React.ReactNode;
|
|
50
|
+
onRequestOtp?: (phoneNumber: string) => Promise<void>;
|
|
51
|
+
onVerificationSuccess?: () => void;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
*
|
|
56
|
+
*
|
|
57
|
+
*/
|
|
58
|
+
export const lauchOtpVerificationModal = (props: OTPVerificationmodalOptions) => {
|
|
59
|
+
const dispose = showModal('otp-verification-modal', {
|
|
60
|
+
onClose: () => dispose(),
|
|
61
|
+
size: 'xs',
|
|
62
|
+
...props,
|
|
63
|
+
});
|
|
64
|
+
};
|
|
65
|
+
export default otpverificationModal;
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Button,
|
|
3
|
+
ButtonSet,
|
|
4
|
+
InlineLoading,
|
|
5
|
+
InlineNotification,
|
|
6
|
+
ModalBody,
|
|
7
|
+
ModalFooter,
|
|
8
|
+
ModalHeader,
|
|
9
|
+
Tabs,
|
|
10
|
+
TextInput,
|
|
11
|
+
Tile,
|
|
12
|
+
} from '@carbon/react';
|
|
13
|
+
import React, { FC, useState } from 'react';
|
|
14
|
+
import { useTranslation } from 'react-i18next';
|
|
15
|
+
import styles from './otp-verification.scss';
|
|
16
|
+
import PinPut from '../pin-put/pinput.component';
|
|
17
|
+
import { Phone } from '@carbon/react/icons';
|
|
18
|
+
export const PHONE_NUMBER_REGEX = /^(\+?254|0)((7|1)\d{8})$/;
|
|
19
|
+
|
|
20
|
+
type OTPVerificationModalProps = {
|
|
21
|
+
onClose?: () => void;
|
|
22
|
+
otpLength?: number;
|
|
23
|
+
onVerify?: (otp: string) => Promise<void>;
|
|
24
|
+
onVerificationSuccess?: () => void;
|
|
25
|
+
obsecureText?: boolean;
|
|
26
|
+
centerBoxes?: boolean;
|
|
27
|
+
phoneNumber: string;
|
|
28
|
+
renderOtpTrigger: (phoneNumber: string) => React.ReactNode;
|
|
29
|
+
onRequestOtp?: (phoneNumber: string) => Promise<void>;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const OTPVerificationModal: FC<OTPVerificationModalProps> = ({
|
|
33
|
+
onClose,
|
|
34
|
+
onVerify,
|
|
35
|
+
otpLength = 5,
|
|
36
|
+
centerBoxes,
|
|
37
|
+
obsecureText,
|
|
38
|
+
phoneNumber,
|
|
39
|
+
renderOtpTrigger,
|
|
40
|
+
onRequestOtp,
|
|
41
|
+
onVerificationSuccess,
|
|
42
|
+
}) => {
|
|
43
|
+
const { t } = useTranslation();
|
|
44
|
+
const [otp, setOtp] = useState('');
|
|
45
|
+
const [newPhoneNumber, setNewPhoneNumber] = useState('');
|
|
46
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
47
|
+
const [error, setError] = useState<{ type: 'request' | 'verification'; error: Error } | null>(null);
|
|
48
|
+
const [mode, setMode] = useState<'landing' | 'verify-otp' | 'change-number'>('landing');
|
|
49
|
+
const [requestingOtp, setRequestingOtp] = useState(false);
|
|
50
|
+
const handleVerify = async () => {
|
|
51
|
+
setError(null);
|
|
52
|
+
try {
|
|
53
|
+
setIsLoading(true);
|
|
54
|
+
await onVerify?.(otp);
|
|
55
|
+
onClose();
|
|
56
|
+
onVerificationSuccess?.();
|
|
57
|
+
} catch (error) {
|
|
58
|
+
setError({ type: 'verification', error });
|
|
59
|
+
} finally {
|
|
60
|
+
setIsLoading(false);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const handleRequestingOtp = async (phone: string) => {
|
|
65
|
+
setError(null);
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
setRequestingOtp(true);
|
|
69
|
+
await onRequestOtp(phone);
|
|
70
|
+
setMode('verify-otp');
|
|
71
|
+
} catch (error) {
|
|
72
|
+
setError({ type: 'request', error });
|
|
73
|
+
} finally {
|
|
74
|
+
setRequestingOtp(false);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
return (
|
|
78
|
+
<React.Fragment>
|
|
79
|
+
<ModalHeader className={styles.sectionHeader} closeModal={onClose}>
|
|
80
|
+
{t('otpVerification', 'OTP Verification')}
|
|
81
|
+
</ModalHeader>
|
|
82
|
+
<ModalBody>
|
|
83
|
+
{requestingOtp && <InlineLoading />}
|
|
84
|
+
{!requestingOtp && (
|
|
85
|
+
<div>
|
|
86
|
+
{error && (
|
|
87
|
+
<>
|
|
88
|
+
<InlineNotification
|
|
89
|
+
lowContrast
|
|
90
|
+
title={
|
|
91
|
+
error?.type === 'request'
|
|
92
|
+
? t('otpRequestError', 'Error requesting OTP')
|
|
93
|
+
: t('otpVerificationError', 'Error Verifying OTP')
|
|
94
|
+
}
|
|
95
|
+
subtitle={error?.error?.message}
|
|
96
|
+
/>
|
|
97
|
+
<br />
|
|
98
|
+
</>
|
|
99
|
+
)}
|
|
100
|
+
{mode === 'landing' ? (
|
|
101
|
+
<Tile role="button" onClick={() => handleRequestingOtp(phoneNumber)}>
|
|
102
|
+
{renderOtpTrigger(phoneNumber)}
|
|
103
|
+
</Tile>
|
|
104
|
+
) : mode === 'verify-otp' ? (
|
|
105
|
+
<PinPut
|
|
106
|
+
value={otp}
|
|
107
|
+
onChange={setOtp}
|
|
108
|
+
numInputs={otpLength}
|
|
109
|
+
centerBoxes={centerBoxes}
|
|
110
|
+
obsecureText={obsecureText}
|
|
111
|
+
/>
|
|
112
|
+
) : null}
|
|
113
|
+
{mode === 'landing' && (
|
|
114
|
+
<>
|
|
115
|
+
<br />
|
|
116
|
+
<Button
|
|
117
|
+
className={styles.changeNoBtn}
|
|
118
|
+
size="sm"
|
|
119
|
+
onClick={() => setMode('change-number')}
|
|
120
|
+
kind="tertiary"
|
|
121
|
+
renderIcon={Phone}>
|
|
122
|
+
{t('changePhoneNumber', 'Change phone number')}
|
|
123
|
+
</Button>
|
|
124
|
+
</>
|
|
125
|
+
)}
|
|
126
|
+
{mode === 'change-number' && (
|
|
127
|
+
<TextInput
|
|
128
|
+
id={'otp-phone-number'}
|
|
129
|
+
labelText={t('phoneNumber', 'Phone number')}
|
|
130
|
+
value={newPhoneNumber}
|
|
131
|
+
onChange={(ev) => setNewPhoneNumber(ev.target.value)}
|
|
132
|
+
/>
|
|
133
|
+
)}
|
|
134
|
+
</div>
|
|
135
|
+
)}
|
|
136
|
+
</ModalBody>
|
|
137
|
+
<ModalFooter>
|
|
138
|
+
<ButtonSet className={styles.buttonSet}>
|
|
139
|
+
<Button kind="secondary" onClick={onClose} className={styles.button}>
|
|
140
|
+
{t('cancel', 'Cancel')}
|
|
141
|
+
</Button>
|
|
142
|
+
{mode !== 'change-number' ? (
|
|
143
|
+
<Button
|
|
144
|
+
disabled={isLoading || otp.length !== otpLength}
|
|
145
|
+
kind="primary"
|
|
146
|
+
onClick={handleVerify}
|
|
147
|
+
className={styles.button}>
|
|
148
|
+
{isLoading ? <InlineLoading title={t('verifyingOtp', 'Verifying OTP')} /> : t('verify', 'Verify')}
|
|
149
|
+
</Button>
|
|
150
|
+
) : (
|
|
151
|
+
<Button
|
|
152
|
+
disabled={!PHONE_NUMBER_REGEX.test(newPhoneNumber)}
|
|
153
|
+
onClick={() => handleRequestingOtp(newPhoneNumber)}>
|
|
154
|
+
{t('sendOtp', 'Send OTP')}
|
|
155
|
+
</Button>
|
|
156
|
+
)}
|
|
157
|
+
</ButtonSet>
|
|
158
|
+
</ModalFooter>
|
|
159
|
+
</React.Fragment>
|
|
160
|
+
);
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
export default OTPVerificationModal;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
@use '@carbon/type';
|
|
2
|
+
@use '@carbon/layout';
|
|
3
|
+
@use '@carbon/colors';
|
|
4
|
+
|
|
5
|
+
.button {
|
|
6
|
+
height: layout.$spacing-10;
|
|
7
|
+
display: flex;
|
|
8
|
+
align-content: flex-start;
|
|
9
|
+
align-items: baseline;
|
|
10
|
+
min-width: 20%;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.buttonSet {
|
|
14
|
+
padding: 0rem;
|
|
15
|
+
margin-top: layout.$spacing-05;
|
|
16
|
+
display: flex;
|
|
17
|
+
justify-content: space-between;
|
|
18
|
+
width: 100%;
|
|
19
|
+
margin-bottom: layout.$spacing-05;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.sectionHeader {
|
|
23
|
+
@include type.type-style('heading-02');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.changeNoBtn {
|
|
27
|
+
width: 100%;
|
|
28
|
+
min-height: 100%;
|
|
29
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import classNames from 'classnames';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import OtpInput, { OTPInputProps } from 'react-otp-input';
|
|
4
|
+
import styles from './pinput.scss';
|
|
5
|
+
|
|
6
|
+
type Props = Partial<Omit<OTPInputProps, 'inputType' | 'renderInput' | 'containerStyle'>> & {
|
|
7
|
+
obsecureText?: boolean;
|
|
8
|
+
centerBoxes?: boolean;
|
|
9
|
+
label?: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const PinPut = ({ numInputs = 5, centerBoxes, obsecureText, label, ...props }: Props) => {
|
|
13
|
+
return (
|
|
14
|
+
<div className={styles.container}>
|
|
15
|
+
{label && <p className={styles.label}> {label}</p>}
|
|
16
|
+
<OtpInput
|
|
17
|
+
renderSeparator={<span>-</span>}
|
|
18
|
+
{...props}
|
|
19
|
+
inputType={obsecureText ? 'password' : 'text'}
|
|
20
|
+
numInputs={numInputs}
|
|
21
|
+
renderInput={(props) => <input {...props} className={classNames(props.className, styles.input)} />}
|
|
22
|
+
skipDefaultStyles
|
|
23
|
+
value={props.value ?? ''}
|
|
24
|
+
onChange={props.onChange}
|
|
25
|
+
containerStyle={{ gap: '4px', justifyContent: centerBoxes ? 'center' : undefined }}
|
|
26
|
+
/>
|
|
27
|
+
</div>
|
|
28
|
+
);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export default PinPut;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
@use '@carbon/type';
|
|
2
|
+
@use '@carbon/layout';
|
|
3
|
+
@use '@carbon/colors';
|
|
4
|
+
|
|
5
|
+
.input {
|
|
6
|
+
border-radius: layout.$spacing-03;
|
|
7
|
+
padding: layout.$spacing-03;
|
|
8
|
+
width: 40px;
|
|
9
|
+
font-weight: bold;
|
|
10
|
+
text-align: center;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.container {
|
|
14
|
+
display: flex;
|
|
15
|
+
flex-direction: column;
|
|
16
|
+
gap: layout.$spacing-03;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.label {
|
|
20
|
+
@include type.type-style('heading-01');
|
|
21
|
+
font-weight: normal;
|
|
22
|
+
}
|
package/translations/am.json
CHANGED
|
@@ -2,22 +2,46 @@
|
|
|
2
2
|
"age": "Age",
|
|
3
3
|
"awaitingConsultation": "Awaiting consultation",
|
|
4
4
|
"awaitingInvestigation": "Awaiting Investigation",
|
|
5
|
+
"cancel": "Cancel",
|
|
6
|
+
"changePhoneNumber": "Change phone number",
|
|
7
|
+
"chooseIdentifierType": "Choose identifier type",
|
|
8
|
+
"clearAll": "Clear All",
|
|
9
|
+
"clientRegistry": "Client registry verification",
|
|
10
|
+
"emergencyRegistration": "Emergency registration",
|
|
11
|
+
"enterIdentifierNumber": "Enter identifier number",
|
|
12
|
+
"error": "Error",
|
|
5
13
|
"errorLoadingPatient": "Error loading patient",
|
|
6
14
|
"gender": "Gender",
|
|
15
|
+
"identificationType": "Identification Type",
|
|
7
16
|
"identifier": "Identifier",
|
|
17
|
+
"identifierNumber": "Identifier number*",
|
|
8
18
|
"investigationComplete": "Investigation complete",
|
|
9
19
|
"loadingPatient": "Loading patient...",
|
|
10
20
|
"loadingQueues": "Loading queues...",
|
|
11
21
|
"name": "Name",
|
|
12
22
|
"nextPage": "Next page",
|
|
13
23
|
"noQueueEntries": "No queue entries found",
|
|
24
|
+
"otpRequestError": "Error requesting OTP",
|
|
25
|
+
"otpVerification": "OTP Verification",
|
|
26
|
+
"otpVerificationError": "Error Verifying OTP",
|
|
14
27
|
"patientAttended": "Patient attended",
|
|
15
28
|
"patientInWaiting": "Patient in waiting",
|
|
29
|
+
"phoneNumber": "Phone number",
|
|
16
30
|
"previousPage": "Previous page",
|
|
17
31
|
"priority": "Priority",
|
|
18
32
|
"programManagement": "Program Management",
|
|
33
|
+
"pullFromHIE": "Pulling from registry...",
|
|
19
34
|
"queueEntries": "Queue Entries",
|
|
35
|
+
"registration": "Registration",
|
|
36
|
+
"searchFailed": "Failed to search for patient. Please try again.",
|
|
37
|
+
"searchPatients": "Search for Patient(s)",
|
|
38
|
+
"selectIdentifierAndNumber": "Please select an identifier type and enter an identifier number",
|
|
39
|
+
"sendOtp": "Send OTP",
|
|
40
|
+
"validationError": "Validation Error",
|
|
41
|
+
"verify": "Verify",
|
|
42
|
+
"verifyingOtp": "Verifying OTP",
|
|
20
43
|
"visitComplete": "Visit complete",
|
|
44
|
+
"visitFormBanner": "Visit Form Banner",
|
|
21
45
|
"visitTime": "Visit Time",
|
|
22
46
|
"vitalsAndAnthropometrics": "Vitals and Anthropometrics"
|
|
23
47
|
}
|
package/translations/en.json
CHANGED
|
@@ -2,22 +2,46 @@
|
|
|
2
2
|
"age": "Age",
|
|
3
3
|
"awaitingConsultation": "Awaiting consultation",
|
|
4
4
|
"awaitingInvestigation": "Awaiting Investigation",
|
|
5
|
+
"cancel": "Cancel",
|
|
6
|
+
"changePhoneNumber": "Change phone number",
|
|
7
|
+
"chooseIdentifierType": "Choose identifier type",
|
|
8
|
+
"clearAll": "Clear All",
|
|
9
|
+
"clientRegistry": "Client registry verification",
|
|
10
|
+
"emergencyRegistration": "Emergency registration",
|
|
11
|
+
"enterIdentifierNumber": "Enter identifier number",
|
|
12
|
+
"error": "Error",
|
|
5
13
|
"errorLoadingPatient": "Error loading patient",
|
|
6
14
|
"gender": "Gender",
|
|
15
|
+
"identificationType": "Identification Type",
|
|
7
16
|
"identifier": "Identifier",
|
|
17
|
+
"identifierNumber": "Identifier number*",
|
|
8
18
|
"investigationComplete": "Investigation complete",
|
|
9
19
|
"loadingPatient": "Loading patient...",
|
|
10
20
|
"loadingQueues": "Loading queues...",
|
|
11
21
|
"name": "Name",
|
|
12
22
|
"nextPage": "Next page",
|
|
13
23
|
"noQueueEntries": "No queue entries found",
|
|
24
|
+
"otpRequestError": "Error requesting OTP",
|
|
25
|
+
"otpVerification": "OTP Verification",
|
|
26
|
+
"otpVerificationError": "Error Verifying OTP",
|
|
14
27
|
"patientAttended": "Patient attended",
|
|
15
28
|
"patientInWaiting": "Patient in waiting",
|
|
29
|
+
"phoneNumber": "Phone number",
|
|
16
30
|
"previousPage": "Previous page",
|
|
17
31
|
"priority": "Priority",
|
|
18
32
|
"programManagement": "Program Management",
|
|
33
|
+
"pullFromHIE": "Pulling from registry...",
|
|
19
34
|
"queueEntries": "Queue Entries",
|
|
35
|
+
"registration": "Registration",
|
|
36
|
+
"searchFailed": "Failed to search for patient. Please try again.",
|
|
37
|
+
"searchPatients": "Search for Patient(s)",
|
|
38
|
+
"selectIdentifierAndNumber": "Please select an identifier type and enter an identifier number",
|
|
39
|
+
"sendOtp": "Send OTP",
|
|
40
|
+
"validationError": "Validation Error",
|
|
41
|
+
"verify": "Verify",
|
|
42
|
+
"verifyingOtp": "Verifying OTP",
|
|
20
43
|
"visitComplete": "Visit complete",
|
|
44
|
+
"visitFormBanner": "Visit Form Banner",
|
|
21
45
|
"visitTime": "Visit Time",
|
|
22
46
|
"vitalsAndAnthropometrics": "Vitals and Anthropometrics"
|
|
23
47
|
}
|
package/translations/sw.json
CHANGED
|
@@ -2,22 +2,46 @@
|
|
|
2
2
|
"age": "Age",
|
|
3
3
|
"awaitingConsultation": "Awaiting consultation",
|
|
4
4
|
"awaitingInvestigation": "Awaiting Investigation",
|
|
5
|
+
"cancel": "Cancel",
|
|
6
|
+
"changePhoneNumber": "Change phone number",
|
|
7
|
+
"chooseIdentifierType": "Choose identifier type",
|
|
8
|
+
"clearAll": "Clear All",
|
|
9
|
+
"clientRegistry": "Client registry verification",
|
|
10
|
+
"emergencyRegistration": "Emergency registration",
|
|
11
|
+
"enterIdentifierNumber": "Enter identifier number",
|
|
12
|
+
"error": "Error",
|
|
5
13
|
"errorLoadingPatient": "Error loading patient",
|
|
6
14
|
"gender": "Gender",
|
|
15
|
+
"identificationType": "Identification Type",
|
|
7
16
|
"identifier": "Identifier",
|
|
17
|
+
"identifierNumber": "Identifier number*",
|
|
8
18
|
"investigationComplete": "Investigation complete",
|
|
9
19
|
"loadingPatient": "Loading patient...",
|
|
10
20
|
"loadingQueues": "Loading queues...",
|
|
11
21
|
"name": "Name",
|
|
12
22
|
"nextPage": "Next page",
|
|
13
23
|
"noQueueEntries": "No queue entries found",
|
|
24
|
+
"otpRequestError": "Error requesting OTP",
|
|
25
|
+
"otpVerification": "OTP Verification",
|
|
26
|
+
"otpVerificationError": "Error Verifying OTP",
|
|
14
27
|
"patientAttended": "Patient attended",
|
|
15
28
|
"patientInWaiting": "Patient in waiting",
|
|
29
|
+
"phoneNumber": "Phone number",
|
|
16
30
|
"previousPage": "Previous page",
|
|
17
31
|
"priority": "Priority",
|
|
18
32
|
"programManagement": "Program Management",
|
|
33
|
+
"pullFromHIE": "Pulling from registry...",
|
|
19
34
|
"queueEntries": "Queue Entries",
|
|
35
|
+
"registration": "Registration",
|
|
36
|
+
"searchFailed": "Failed to search for patient. Please try again.",
|
|
37
|
+
"searchPatients": "Search for Patient(s)",
|
|
38
|
+
"selectIdentifierAndNumber": "Please select an identifier type and enter an identifier number",
|
|
39
|
+
"sendOtp": "Send OTP",
|
|
40
|
+
"validationError": "Validation Error",
|
|
41
|
+
"verify": "Verify",
|
|
42
|
+
"verifyingOtp": "Verifying OTP",
|
|
20
43
|
"visitComplete": "Visit complete",
|
|
44
|
+
"visitFormBanner": "Visit Form Banner",
|
|
21
45
|
"visitTime": "Visit Time",
|
|
22
46
|
"vitalsAndAnthropometrics": "Vitals and Anthropometrics"
|
|
23
47
|
}
|