medos-sdk 1.0.1 → 1.0.3
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/README.md +208 -73
- package/dist/client/MedosClient.d.ts +1 -1
- package/dist/client/MedosClient.js +1 -1
- package/dist/components/AppointmentCalender.d.ts +1 -4
- package/dist/components/AppointmentCalender.js +282 -486
- package/dist/components/AppointmentDateTimeModal.d.ts +14 -0
- package/dist/components/AppointmentDateTimeModal.js +206 -0
- package/dist/components/ConfigurableCard.d.ts +12 -0
- package/dist/components/ConfigurableCard.js +29 -0
- package/dist/components/DoctorSelectModal.d.ts +7 -0
- package/dist/components/DoctorSelectModal.js +80 -0
- package/dist/components/Icons/Check.d.ts +6 -0
- package/dist/components/Icons/Check.js +2 -0
- package/dist/components/Icons/ChevronDownIcon.d.ts +4 -0
- package/dist/components/Icons/ChevronDownIcon.js +2 -0
- package/dist/components/Icons/ChevronLeft.d.ts +3 -0
- package/dist/components/Icons/ChevronLeft.js +3 -0
- package/dist/components/Icons/ChevronRight.d.ts +3 -0
- package/dist/components/Icons/ChevronRight.js +3 -0
- package/dist/components/Icons/ConfirmationCheck.d.ts +1 -0
- package/dist/components/Icons/ConfirmationCheck.js +9 -0
- package/dist/components/Icons/ConsultationType.d.ts +1 -0
- package/dist/components/Icons/ConsultationType.js +2 -0
- package/dist/components/Icons/Date&TimeIcon.d.ts +1 -0
- package/dist/components/Icons/Date&TimeIcon.js +2 -0
- package/dist/components/Icons/MapIcon.d.ts +1 -0
- package/dist/components/Icons/MapIcon.js +2 -0
- package/dist/components/Icons/PaymentMethodIcon.d.ts +1 -0
- package/dist/components/Icons/PaymentMethodIcon.js +2 -0
- package/dist/components/Icons/UserIcon.d.ts +1 -0
- package/dist/components/Icons/UserIcon.js +2 -0
- package/dist/components/PatientDetailsStep.d.ts +3 -0
- package/dist/components/PatientDetailsStep.js +76 -0
- package/dist/components/PhoneVerificationStep.d.ts +3 -0
- package/dist/components/PhoneVerificationStep.js +39 -0
- package/dist/components/SuccessStep.d.ts +3 -0
- package/dist/components/SuccessStep.js +17 -0
- package/dist/components/custom-calendar.d.ts +5 -0
- package/dist/components/custom-calendar.js +153 -0
- package/dist/components/styles.d.ts +6 -0
- package/dist/components/styles.js +257 -0
- package/dist/components/types.d.ts +182 -0
- package/dist/components/types.js +55 -0
- package/dist/components/ui/select.d.ts +10 -0
- package/dist/components/ui/select.js +21 -0
- package/dist/components/uiComponents/SelectDropdown.d.ts +41 -0
- package/dist/components/uiComponents/SelectDropdown.js +302 -0
- package/dist/components/utils.d.ts +5 -0
- package/dist/components/utils.js +15 -0
- package/dist/components/validation.d.ts +2 -0
- package/dist/components/validation.js +7 -0
- package/dist/context/TemplateContext.d.ts +12 -0
- package/dist/context/TemplateContext.js +19 -0
- package/dist/core/index.d.ts +8 -0
- package/dist/core/index.js +8 -0
- package/dist/lib/templateUtils.d.ts +3 -0
- package/dist/lib/templateUtils.js +28 -0
- package/dist/react/index.d.ts +2 -0
- package/dist/react/index.js +2 -0
- package/dist/services/AppointmentService.d.ts +9 -10
- package/dist/services/AppointmentService.js +12 -10
- package/dist/templates/registry.d.ts +12 -0
- package/dist/templates/registry.js +58 -0
- package/dist/vanilla/AppointmentCalendarWidget.d.ts +41 -0
- package/dist/vanilla/AppointmentCalendarWidget.js +848 -0
- package/dist/vanilla/appointment-calendar/provider.d.ts +13 -0
- package/dist/vanilla/appointment-calendar/types.d.ts +144 -0
- package/dist/vanilla/appointments/provider.d.ts +13 -0
- package/dist/vanilla/appointments/types.d.ts +81 -0
- package/dist/vanilla/client/MedosClient.d.ts +32 -0
- package/dist/vanilla/components/AppointmentCalender.d.ts +3 -0
- package/dist/vanilla/components/AppointmentDateTimeModal.d.ts +14 -0
- package/dist/vanilla/components/ConfigurableCard.d.ts +12 -0
- package/dist/vanilla/components/DoctorSelectModal.d.ts +7 -0
- package/dist/vanilla/components/Icons/Check.d.ts +6 -0
- package/dist/vanilla/components/Icons/ChevronDownIcon.d.ts +4 -0
- package/dist/vanilla/components/Icons/ChevronLeft.d.ts +3 -0
- package/dist/vanilla/components/Icons/ChevronRight.d.ts +3 -0
- package/dist/vanilla/components/Icons/ConfirmationCheck.d.ts +1 -0
- package/dist/vanilla/components/Icons/ConsultationType.d.ts +1 -0
- package/dist/vanilla/components/Icons/Date&TimeIcon.d.ts +1 -0
- package/dist/vanilla/components/Icons/MapIcon.d.ts +1 -0
- package/dist/vanilla/components/Icons/PaymentMethodIcon.d.ts +1 -0
- package/dist/vanilla/components/Icons/UserIcon.d.ts +1 -0
- package/dist/vanilla/components/PatientDetailsStep.d.ts +3 -0
- package/dist/vanilla/components/PhoneVerificationStep.d.ts +3 -0
- package/dist/vanilla/components/SuccessStep.d.ts +3 -0
- package/dist/vanilla/components/custom-calendar.d.ts +5 -0
- package/dist/vanilla/components/styles.d.ts +6 -0
- package/dist/vanilla/components/types.d.ts +182 -0
- package/dist/vanilla/components/ui/select.d.ts +10 -0
- package/dist/vanilla/components/uiComponents/SelectDropdown.d.ts +41 -0
- package/dist/vanilla/components/utils.d.ts +5 -0
- package/dist/vanilla/components/validation.d.ts +2 -0
- package/dist/vanilla/context/TemplateContext.d.ts +12 -0
- package/dist/vanilla/core/index.d.ts +8 -0
- package/dist/vanilla/index.d.ts +7 -0
- package/dist/vanilla/index.js +2 -0
- package/dist/vanilla/lib/templateUtils.d.ts +3 -0
- package/dist/vanilla/react/index.d.ts +2 -0
- package/dist/vanilla/services/AppointmentService.d.ts +85 -0
- package/dist/vanilla/services/AuthService.d.ts +6 -0
- package/dist/vanilla/services/PatientService.d.ts +14 -0
- package/dist/vanilla/templates/alternative.css +13 -0
- package/dist/vanilla/templates/default.css +13 -0
- package/dist/vanilla/templates/registry.d.ts +12 -0
- package/dist/vanilla/vanilla/AppointmentCalendarWidget.d.ts +41 -0
- package/dist/vanilla/vanilla/index.d.ts +2 -0
- package/dist/vanilla/vanilla/widget.d.ts +10 -0
- package/dist/vanilla/widget.css +217 -0
- package/dist/vanilla/widget.d.ts +10 -0
- package/dist/vanilla/widget.js +5084 -0
- package/package.json +40 -5
|
@@ -1,570 +1,366 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs
|
|
2
|
-
import { useEffect,
|
|
3
|
-
import { AppointmentService
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useCallback, useReducer } from "react";
|
|
3
|
+
import { AppointmentService } from "../services/AppointmentService";
|
|
4
4
|
import { PatientService } from "../services/PatientService";
|
|
5
|
+
import { AppointmentDateTimeModal } from "./AppointmentDateTimeModal";
|
|
6
|
+
import { DoctorSelectModal } from "./DoctorSelectModal";
|
|
7
|
+
import { PhoneVerificationStep } from "./PhoneVerificationStep";
|
|
8
|
+
import { PatientDetailsStep } from "./PatientDetailsStep";
|
|
9
|
+
import { SuccessStep } from "./SuccessStep";
|
|
10
|
+
import { INITIAL_STATE, } from "./types";
|
|
11
|
+
import { CONTAINER_STYLES } from "./styles";
|
|
12
|
+
import { validatePhoneNumber, validateCountryCode } from "./validation";
|
|
13
|
+
import { formatDateToISO, parsePatientName } from "./utils";
|
|
14
|
+
const appointmentReducer = (state, action) => {
|
|
15
|
+
switch (action.type) {
|
|
16
|
+
case "SET_STEP":
|
|
17
|
+
return { ...state, step: action.payload };
|
|
18
|
+
case "SET_LOADING":
|
|
19
|
+
return { ...state, loading: action.payload };
|
|
20
|
+
case "SET_ERROR":
|
|
21
|
+
return { ...state, error: action.payload };
|
|
22
|
+
case "SET_WORKSPACE": {
|
|
23
|
+
const doctorMap = {};
|
|
24
|
+
action.payload.addresses.forEach((addr) => {
|
|
25
|
+
doctorMap[addr.id] = addr.doctors || [];
|
|
26
|
+
});
|
|
27
|
+
return {
|
|
28
|
+
...state,
|
|
29
|
+
workspaceId: action.payload.id,
|
|
30
|
+
addresses: action.payload.addresses,
|
|
31
|
+
addressDoctorsMap: doctorMap,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
case "SET_SELECTED_ADDRESS":
|
|
35
|
+
return { ...state, selectedAddress: action.payload };
|
|
36
|
+
case "SET_SELECTED_DOCTOR":
|
|
37
|
+
return { ...state, selectedDoctor: action.payload };
|
|
38
|
+
case "SET_SELECTED_DATE":
|
|
39
|
+
return { ...state, selectedDate: action.payload, selectedSlot: null };
|
|
40
|
+
case "SET_SLOTS":
|
|
41
|
+
return { ...state, slots: action.payload };
|
|
42
|
+
case "SET_SELECTED_SLOT":
|
|
43
|
+
return { ...state, selectedSlot: action.payload };
|
|
44
|
+
case "SET_CONSULTATION_MODE":
|
|
45
|
+
return { ...state, consultationMode: action.payload };
|
|
46
|
+
case "SET_CONSULTATION_CHARGE":
|
|
47
|
+
return { ...state, consultationCharge: action.payload };
|
|
48
|
+
case "SET_PATIENT_NAME":
|
|
49
|
+
return { ...state, patientName: action.payload };
|
|
50
|
+
case "SET_PATIENT_AGE":
|
|
51
|
+
return { ...state, patientAge: action.payload };
|
|
52
|
+
case "SET_PATIENT_EMAIL":
|
|
53
|
+
return { ...state, patientEmail: action.payload };
|
|
54
|
+
case "SET_PATIENT_GENDER":
|
|
55
|
+
return { ...state, patientGender: action.payload };
|
|
56
|
+
case "SET_BLOOD_GROUP":
|
|
57
|
+
return { ...state, bloodGroup: action.payload };
|
|
58
|
+
case "SET_PATIENT_ADDRESS":
|
|
59
|
+
return { ...state, patientAddress: action.payload };
|
|
60
|
+
case "SET_PATIENT_CITY":
|
|
61
|
+
return { ...state, patientCity: action.payload };
|
|
62
|
+
case "SET_PATIENT_STATE":
|
|
63
|
+
return { ...state, patientState: action.payload };
|
|
64
|
+
case "SET_PATIENT_COUNTRY":
|
|
65
|
+
return { ...state, patientCountry: action.payload };
|
|
66
|
+
case "SET_PATIENT_ZIPCODE":
|
|
67
|
+
return { ...state, patientZipcode: action.payload };
|
|
68
|
+
case "SET_PATIENT_LANDMARK":
|
|
69
|
+
return { ...state, patientLandmark: action.payload };
|
|
70
|
+
case "SET_COUNTRY_CODE":
|
|
71
|
+
return { ...state, countryCode: action.payload };
|
|
72
|
+
case "SET_PATIENT_PHONE":
|
|
73
|
+
return { ...state, patientPhone: action.payload };
|
|
74
|
+
case "SET_OTP_CODE":
|
|
75
|
+
return { ...state, otpCode: action.payload };
|
|
76
|
+
case "SET_OTP_SENT":
|
|
77
|
+
return { ...state, otpSent: action.payload };
|
|
78
|
+
case "SET_OTP_VERIFIED":
|
|
79
|
+
return { ...state, otpVerified: action.payload };
|
|
80
|
+
case "SET_OTP_SENDING":
|
|
81
|
+
return { ...state, otpSending: action.payload };
|
|
82
|
+
case "SET_OTP_VERIFYING":
|
|
83
|
+
return { ...state, otpVerifying: action.payload };
|
|
84
|
+
case "RESET_FORM":
|
|
85
|
+
return INITIAL_STATE;
|
|
86
|
+
default:
|
|
87
|
+
return state;
|
|
88
|
+
}
|
|
89
|
+
};
|
|
5
90
|
export const AppointmentCalender = ({ onError, }) => {
|
|
6
|
-
const [
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const [selectedSlot, setSelectedSlot] = useState(null);
|
|
16
|
-
const [loading, setLoading] = useState(false);
|
|
17
|
-
const [error, setError] = useState(null);
|
|
18
|
-
const [patientName, setPatientName] = useState("");
|
|
19
|
-
const [patientAge, setPatientAge] = useState("");
|
|
20
|
-
const [patientAddress, setPatientAddress] = useState("");
|
|
21
|
-
const [patientEmail, setPatientEmail] = useState("");
|
|
22
|
-
const [patientGender, setPatientGender] = useState("");
|
|
23
|
-
const [problemFacing, setProblemFacing] = useState("");
|
|
24
|
-
const [consultationCharge, setConsultationCharge] = useState("");
|
|
25
|
-
const [countryCode, setCountryCode] = useState("+91");
|
|
26
|
-
const [patientPhone, setPatientPhone] = useState("");
|
|
27
|
-
const [otpCode, setOtpCode] = useState("");
|
|
28
|
-
const [otpSent, setOtpSent] = useState(false);
|
|
29
|
-
const [otpVerified, setOtpVerified] = useState(false);
|
|
30
|
-
const [otpSending, setOtpSending] = useState(false);
|
|
31
|
-
const [otpVerifying, setOtpVerifying] = useState(false);
|
|
32
|
-
useEffect(() => {
|
|
91
|
+
const [state, dispatch] = useReducer(appointmentReducer, INITIAL_STATE);
|
|
92
|
+
const handleDateChange = useCallback(async (date) => {
|
|
93
|
+
dispatch({ type: "SET_SELECTED_DATE", payload: date });
|
|
94
|
+
if (!state.workspaceId ||
|
|
95
|
+
!state.selectedAddress ||
|
|
96
|
+
!state.selectedDoctor) {
|
|
97
|
+
dispatch({ type: "SET_SLOTS", payload: [] });
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
33
100
|
let mounted = true;
|
|
34
|
-
(
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
if (addrResp && Array.isArray(addrResp.addresses)) {
|
|
42
|
-
fetchedAddresses = addrResp.addresses;
|
|
43
|
-
if (addrResp.workspaceId && mounted) {
|
|
44
|
-
setWorkspaceId(addrResp.workspaceId);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
catch (e) {
|
|
49
|
-
if (!mounted)
|
|
50
|
-
return;
|
|
51
|
-
throw e;
|
|
52
|
-
}
|
|
53
|
-
if (mounted && fetchedAddresses.length > 0) {
|
|
54
|
-
const addrMap = {};
|
|
55
|
-
const mappedAddrs = fetchedAddresses.map((a, idx) => {
|
|
56
|
-
const id = String(a.id ?? idx);
|
|
57
|
-
const label = a.completeAddress ?? a.label ?? a.address ?? `Address ${idx + 1}`;
|
|
58
|
-
const docs = Array.isArray(a.doctors)
|
|
59
|
-
? a.doctors
|
|
60
|
-
: [];
|
|
61
|
-
addrMap[id] = docs || [];
|
|
62
|
-
return { id, label };
|
|
63
|
-
});
|
|
64
|
-
setAddresses(mappedAddrs);
|
|
65
|
-
setAddressDoctorsMap(addrMap);
|
|
66
|
-
const anyDoctorsExist = Object.values(addrMap).some((arr) => Array.isArray(arr) && arr.length > 0);
|
|
67
|
-
if (mappedAddrs.length === 1) {
|
|
68
|
-
const only = mappedAddrs[0];
|
|
69
|
-
setSelectedAddress(only.id);
|
|
70
|
-
const docsForAddr = addrMap[only.id] || [];
|
|
71
|
-
if (docsForAddr.length > 0) {
|
|
72
|
-
setDoctors(docsForAddr);
|
|
73
|
-
if (docsForAddr.length === 1) {
|
|
74
|
-
setSelectedDoctor(docsForAddr[0].id);
|
|
75
|
-
setStep(1);
|
|
76
|
-
}
|
|
77
|
-
else {
|
|
78
|
-
setStep(0);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
else {
|
|
82
|
-
if (anyDoctorsExist) {
|
|
83
|
-
setError("No doctors at this address. Please choose a different address.");
|
|
84
|
-
setDoctors([]);
|
|
85
|
-
setStep(0);
|
|
86
|
-
}
|
|
87
|
-
else {
|
|
88
|
-
setError("No doctors available for the selected location(s).");
|
|
89
|
-
setDoctors([]);
|
|
90
|
-
setStep(0);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
else {
|
|
95
|
-
setStep(0);
|
|
96
|
-
}
|
|
97
|
-
setLoading(false);
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
if (mounted) {
|
|
101
|
-
setError("No addresses or doctors available.");
|
|
102
|
-
setAddresses([]);
|
|
103
|
-
setAddressDoctorsMap({});
|
|
104
|
-
setDoctors([]);
|
|
105
|
-
setStep(0);
|
|
106
|
-
}
|
|
101
|
+
dispatch({ type: "SET_LOADING", payload: true });
|
|
102
|
+
dispatch({ type: "SET_ERROR", payload: null });
|
|
103
|
+
try {
|
|
104
|
+
const dateStr = formatDateToISO(date);
|
|
105
|
+
const fetchedSlots = await AppointmentService.fetchSlots(state.workspaceId, state.selectedAddress, state.selectedDoctor, dateStr);
|
|
106
|
+
if (mounted) {
|
|
107
|
+
dispatch({ type: "SET_SLOTS", payload: fetchedSlots || [] });
|
|
107
108
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const msg = e.message || "Failed to load
|
|
112
|
-
|
|
109
|
+
}
|
|
110
|
+
catch (e) {
|
|
111
|
+
if (mounted) {
|
|
112
|
+
const msg = e.message || "Failed to load slots";
|
|
113
|
+
dispatch({ type: "SET_ERROR", payload: msg });
|
|
113
114
|
onError?.(e);
|
|
114
115
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
}
|
|
116
|
+
}
|
|
117
|
+
finally {
|
|
118
|
+
if (mounted)
|
|
119
|
+
dispatch({ type: "SET_LOADING", payload: false });
|
|
120
|
+
}
|
|
120
121
|
return () => {
|
|
121
122
|
mounted = false;
|
|
122
123
|
};
|
|
123
|
-
}, []);
|
|
124
|
+
}, [state.workspaceId, state.selectedAddress, state.selectedDoctor, onError]);
|
|
124
125
|
useEffect(() => {
|
|
125
126
|
let mounted = true;
|
|
126
127
|
(async () => {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
setSelectedDoctor(null);
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
|
-
setLoading(true);
|
|
133
|
-
setError(null);
|
|
128
|
+
dispatch({ type: "SET_LOADING", payload: true });
|
|
129
|
+
dispatch({ type: "SET_ERROR", payload: null });
|
|
134
130
|
try {
|
|
135
|
-
const
|
|
136
|
-
if (
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
131
|
+
const response = await AppointmentService.getAddresses();
|
|
132
|
+
if (!mounted)
|
|
133
|
+
return;
|
|
134
|
+
if (response.workspaceId && response.addresses?.length > 0) {
|
|
135
|
+
dispatch({
|
|
136
|
+
type: "SET_WORKSPACE",
|
|
137
|
+
payload: {
|
|
138
|
+
id: response.workspaceId,
|
|
139
|
+
addresses: response.addresses,
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
if (response.addresses.length === 1) {
|
|
143
|
+
const address = response.addresses[0];
|
|
144
|
+
dispatch({ type: "SET_SELECTED_ADDRESS", payload: address.id });
|
|
145
|
+
if (address.doctors?.length === 1) {
|
|
146
|
+
dispatch({
|
|
147
|
+
type: "SET_SELECTED_DOCTOR",
|
|
148
|
+
payload: address.doctors[0].id,
|
|
149
|
+
});
|
|
150
|
+
dispatch({ type: "SET_STEP", payload: 1 });
|
|
145
151
|
}
|
|
146
152
|
}
|
|
147
153
|
}
|
|
148
154
|
else {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
if (otherHasDoctors) {
|
|
154
|
-
setError("No doctors at this address. Please select a different address.");
|
|
155
|
-
}
|
|
156
|
-
else {
|
|
157
|
-
setError("No doctors available for the selected location(s).");
|
|
158
|
-
}
|
|
159
|
-
}
|
|
155
|
+
dispatch({
|
|
156
|
+
type: "SET_ERROR",
|
|
157
|
+
payload: "No addresses or doctors available.",
|
|
158
|
+
});
|
|
160
159
|
}
|
|
161
160
|
}
|
|
162
161
|
catch (e) {
|
|
163
162
|
if (!mounted)
|
|
164
163
|
return;
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
if (mounted)
|
|
169
|
-
setLoading(false);
|
|
170
|
-
}
|
|
171
|
-
})();
|
|
172
|
-
return () => {
|
|
173
|
-
mounted = false;
|
|
174
|
-
};
|
|
175
|
-
}, [selectedAddress, addressDoctorsMap]);
|
|
176
|
-
useEffect(() => {
|
|
177
|
-
if (!workspaceId || !selectedAddress || !selectedDoctor || !date) {
|
|
178
|
-
setSlots([]);
|
|
179
|
-
setSelectedSlot(null);
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
182
|
-
let mounted = true;
|
|
183
|
-
setLoading(true);
|
|
184
|
-
setError(null);
|
|
185
|
-
(async () => {
|
|
186
|
-
try {
|
|
187
|
-
const s = await AppointmentService.fetchSlots(workspaceId, selectedAddress, selectedDoctor, date);
|
|
188
|
-
if (!mounted)
|
|
189
|
-
return;
|
|
190
|
-
setSlots(s || []);
|
|
191
|
-
}
|
|
192
|
-
catch (e) {
|
|
193
|
-
if (!mounted)
|
|
194
|
-
return;
|
|
195
|
-
setError(e.message || "Failed to load slots");
|
|
164
|
+
const msg = e.message || "Failed to load addresses";
|
|
165
|
+
dispatch({ type: "SET_ERROR", payload: msg });
|
|
166
|
+
onError?.(e);
|
|
196
167
|
}
|
|
197
168
|
finally {
|
|
198
169
|
if (mounted)
|
|
199
|
-
|
|
170
|
+
dispatch({ type: "SET_LOADING", payload: false });
|
|
200
171
|
}
|
|
201
172
|
})();
|
|
202
173
|
return () => {
|
|
203
174
|
mounted = false;
|
|
204
175
|
};
|
|
205
|
-
}, [
|
|
206
|
-
const
|
|
207
|
-
if (step ===
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
return;
|
|
212
|
-
setStep(1);
|
|
213
|
-
return;
|
|
176
|
+
}, [onError]);
|
|
177
|
+
const goBack = useCallback(() => {
|
|
178
|
+
if (state.step === 3) {
|
|
179
|
+
dispatch({ type: "SET_OTP_SENT", payload: false });
|
|
180
|
+
dispatch({ type: "SET_OTP_CODE", payload: "" });
|
|
181
|
+
dispatch({ type: "SET_OTP_VERIFIED", payload: false });
|
|
214
182
|
}
|
|
215
|
-
|
|
216
|
-
};
|
|
217
|
-
const
|
|
218
|
-
|
|
219
|
-
};
|
|
220
|
-
const
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
const validateCountryCode = (code) => {
|
|
225
|
-
return /^\+[1-9]\d{0,3}$/.test(code);
|
|
226
|
-
};
|
|
227
|
-
const canProceedFromMergedStep = (() => {
|
|
228
|
-
if (addresses.length === 0 || doctors.length === 0)
|
|
229
|
-
return false;
|
|
230
|
-
if (addresses.length > 1 && !selectedAddress)
|
|
231
|
-
return false;
|
|
232
|
-
if (doctors.length > 1 && !selectedDoctor)
|
|
233
|
-
return false;
|
|
234
|
-
return true;
|
|
235
|
-
})();
|
|
236
|
-
const sendOtp = async () => {
|
|
237
|
-
setError(null);
|
|
238
|
-
if (!countryCode) {
|
|
239
|
-
setError("Please enter country code.");
|
|
183
|
+
dispatch({ type: "SET_STEP", payload: Math.max(0, state.step - 1) });
|
|
184
|
+
}, [state.step]);
|
|
185
|
+
const goToNext = useCallback(() => {
|
|
186
|
+
dispatch({ type: "SET_STEP", payload: Math.min(5, state.step + 1) });
|
|
187
|
+
}, [state.step]);
|
|
188
|
+
const sendOtp = useCallback(async () => {
|
|
189
|
+
dispatch({ type: "SET_ERROR", payload: null });
|
|
190
|
+
if (!state.countryCode) {
|
|
191
|
+
dispatch({ type: "SET_ERROR", payload: "Please enter country code." });
|
|
240
192
|
return;
|
|
241
193
|
}
|
|
242
|
-
if (!validateCountryCode(countryCode)) {
|
|
243
|
-
|
|
194
|
+
if (!validateCountryCode(state.countryCode)) {
|
|
195
|
+
dispatch({
|
|
196
|
+
type: "SET_ERROR",
|
|
197
|
+
payload: "Please enter a valid country code (e.g., +91, +1).",
|
|
198
|
+
});
|
|
244
199
|
return;
|
|
245
200
|
}
|
|
246
|
-
if (!patientPhone) {
|
|
247
|
-
|
|
201
|
+
if (!state.patientPhone) {
|
|
202
|
+
dispatch({ type: "SET_ERROR", payload: "Please enter phone number." });
|
|
248
203
|
return;
|
|
249
204
|
}
|
|
250
|
-
if (!validatePhoneNumber(patientPhone)) {
|
|
251
|
-
|
|
205
|
+
if (!validatePhoneNumber(state.patientPhone)) {
|
|
206
|
+
dispatch({
|
|
207
|
+
type: "SET_ERROR",
|
|
208
|
+
payload: "Please enter a valid phone number (7-15 digits).",
|
|
209
|
+
});
|
|
252
210
|
return;
|
|
253
211
|
}
|
|
254
|
-
|
|
212
|
+
dispatch({ type: "SET_OTP_SENDING", payload: true });
|
|
255
213
|
try {
|
|
256
214
|
await PatientService.sendPhoneVerificationOtp({
|
|
257
|
-
countryCode,
|
|
258
|
-
phoneNumber: patientPhone,
|
|
215
|
+
countryCode: state.countryCode,
|
|
216
|
+
phoneNumber: state.patientPhone,
|
|
259
217
|
});
|
|
260
|
-
|
|
261
|
-
|
|
218
|
+
dispatch({ type: "SET_OTP_SENT", payload: true });
|
|
219
|
+
dispatch({ type: "SET_ERROR", payload: null });
|
|
262
220
|
}
|
|
263
221
|
catch (e) {
|
|
264
222
|
const msg = e.message || "Failed to send OTP";
|
|
265
|
-
|
|
223
|
+
dispatch({ type: "SET_ERROR", payload: msg });
|
|
266
224
|
onError?.(e);
|
|
267
225
|
}
|
|
268
226
|
finally {
|
|
269
|
-
|
|
227
|
+
dispatch({ type: "SET_OTP_SENDING", payload: false });
|
|
270
228
|
}
|
|
271
|
-
};
|
|
272
|
-
const verifyOtp = async () => {
|
|
273
|
-
|
|
274
|
-
if (!countryCode || !patientPhone || !otpCode) {
|
|
275
|
-
|
|
229
|
+
}, [state.countryCode, state.patientPhone, onError]);
|
|
230
|
+
const verifyOtp = useCallback(async () => {
|
|
231
|
+
dispatch({ type: "SET_ERROR", payload: null });
|
|
232
|
+
if (!state.countryCode || !state.patientPhone || !state.otpCode) {
|
|
233
|
+
dispatch({
|
|
234
|
+
type: "SET_ERROR",
|
|
235
|
+
payload: "Please enter all required fields.",
|
|
236
|
+
});
|
|
276
237
|
return;
|
|
277
238
|
}
|
|
278
|
-
if (otpCode.length !== 6) {
|
|
279
|
-
|
|
239
|
+
if (state.otpCode.length !== 6) {
|
|
240
|
+
dispatch({
|
|
241
|
+
type: "SET_ERROR",
|
|
242
|
+
payload: "Please enter a 6-digit OTP code.",
|
|
243
|
+
});
|
|
280
244
|
return;
|
|
281
245
|
}
|
|
282
|
-
|
|
246
|
+
dispatch({ type: "SET_OTP_VERIFYING", payload: true });
|
|
283
247
|
try {
|
|
284
248
|
await PatientService.verifyPhoneVerificationOtp({
|
|
285
|
-
countryCode,
|
|
286
|
-
phoneNumber: patientPhone,
|
|
287
|
-
otpCode,
|
|
249
|
+
countryCode: state.countryCode,
|
|
250
|
+
phoneNumber: state.patientPhone,
|
|
251
|
+
otpCode: state.otpCode,
|
|
288
252
|
});
|
|
289
|
-
|
|
290
|
-
|
|
253
|
+
dispatch({ type: "SET_OTP_VERIFIED", payload: true });
|
|
254
|
+
dispatch({ type: "SET_ERROR", payload: null });
|
|
291
255
|
}
|
|
292
256
|
catch (e) {
|
|
293
257
|
const msg = e.message || "Invalid OTP code";
|
|
294
|
-
|
|
258
|
+
dispatch({ type: "SET_ERROR", payload: msg });
|
|
295
259
|
onError?.(e);
|
|
296
260
|
}
|
|
297
261
|
finally {
|
|
298
|
-
|
|
262
|
+
dispatch({ type: "SET_OTP_VERIFYING", payload: false });
|
|
299
263
|
}
|
|
300
|
-
};
|
|
301
|
-
const submitAppointment = async () => {
|
|
302
|
-
|
|
303
|
-
if (!selectedDoctor ||
|
|
304
|
-
!selectedSlot ||
|
|
305
|
-
!workspaceId ||
|
|
306
|
-
!selectedAddress
|
|
307
|
-
|
|
308
|
-
|
|
264
|
+
}, [state.countryCode, state.patientPhone, state.otpCode, onError]);
|
|
265
|
+
const submitAppointment = useCallback(async () => {
|
|
266
|
+
dispatch({ type: "SET_ERROR", payload: null });
|
|
267
|
+
if (!state.selectedDoctor ||
|
|
268
|
+
!state.selectedSlot ||
|
|
269
|
+
!state.workspaceId ||
|
|
270
|
+
!state.selectedAddress) {
|
|
271
|
+
dispatch({
|
|
272
|
+
type: "SET_ERROR",
|
|
273
|
+
payload: "Please ensure all required fields are complete.",
|
|
274
|
+
});
|
|
309
275
|
return;
|
|
310
276
|
}
|
|
311
|
-
if (!otpVerified) {
|
|
312
|
-
|
|
277
|
+
if (!state.otpVerified) {
|
|
278
|
+
dispatch({
|
|
279
|
+
type: "SET_ERROR",
|
|
280
|
+
payload: "Please verify your phone number first.",
|
|
281
|
+
});
|
|
313
282
|
return;
|
|
314
283
|
}
|
|
315
|
-
|
|
284
|
+
if (!state.patientName ||
|
|
285
|
+
!state.patientAge ||
|
|
286
|
+
!state.patientEmail ||
|
|
287
|
+
!state.patientGender ||
|
|
288
|
+
!state.bloodGroup) {
|
|
289
|
+
dispatch({
|
|
290
|
+
type: "SET_ERROR",
|
|
291
|
+
payload: "Please fill in all patient details.",
|
|
292
|
+
});
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
if (!state.patientAddress ||
|
|
296
|
+
!state.patientCity ||
|
|
297
|
+
!state.patientState ||
|
|
298
|
+
!state.patientCountry ||
|
|
299
|
+
!state.patientZipcode) {
|
|
300
|
+
dispatch({
|
|
301
|
+
type: "SET_ERROR",
|
|
302
|
+
payload: "Please fill in all address fields.",
|
|
303
|
+
});
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
dispatch({ type: "SET_LOADING", payload: true });
|
|
316
307
|
try {
|
|
317
|
-
const
|
|
318
|
-
const
|
|
319
|
-
const lastName = nameParts.slice(1).join(" ") || "";
|
|
320
|
-
const startDate = new Date(selectedSlot.start);
|
|
321
|
-
const endDate = new Date(selectedSlot.end);
|
|
322
|
-
const appointmentDate = startDate.toISOString().split("T")[0];
|
|
323
|
-
const formatTime = (date) => {
|
|
324
|
-
const hours = String(date.getHours()).padStart(2, "0");
|
|
325
|
-
const minutes = String(date.getMinutes()).padStart(2, "0");
|
|
326
|
-
return `${hours}:${minutes}`;
|
|
327
|
-
};
|
|
328
|
-
const fromDateTimeTs = formatTime(startDate);
|
|
329
|
-
const toDateTimeTs = formatTime(endDate);
|
|
330
|
-
const patientAddressPayload = {
|
|
331
|
-
addressLine1: patientAddress,
|
|
332
|
-
};
|
|
308
|
+
const { firstName, lastName } = parsePatientName(state.patientName);
|
|
309
|
+
const appointmentDate = formatDateToISO(state.selectedDate);
|
|
333
310
|
await AppointmentService.createAppointment({
|
|
334
|
-
workspaceId: workspaceId,
|
|
335
|
-
workspaceAddressId: selectedAddress,
|
|
336
|
-
doctorId: selectedDoctor,
|
|
337
|
-
mode:
|
|
338
|
-
appointmentDate,
|
|
339
|
-
fromDateTimeTs,
|
|
340
|
-
toDateTimeTs,
|
|
341
|
-
consultationCharge: consultationCharge || "0",
|
|
311
|
+
workspaceId: state.workspaceId,
|
|
312
|
+
workspaceAddressId: state.selectedAddress,
|
|
313
|
+
doctorId: state.selectedDoctor,
|
|
314
|
+
mode: state.consultationMode,
|
|
315
|
+
appointmentDate: appointmentDate,
|
|
316
|
+
fromDateTimeTs: state.selectedSlot.start,
|
|
317
|
+
toDateTimeTs: state.selectedSlot.end,
|
|
318
|
+
consultationCharge: state.consultationCharge || "0",
|
|
342
319
|
type: "CONSULTATION",
|
|
343
320
|
source: "SDK_POWERED_WEBSITE",
|
|
344
321
|
patientPayload: {
|
|
345
322
|
firstName,
|
|
346
323
|
lastName,
|
|
347
|
-
email: patientEmail
|
|
348
|
-
countryCode,
|
|
349
|
-
phoneNumber: patientPhone,
|
|
350
|
-
age:
|
|
351
|
-
gender: patientGender
|
|
352
|
-
|
|
353
|
-
|
|
324
|
+
email: state.patientEmail,
|
|
325
|
+
countryCode: state.countryCode,
|
|
326
|
+
phoneNumber: state.patientPhone,
|
|
327
|
+
age: parseInt(state.patientAge, 10),
|
|
328
|
+
gender: state.patientGender.toUpperCase(),
|
|
329
|
+
},
|
|
330
|
+
patientAddress: {
|
|
331
|
+
addressLine1: state.patientAddress,
|
|
332
|
+
city: state.patientCity,
|
|
333
|
+
state: state.patientState,
|
|
334
|
+
country: state.patientCountry,
|
|
335
|
+
zipcode: state.patientZipcode,
|
|
336
|
+
landmark: state.patientLandmark || undefined,
|
|
354
337
|
},
|
|
355
|
-
patientAddress: patientAddressPayload,
|
|
356
338
|
});
|
|
357
|
-
|
|
339
|
+
dispatch({ type: "SET_STEP", payload: 5 });
|
|
358
340
|
}
|
|
359
341
|
catch (e) {
|
|
360
342
|
const msg = e.message || "Failed to create appointment";
|
|
361
|
-
|
|
343
|
+
dispatch({ type: "SET_ERROR", payload: msg });
|
|
362
344
|
onError?.(e);
|
|
363
345
|
}
|
|
364
346
|
finally {
|
|
365
|
-
|
|
347
|
+
dispatch({ type: "SET_LOADING", payload: false });
|
|
366
348
|
}
|
|
367
|
-
};
|
|
368
|
-
const
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
},
|
|
385
|
-
header: {
|
|
386
|
-
display: "flex",
|
|
387
|
-
alignItems: "center",
|
|
388
|
-
justifyContent: "space-between",
|
|
389
|
-
marginBottom: 16,
|
|
390
|
-
},
|
|
391
|
-
title: { margin: 0, fontSize: 20, fontWeight: 600 },
|
|
392
|
-
stepper: {
|
|
393
|
-
display: "flex",
|
|
394
|
-
gap: 8,
|
|
395
|
-
alignItems: "center",
|
|
396
|
-
},
|
|
397
|
-
stepPill: (active = false) => ({
|
|
398
|
-
padding: "6px 10px",
|
|
399
|
-
borderRadius: 999,
|
|
400
|
-
background: active ? "#0b79f7" : "#eef2ff",
|
|
401
|
-
color: active ? "#fff" : "#333",
|
|
402
|
-
fontSize: 12,
|
|
403
|
-
fontWeight: 600,
|
|
404
|
-
}),
|
|
405
|
-
section: { marginTop: 12 },
|
|
406
|
-
label: {
|
|
407
|
-
display: "block",
|
|
408
|
-
fontSize: 13,
|
|
409
|
-
marginBottom: 6,
|
|
410
|
-
color: "#374151",
|
|
411
|
-
},
|
|
412
|
-
input: {
|
|
413
|
-
width: "100%",
|
|
414
|
-
padding: "10px 12px",
|
|
415
|
-
borderRadius: 8,
|
|
416
|
-
border: "1px solid #e6e9ef",
|
|
417
|
-
outline: "none",
|
|
418
|
-
fontSize: 14,
|
|
419
|
-
boxSizing: "border-box",
|
|
420
|
-
},
|
|
421
|
-
select: {
|
|
422
|
-
width: "100%",
|
|
423
|
-
padding: "10px 12px",
|
|
424
|
-
borderRadius: 8,
|
|
425
|
-
border: "1px solid #e6e9ef",
|
|
426
|
-
background: "#fff",
|
|
427
|
-
fontSize: 14,
|
|
428
|
-
},
|
|
429
|
-
actions: {
|
|
430
|
-
display: "flex",
|
|
431
|
-
gap: 8,
|
|
432
|
-
marginTop: 16,
|
|
433
|
-
justifyContent: "flex-end",
|
|
434
|
-
},
|
|
435
|
-
primaryBtn: {
|
|
436
|
-
background: "#0b79f7",
|
|
437
|
-
color: "#fff",
|
|
438
|
-
border: "none",
|
|
439
|
-
padding: "10px 14px",
|
|
440
|
-
borderRadius: 8,
|
|
441
|
-
cursor: "pointer",
|
|
442
|
-
fontWeight: 600,
|
|
443
|
-
},
|
|
444
|
-
secondaryBtn: {
|
|
445
|
-
background: "#fff",
|
|
446
|
-
color: "#0b254a",
|
|
447
|
-
border: "1px solid #e6e9ef",
|
|
448
|
-
padding: "10px 14px",
|
|
449
|
-
borderRadius: 8,
|
|
450
|
-
cursor: "pointer",
|
|
451
|
-
},
|
|
452
|
-
slotGrid: {
|
|
453
|
-
display: "grid",
|
|
454
|
-
gridTemplateColumns: "repeat(auto-fit,minmax(140px,1fr))",
|
|
455
|
-
gap: 12,
|
|
456
|
-
marginTop: 12,
|
|
457
|
-
},
|
|
458
|
-
slotCard: (selected = false) => ({
|
|
459
|
-
padding: 12,
|
|
460
|
-
borderRadius: 10,
|
|
461
|
-
border: selected ? "2px solid #0b79f7" : "1px solid #e6e9ef",
|
|
462
|
-
background: selected
|
|
463
|
-
? "linear-gradient(180deg,#f0f6ff,#e9f2ff)"
|
|
464
|
-
: "#fff",
|
|
465
|
-
cursor: "pointer",
|
|
466
|
-
textAlign: "center",
|
|
467
|
-
}),
|
|
468
|
-
smallMuted: { fontSize: 12, color: "#6b7280" },
|
|
469
|
-
successCard: {
|
|
470
|
-
padding: 20,
|
|
471
|
-
borderRadius: 12,
|
|
472
|
-
background: "linear-gradient(90deg,#ecfdf5,#eff6ff)",
|
|
473
|
-
textAlign: "center",
|
|
474
|
-
},
|
|
475
|
-
};
|
|
476
|
-
return (_jsx("div", { style: styles.container, children: _jsxs("div", { style: styles.card, children: [_jsxs("div", { style: styles.header, children: [_jsx("h2", { style: styles.title, children: "Book Appointment" }), _jsxs("div", { style: styles.stepper, children: [_jsx("div", { style: styles.stepPill(step === 0), children: "1 Address" }), _jsx("div", { style: styles.stepPill(step === 1), children: "2 Date" }), _jsx("div", { style: styles.stepPill(step === 2), children: "3 Slot" }), _jsx("div", { style: styles.stepPill(step === 3), children: "4 Phone" }), _jsx("div", { style: styles.stepPill(step === 4), children: "5 Details" })] })] }), loading && _jsx("div", { style: { marginBottom: 12 }, children: "Loading..." }), error && (_jsx("div", { style: {
|
|
477
|
-
marginBottom: 12,
|
|
478
|
-
color: "#ef4444",
|
|
479
|
-
fontWeight: 600,
|
|
480
|
-
}, children: error })), step === 0 && (_jsxs("div", { style: styles.section, children: [_jsxs("div", { style: {
|
|
481
|
-
display: "grid",
|
|
482
|
-
gridTemplateColumns: "1fr 1fr",
|
|
483
|
-
gap: 16,
|
|
484
|
-
}, children: [_jsxs("div", { children: [_jsx("label", { style: styles.label, children: "Address" }), addresses.length === 0 ? (_jsx("div", { style: styles.smallMuted, children: "No addresses available" })) : addresses.length === 1 ? (_jsx("div", { style: { ...styles.smallMuted, fontWeight: 600 }, children: `${addresses[0].label}` })) : (_jsxs("select", { style: styles.select, value: selectedAddress || "", onChange: (e) => {
|
|
485
|
-
setSelectedAddress(e.target.value || null);
|
|
486
|
-
setError(null);
|
|
487
|
-
}, children: [_jsx("option", { value: "", children: "-- choose address --" }), addresses.map((a) => (_jsx("option", { value: a.id, children: a.label }, a.id)))] }))] }), _jsxs("div", { children: [_jsx("label", { style: styles.label, children: "Doctor" }), doctors.length === 0 ? (_jsx("div", { style: styles.smallMuted, children: "No doctors available" })) : doctors.length === 1 ? (_jsx("div", { style: { ...styles.smallMuted, fontWeight: 600 }, children: `${doctors[0].name} ${doctors[0].specialty ? `• ${doctors[0].specialty}` : ""}` })) : (_jsxs("select", { style: styles.select, value: selectedDoctor || "", onChange: (e) => setSelectedDoctor(e.target.value), children: [_jsx("option", { value: "", children: "-- choose doctor --" }), doctors.map((d) => (_jsxs("option", { value: d.id, children: [d.name, " ", d.specialty ? `(${d.specialty})` : ""] }, d.id)))] }))] })] }), _jsxs("div", { style: styles.actions, children: [_jsx("button", { style: styles.secondaryBtn, onClick: () => {
|
|
488
|
-
}, children: "Cancel" }), _jsx("button", { style: {
|
|
489
|
-
...styles.primaryBtn,
|
|
490
|
-
opacity: canProceedFromMergedStep ? 1 : 0.6,
|
|
491
|
-
}, disabled: !canProceedFromMergedStep, onClick: goToNext, children: "Next" })] })] })), step === 1 && (_jsxs("div", { style: styles.section, children: [_jsx("label", { style: styles.label, children: "Select Date" }), _jsx("input", { style: styles.input, type: "date", value: date, onChange: (e) => setDate(e.target.value) }), _jsxs("div", { style: styles.actions, children: [_jsx("button", { style: styles.secondaryBtn, onClick: goBack, children: "Back" }), _jsx("button", { style: { ...styles.primaryBtn, opacity: date ? 1 : 0.6 }, disabled: !date, onClick: () => setStep(2), children: "Next" })] })] })), step === 2 && (_jsxs("div", { style: styles.section, children: [_jsx("label", { style: styles.label, children: "Choose Time Slot" }), slots.length === 0 ? (_jsx("div", { style: styles.smallMuted, children: "No slots available for selected date" })) : (_jsx("div", { style: styles.slotGrid, children: slots.map((s) => {
|
|
492
|
-
const start = new Date(s.start).toLocaleTimeString([], {
|
|
493
|
-
hour: "2-digit",
|
|
494
|
-
minute: "2-digit",
|
|
495
|
-
});
|
|
496
|
-
const end = new Date(s.end).toLocaleTimeString([], {
|
|
497
|
-
hour: "2-digit",
|
|
498
|
-
minute: "2-digit",
|
|
499
|
-
});
|
|
500
|
-
const selected = selectedSlot?.start === s.start &&
|
|
501
|
-
selectedSlot?.end === s.end;
|
|
502
|
-
return (_jsxs("div", { style: styles.slotCard(selected), onClick: () => setSelectedSlot(s), children: [_jsx("div", { style: { fontWeight: 700 }, children: `${start} — ${end}` }), _jsx("div", { style: styles.smallMuted })] }, s.id ?? `${s.start}-${s.end}`));
|
|
503
|
-
}) })), _jsxs("div", { style: styles.actions, children: [_jsx("button", { style: styles.secondaryBtn, onClick: goBack, children: "Back" }), _jsx("button", { style: {
|
|
504
|
-
...styles.primaryBtn,
|
|
505
|
-
opacity: selectedSlot ? 1 : 0.6,
|
|
506
|
-
}, disabled: !selectedSlot, onClick: () => setStep(3), children: "Next" })] })] })), step === 3 && (_jsxs("div", { style: styles.section, children: [_jsx("label", { style: styles.label, children: "Country Code" }), _jsx("input", { style: styles.input, placeholder: "+91", value: countryCode, onChange: (e) => {
|
|
507
|
-
let value = e.target.value;
|
|
508
|
-
if (value && !value.startsWith("+")) {
|
|
509
|
-
value = "+" + value;
|
|
510
|
-
}
|
|
511
|
-
value = value.replace(/[^\d+]/g, "");
|
|
512
|
-
setCountryCode(value);
|
|
513
|
-
} }), countryCode && !validateCountryCode(countryCode) && (_jsx("div", { style: { marginTop: 4, fontSize: 12, color: "#ef4444" }, children: "Please enter a valid country code (e.g., +91, +1)" })), _jsx("label", { style: { ...styles.label, marginTop: 12 }, children: "Phone Number" }), _jsx("input", { style: styles.input, type: "tel", placeholder: "9311840587", value: patientPhone, onChange: (e) => {
|
|
514
|
-
const value = e.target.value.replace(/\D/g, "");
|
|
515
|
-
setPatientPhone(value);
|
|
516
|
-
}, disabled: otpSent, maxLength: 15 }), patientPhone && !validatePhoneNumber(patientPhone) && (_jsx("div", { style: { marginTop: 4, fontSize: 12, color: "#ef4444" }, children: "Phone number should be 7-15 digits" })), !otpSent ? (_jsxs("div", { style: styles.actions, children: [_jsx("button", { style: styles.secondaryBtn, onClick: goBack, children: "Back" }), _jsx("button", { style: {
|
|
517
|
-
...styles.primaryBtn,
|
|
518
|
-
opacity: countryCode && patientPhone ? 1 : 0.6,
|
|
519
|
-
}, disabled: !countryCode ||
|
|
520
|
-
!patientPhone ||
|
|
521
|
-
otpSending ||
|
|
522
|
-
!validateCountryCode(countryCode) ||
|
|
523
|
-
!validatePhoneNumber(patientPhone), onClick: sendOtp, children: otpSending ? "Sending..." : "Send OTP" })] })) : (_jsx(_Fragment, { children: otpVerified ? (_jsxs(_Fragment, { children: [_jsx("div", { style: {
|
|
524
|
-
marginTop: 12,
|
|
525
|
-
padding: 12,
|
|
526
|
-
borderRadius: 8,
|
|
527
|
-
background: "#ecfdf5",
|
|
528
|
-
color: "#10b981",
|
|
529
|
-
fontWeight: 600,
|
|
530
|
-
}, children: "\u2713 Phone verified successfully" }), _jsxs("div", { style: styles.actions, children: [_jsx("button", { style: styles.secondaryBtn, onClick: goBack, children: "Back" }), _jsx("button", { style: styles.primaryBtn, onClick: goToNext, children: "Continue to Details" })] })] })) : (_jsxs(_Fragment, { children: [_jsx("label", { style: { ...styles.label, marginTop: 12 }, children: "Enter OTP" }), _jsx("input", { style: styles.input, type: "text", placeholder: "Enter 6-digit OTP", value: otpCode, onChange: (e) => setOtpCode(e.target.value), maxLength: 6 }), _jsxs("div", { style: {
|
|
531
|
-
marginTop: 8,
|
|
532
|
-
fontSize: 12,
|
|
533
|
-
color: "#6b7280",
|
|
534
|
-
}, children: ["OTP sent to ", countryCode, " ", patientPhone] }), _jsxs("div", { style: styles.actions, children: [_jsx("button", { style: styles.secondaryBtn, onClick: () => {
|
|
535
|
-
setOtpSent(false);
|
|
536
|
-
setOtpCode("");
|
|
537
|
-
setOtpVerified(false);
|
|
538
|
-
}, children: "Change Number" }), _jsx("button", { style: {
|
|
539
|
-
...styles.primaryBtn,
|
|
540
|
-
opacity: otpCode.length === 6 ? 1 : 0.6,
|
|
541
|
-
}, disabled: otpCode.length !== 6 || otpVerifying, onClick: verifyOtp, children: otpVerifying ? "Verifying..." : "Verify OTP" })] })] })) }))] })), step === 4 && (_jsxs("div", { style: styles.section, children: [_jsx("label", { style: styles.label, children: "Patient Name" }), _jsx("input", { style: styles.input, placeholder: "Full name", value: patientName, onChange: (e) => setPatientName(e.target.value) }), _jsx("label", { style: { ...styles.label, marginTop: 12 }, children: "Age" }), _jsx("input", { style: styles.input, type: "number", placeholder: "Age", value: patientAge, onChange: (e) => setPatientAge(e.target.value) }), _jsx("label", { style: { ...styles.label, marginTop: 12 }, children: "Email (Optional)" }), _jsx("input", { style: styles.input, type: "email", placeholder: "patient@example.com", value: patientEmail, onChange: (e) => setPatientEmail(e.target.value) }), _jsx("label", { style: { ...styles.label, marginTop: 12 }, children: "Gender (Optional)" }), _jsxs("select", { style: styles.select, value: patientGender, onChange: (e) => setPatientGender(e.target.value), children: [_jsx("option", { value: "", children: "-- Select Gender --" }), _jsx("option", { value: "MALE", children: "Male" }), _jsx("option", { value: "FEMALE", children: "Female" }), _jsx("option", { value: "OTHER", children: "Other" })] }), _jsx("label", { style: { ...styles.label, marginTop: 12 }, children: "Address" }), _jsx("textarea", { style: { ...styles.input, minHeight: 80, resize: "vertical" }, placeholder: "Enter your address", value: patientAddress, onChange: (e) => setPatientAddress(e.target.value) }), _jsx("label", { style: { ...styles.label, marginTop: 12 }, children: "Problem Facing" }), _jsx("textarea", { style: { ...styles.input, minHeight: 80, resize: "vertical" }, placeholder: "Describe the problem you're facing", value: problemFacing, onChange: (e) => setProblemFacing(e.target.value) }), _jsxs("div", { style: styles.actions, children: [_jsx("button", { style: styles.secondaryBtn, onClick: goBack, children: "Back" }), _jsx("button", { style: {
|
|
542
|
-
...styles.primaryBtn,
|
|
543
|
-
opacity: patientName && otpVerified ? 1 : 0.6,
|
|
544
|
-
}, disabled: !patientName || !otpVerified || loading, onClick: submitAppointment, children: loading ? "Booking..." : "Book Appointment" })] })] })), step === 5 && (_jsxs("div", { style: { ...styles.section, textAlign: "center" }, children: [_jsxs("div", { style: styles.successCard, children: [_jsx("div", { style: {
|
|
545
|
-
fontSize: 32,
|
|
546
|
-
color: "#10b981",
|
|
547
|
-
marginBottom: 8,
|
|
548
|
-
}, children: "\u2713" }), _jsx("div", { style: { fontWeight: 700, marginBottom: 6 }, children: "Appointment Confirmed" }), _jsxs("div", { style: styles.smallMuted, children: ["Thank you, ", patientName || "Patient", ". Your appointment is confirmed."] })] }), _jsx("div", { style: {
|
|
549
|
-
marginTop: 14,
|
|
550
|
-
display: "flex",
|
|
551
|
-
justifyContent: "center",
|
|
552
|
-
}, children: _jsx("button", { style: { ...styles.primaryBtn, width: 160 }, onClick: () => {
|
|
553
|
-
setStep(0);
|
|
554
|
-
setSelectedSlot(null);
|
|
555
|
-
setSelectedDoctor(null);
|
|
556
|
-
setDate("");
|
|
557
|
-
setPatientName("");
|
|
558
|
-
setPatientAge("");
|
|
559
|
-
setPatientAddress("");
|
|
560
|
-
setPatientEmail("");
|
|
561
|
-
setPatientGender("");
|
|
562
|
-
setProblemFacing("");
|
|
563
|
-
setConsultationCharge("");
|
|
564
|
-
setCountryCode("+91");
|
|
565
|
-
setPatientPhone("");
|
|
566
|
-
setOtpCode("");
|
|
567
|
-
setOtpSent(false);
|
|
568
|
-
setOtpVerified(false);
|
|
569
|
-
}, children: "Book Another" }) })] }))] }) }));
|
|
349
|
+
}, [state, onError]);
|
|
350
|
+
const resetForm = useCallback(() => {
|
|
351
|
+
dispatch({ type: "RESET_FORM" });
|
|
352
|
+
}, []);
|
|
353
|
+
const handleDoctorSelect = useCallback((addrId, docId) => {
|
|
354
|
+
dispatch({ type: "SET_SELECTED_ADDRESS", payload: addrId });
|
|
355
|
+
dispatch({ type: "SET_SELECTED_DOCTOR", payload: docId });
|
|
356
|
+
dispatch({ type: "SET_STEP", payload: 1 });
|
|
357
|
+
}, []);
|
|
358
|
+
const handleDateTimeModalContinue = useCallback((mode, date, slot, charge) => {
|
|
359
|
+
dispatch({ type: "SET_CONSULTATION_MODE", payload: mode });
|
|
360
|
+
dispatch({ type: "SET_SELECTED_DATE", payload: date });
|
|
361
|
+
dispatch({ type: "SET_SELECTED_SLOT", payload: slot });
|
|
362
|
+
dispatch({ type: "SET_CONSULTATION_CHARGE", payload: charge });
|
|
363
|
+
dispatch({ type: "SET_STEP", payload: 3 });
|
|
364
|
+
}, []);
|
|
365
|
+
return (_jsx("div", { style: CONTAINER_STYLES.container, children: _jsxs("div", { style: CONTAINER_STYLES.card, children: [_jsx("div", { style: CONTAINER_STYLES.header, children: _jsx("h2", { style: CONTAINER_STYLES.title, children: "Book Appointment" }) }), state.loading && _jsx("div", { style: { marginBottom: 12 }, children: "Loading..." }), state.error && (_jsx("div", { style: CONTAINER_STYLES.errorMessage, children: state.error })), state.step === 0 && (_jsx(DoctorSelectModal, { onCancel: () => dispatch({ type: "SET_STEP", payload: 0 }), onContinue: handleDoctorSelect })), state.step === 1 && (_jsx(AppointmentDateTimeModal, { onlineFee: 500, offlineFee: 300, slots: state.slots, onCancel: goBack, onDateChange: handleDateChange, onContinue: handleDateTimeModalContinue })), state.step === 3 && (_jsx(PhoneVerificationStep, { state: state, dispatch: dispatch, onSendOtp: sendOtp, onVerifyOtp: verifyOtp, onBack: goBack, onContinue: goToNext })), state.step === 4 && (_jsx(PatientDetailsStep, { state: state, dispatch: dispatch, onBack: goBack, onSubmit: submitAppointment })), state.step === 5 && _jsx(SuccessStep, { state: state, onReset: resetForm })] }) }));
|
|
570
366
|
};
|