medos-sdk 1.0.2 → 1.1.0

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