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.
- package/README.md +39 -0
- package/dist/client/MedosClient.d.ts +3 -5
- package/dist/client/MedosClient.js +4 -4
- package/dist/components/AppointmentCalender.d.ts +1 -4
- package/dist/components/AppointmentCalender.js +323 -530
- package/dist/components/AppointmentDateTimeModal.d.ts +14 -0
- package/dist/components/AppointmentDateTimeModal.js +220 -0
- package/dist/components/ConfigurableCard.d.ts +12 -0
- package/dist/components/ConfigurableCard.js +29 -0
- package/dist/components/ContactInformationStep.d.ts +13 -0
- package/dist/components/ContactInformationStep.js +14 -0
- package/dist/components/ContactPreferenceStep.d.ts +9 -0
- package/dist/components/ContactPreferenceStep.js +16 -0
- package/dist/components/DoctorSelectModal.d.ts +7 -0
- package/dist/components/DoctorSelectModal.js +93 -0
- package/dist/components/EnquiryForm.d.ts +7 -0
- package/dist/components/EnquiryForm.js +212 -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/InquiryDetailsStep.d.ts +10 -0
- package/dist/components/InquiryDetailsStep.js +15 -0
- package/dist/components/PatientDetailsStep.d.ts +3 -0
- package/dist/components/PatientDetailsStep.js +84 -0
- package/dist/components/PhoneVerificationStep.d.ts +3 -0
- package/dist/components/PhoneVerificationStep.js +49 -0
- package/dist/components/SuccessStep.d.ts +5 -0
- package/dist/components/SuccessStep.js +9 -0
- package/dist/components/custom-calendar.d.ts +5 -0
- package/dist/components/custom-calendar.js +171 -0
- package/dist/components/styles.d.ts +6 -0
- package/dist/components/styles.js +257 -0
- package/dist/components/theme-styles.d.ts +12 -0
- package/dist/components/theme-styles.js +319 -0
- package/dist/components/types.d.ts +181 -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 +4 -0
- package/dist/core/index.js +4 -0
- package/dist/core/theme/index.d.ts +3 -0
- package/dist/core/theme/index.js +3 -0
- package/dist/core/theme/themes.d.ts +8 -0
- package/dist/core/theme/themes.js +178 -0
- package/dist/core/theme/types.d.ts +106 -0
- package/dist/core/theme/types.js +1 -0
- package/dist/core/theme/utils.d.ts +8 -0
- package/dist/core/theme/utils.js +135 -0
- package/dist/enquiry-form/index.d.ts +4 -0
- package/dist/enquiry-form/index.js +4 -0
- package/dist/enquiry-form/provider.d.ts +3 -0
- package/dist/enquiry-form/provider.js +9 -0
- package/dist/enquiry-form/serialization.d.ts +4 -0
- package/dist/enquiry-form/serialization.js +57 -0
- package/dist/enquiry-form/types.d.ts +38 -0
- package/dist/enquiry-form/types.js +1 -0
- package/dist/enquiry-form/validation.d.ts +6 -0
- package/dist/enquiry-form/validation.js +21 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +7 -0
- package/dist/lib/templateUtils.d.ts +3 -0
- package/dist/lib/templateUtils.js +28 -0
- package/dist/react/ThemeProvider.d.ts +18 -0
- package/dist/react/ThemeProvider.js +45 -0
- package/dist/react/hooks/useTheme.d.ts +1 -0
- package/dist/react/hooks/useTheme.js +1 -0
- package/dist/react/index.d.ts +5 -0
- package/dist/react/index.js +3 -0
- package/dist/services/AppointmentService.d.ts +4 -5
- package/dist/services/AppointmentService.js +12 -10
- package/dist/services/EnquiryService.d.ts +5 -0
- package/dist/services/EnquiryService.js +30 -0
- package/dist/templates/registry.d.ts +12 -0
- package/dist/templates/registry.js +58 -0
- package/dist/vanilla/AppointmentCalendarWidget.d.ts +2 -34
- package/dist/vanilla/AppointmentCalendarWidget.js +264 -275
- package/dist/vanilla/EnquiryFormWidget.d.ts +35 -0
- package/dist/vanilla/EnquiryFormWidget.js +425 -0
- package/dist/vanilla/client/MedosClient.d.ts +3 -5
- package/dist/vanilla/components/AppointmentCalender.d.ts +1 -4
- package/dist/vanilla/components/AppointmentDateTimeModal.d.ts +14 -0
- package/dist/vanilla/components/ConfigurableCard.d.ts +12 -0
- package/dist/vanilla/components/ContactInformationStep.d.ts +13 -0
- package/dist/vanilla/components/ContactPreferenceStep.d.ts +9 -0
- package/dist/vanilla/components/DoctorSelectModal.d.ts +7 -0
- package/dist/vanilla/components/EnquiryForm.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/InquiryDetailsStep.d.ts +10 -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 +5 -0
- package/dist/vanilla/components/custom-calendar.d.ts +5 -0
- package/dist/vanilla/components/styles.d.ts +6 -0
- package/dist/vanilla/components/theme-styles.d.ts +12 -0
- package/dist/vanilla/components/types.d.ts +181 -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 +4 -0
- package/dist/vanilla/core/theme/index.d.ts +3 -0
- package/dist/vanilla/core/theme/themes.d.ts +8 -0
- package/dist/vanilla/core/theme/types.d.ts +106 -0
- package/dist/vanilla/core/theme/utils.d.ts +8 -0
- package/dist/vanilla/enquiry-form/index.d.ts +4 -0
- package/dist/vanilla/enquiry-form/provider.d.ts +3 -0
- package/dist/vanilla/enquiry-form/serialization.d.ts +4 -0
- package/dist/vanilla/enquiry-form/types.d.ts +38 -0
- package/dist/vanilla/enquiry-form/validation.d.ts +6 -0
- package/dist/vanilla/enquiry-widget.js +4650 -0
- package/dist/vanilla/index.d.ts +9 -0
- package/dist/vanilla/index.js +3 -1
- package/dist/vanilla/lib/templateUtils.d.ts +3 -0
- package/dist/vanilla/react/ThemeProvider.d.ts +18 -0
- package/dist/vanilla/react/hooks/useTheme.d.ts +1 -0
- package/dist/vanilla/react/index.d.ts +5 -0
- package/dist/vanilla/services/AppointmentService.d.ts +4 -5
- package/dist/vanilla/services/EnquiryService.d.ts +5 -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/theme-injector.d.ts +6 -0
- package/dist/vanilla/theme-injector.js +44 -0
- package/dist/vanilla/vanilla/AppointmentCalendarWidget.d.ts +2 -34
- package/dist/vanilla/vanilla/EnquiryFormWidget.d.ts +35 -0
- package/dist/vanilla/vanilla/index.d.ts +3 -1
- package/dist/vanilla/vanilla/theme-injector.d.ts +6 -0
- package/dist/vanilla/vanilla/widget.d.ts +6 -1
- package/dist/vanilla/widget.css +173 -0
- package/dist/vanilla/widget.d.ts +6 -1
- package/dist/vanilla/widget.js +813 -288
- package/package.json +9 -4
|
@@ -1,40 +1,13 @@
|
|
|
1
|
-
import { AppointmentService } from "../services/AppointmentService";
|
|
1
|
+
import { AppointmentService, } from "../services/AppointmentService";
|
|
2
2
|
import { PatientService } from "../services/PatientService";
|
|
3
3
|
import { MedosClient } from "../client/MedosClient";
|
|
4
|
+
import { INITIAL_STATE, } from "../components/types";
|
|
5
|
+
import { validatePhoneNumber, validateCountryCode, } from "../components/validation";
|
|
6
|
+
import { formatDateToISO, parsePatientName } from "../components/utils";
|
|
4
7
|
class AppointmentCalendarWidget {
|
|
5
8
|
constructor(container, options) {
|
|
6
9
|
this.mounted = true;
|
|
7
|
-
this.step = 0;
|
|
8
|
-
this.addresses = [];
|
|
9
|
-
this.addressDoctorsMap = {};
|
|
10
|
-
this.selectedAddress = null;
|
|
11
|
-
this.workspaceId = null;
|
|
12
10
|
this.doctors = [];
|
|
13
|
-
this.selectedDoctor = null;
|
|
14
|
-
this.date = "";
|
|
15
|
-
this.slots = [];
|
|
16
|
-
this.selectedSlot = null;
|
|
17
|
-
this.loading = false;
|
|
18
|
-
this.error = null;
|
|
19
|
-
this.patientName = "";
|
|
20
|
-
this.patientAge = "";
|
|
21
|
-
this.patientAddress = "";
|
|
22
|
-
this.patientCity = "";
|
|
23
|
-
this.patientState = "";
|
|
24
|
-
this.patientCountry = "";
|
|
25
|
-
this.patientZipcode = "";
|
|
26
|
-
this.patientLandmark = "";
|
|
27
|
-
this.patientEmail = "";
|
|
28
|
-
this.patientGender = "";
|
|
29
|
-
this.problemFacing = "";
|
|
30
|
-
this.consultationCharge = "";
|
|
31
|
-
this.countryCode = "+91";
|
|
32
|
-
this.patientPhone = "";
|
|
33
|
-
this.otpCode = "";
|
|
34
|
-
this.otpSent = false;
|
|
35
|
-
this.otpVerified = false;
|
|
36
|
-
this.otpSending = false;
|
|
37
|
-
this.otpVerifying = false;
|
|
38
11
|
if (typeof container === "string") {
|
|
39
12
|
const el = document.getElementById(container);
|
|
40
13
|
if (!el) {
|
|
@@ -46,19 +19,18 @@ class AppointmentCalendarWidget {
|
|
|
46
19
|
this.container = container;
|
|
47
20
|
}
|
|
48
21
|
this.options = options;
|
|
22
|
+
this.state = { ...INITIAL_STATE };
|
|
49
23
|
this.init();
|
|
50
24
|
}
|
|
51
25
|
async init() {
|
|
52
26
|
if (this.options.apiKey) {
|
|
53
27
|
await MedosClient.init({
|
|
54
28
|
apiKey: this.options.apiKey,
|
|
55
|
-
baseURL: this.options.baseURL,
|
|
56
29
|
});
|
|
57
30
|
}
|
|
58
31
|
else if (this.options.sessionToken) {
|
|
59
32
|
await MedosClient.initWithSession({
|
|
60
33
|
sessionToken: this.options.sessionToken,
|
|
61
|
-
baseURL: this.options.baseURL,
|
|
62
34
|
});
|
|
63
35
|
}
|
|
64
36
|
else {
|
|
@@ -68,19 +40,18 @@ class AppointmentCalendarWidget {
|
|
|
68
40
|
this.render();
|
|
69
41
|
}
|
|
70
42
|
async loadAddresses() {
|
|
71
|
-
this.
|
|
72
|
-
this.setError(null);
|
|
43
|
+
this.setState({ loading: true, error: null });
|
|
73
44
|
try {
|
|
74
45
|
const addrResp = await AppointmentService.getAddresses();
|
|
75
46
|
if (addrResp && Array.isArray(addrResp.addresses)) {
|
|
76
47
|
const fetchedAddresses = addrResp.addresses;
|
|
77
48
|
if (addrResp.workspaceId) {
|
|
78
|
-
this.workspaceId = addrResp.workspaceId;
|
|
49
|
+
this.state.workspaceId = addrResp.workspaceId;
|
|
79
50
|
}
|
|
80
51
|
if (fetchedAddresses.length > 0) {
|
|
81
52
|
const addrMap = {};
|
|
82
53
|
const mappedAddrs = fetchedAddresses.map((a, idx) => {
|
|
83
|
-
const id =
|
|
54
|
+
const id = Number(a.id ?? idx);
|
|
84
55
|
const label = a.completeAddress ?? a.label ?? a.address ?? `Address ${idx + 1}`;
|
|
85
56
|
const docs = Array.isArray(a.doctors)
|
|
86
57
|
? a.doctors
|
|
@@ -88,233 +59,242 @@ class AppointmentCalendarWidget {
|
|
|
88
59
|
addrMap[id] = docs || [];
|
|
89
60
|
return { id, label };
|
|
90
61
|
});
|
|
91
|
-
this.addresses = mappedAddrs;
|
|
92
|
-
this.addressDoctorsMap = addrMap;
|
|
62
|
+
this.state.addresses = mappedAddrs;
|
|
63
|
+
this.state.addressDoctorsMap = addrMap;
|
|
93
64
|
const anyDoctorsExist = Object.values(addrMap).some((arr) => Array.isArray(arr) && arr.length > 0);
|
|
94
65
|
if (mappedAddrs.length === 1) {
|
|
95
66
|
const only = mappedAddrs[0];
|
|
96
|
-
this.selectedAddress = only.id;
|
|
67
|
+
this.state.selectedAddress = only.id;
|
|
97
68
|
const docsForAddr = addrMap[only.id] || [];
|
|
98
69
|
if (docsForAddr.length > 0) {
|
|
99
70
|
this.doctors = docsForAddr;
|
|
100
71
|
if (docsForAddr.length === 1) {
|
|
101
|
-
this.selectedDoctor = docsForAddr[0].id;
|
|
102
|
-
this.step = 1;
|
|
72
|
+
this.state.selectedDoctor = docsForAddr[0].id;
|
|
73
|
+
this.state.step = 1;
|
|
103
74
|
}
|
|
104
75
|
else {
|
|
105
|
-
this.step = 0;
|
|
76
|
+
this.state.step = 0;
|
|
106
77
|
}
|
|
107
78
|
}
|
|
108
79
|
else {
|
|
109
80
|
if (anyDoctorsExist) {
|
|
110
|
-
this.
|
|
81
|
+
this.setState({
|
|
82
|
+
error: "No doctors at this address. Please choose a different address.",
|
|
83
|
+
});
|
|
111
84
|
this.doctors = [];
|
|
112
|
-
this.step = 0;
|
|
85
|
+
this.state.step = 0;
|
|
113
86
|
}
|
|
114
87
|
else {
|
|
115
|
-
this.
|
|
88
|
+
this.setState({
|
|
89
|
+
error: "No doctors available for the selected location(s).",
|
|
90
|
+
});
|
|
116
91
|
this.doctors = [];
|
|
117
|
-
this.step = 0;
|
|
92
|
+
this.state.step = 0;
|
|
118
93
|
}
|
|
119
94
|
}
|
|
120
95
|
}
|
|
121
96
|
else {
|
|
122
|
-
this.step = 0;
|
|
97
|
+
this.state.step = 0;
|
|
123
98
|
}
|
|
124
99
|
}
|
|
125
100
|
else {
|
|
126
|
-
this.
|
|
127
|
-
this.addresses = [];
|
|
128
|
-
this.addressDoctorsMap = {};
|
|
101
|
+
this.setState({ error: "No addresses or doctors available." });
|
|
102
|
+
this.state.addresses = [];
|
|
103
|
+
this.state.addressDoctorsMap = {};
|
|
129
104
|
this.doctors = [];
|
|
130
|
-
this.step = 0;
|
|
105
|
+
this.state.step = 0;
|
|
131
106
|
}
|
|
132
107
|
}
|
|
133
108
|
}
|
|
134
109
|
catch (e) {
|
|
135
110
|
const msg = e.message || "Failed to load addresses";
|
|
136
|
-
this.
|
|
111
|
+
this.setState({ error: msg });
|
|
137
112
|
this.options.onError?.(e);
|
|
138
113
|
}
|
|
139
114
|
finally {
|
|
140
|
-
this.
|
|
115
|
+
this.setState({ loading: false });
|
|
141
116
|
}
|
|
142
117
|
}
|
|
143
118
|
async handleAddressChange(addressId) {
|
|
144
|
-
this.selectedAddress = addressId;
|
|
119
|
+
this.state.selectedAddress = addressId;
|
|
145
120
|
if (!addressId) {
|
|
146
121
|
this.doctors = [];
|
|
147
|
-
this.selectedDoctor = null;
|
|
122
|
+
this.state.selectedDoctor = null;
|
|
148
123
|
this.render();
|
|
149
124
|
return;
|
|
150
125
|
}
|
|
151
|
-
this.
|
|
152
|
-
this.setError(null);
|
|
126
|
+
this.setState({ loading: true, error: null });
|
|
153
127
|
try {
|
|
154
|
-
const docsForAddr = this.addressDoctorsMap[addressId] ?? [];
|
|
128
|
+
const docsForAddr = this.state.addressDoctorsMap[addressId] ?? [];
|
|
155
129
|
if (docsForAddr.length > 0) {
|
|
156
130
|
this.doctors = docsForAddr;
|
|
157
131
|
if (docsForAddr.length === 1) {
|
|
158
|
-
this.selectedDoctor = docsForAddr[0].id;
|
|
159
|
-
this.step = 1;
|
|
132
|
+
this.state.selectedDoctor = docsForAddr[0].id;
|
|
133
|
+
this.state.step = 1;
|
|
160
134
|
}
|
|
161
135
|
else {
|
|
162
|
-
this.selectedDoctor = null;
|
|
136
|
+
this.state.selectedDoctor = null;
|
|
163
137
|
}
|
|
164
138
|
}
|
|
165
139
|
else {
|
|
166
|
-
const otherHasDoctors = Object.entries(this.addressDoctorsMap).some(([key, docs]) => key !== addressId && Array.isArray(docs) && docs.length > 0);
|
|
140
|
+
const otherHasDoctors = Object.entries(this.state.addressDoctorsMap).some(([key, docs]) => Number(key) !== addressId && Array.isArray(docs) && docs.length > 0);
|
|
167
141
|
this.doctors = [];
|
|
168
|
-
this.selectedDoctor = null;
|
|
142
|
+
this.state.selectedDoctor = null;
|
|
169
143
|
if (otherHasDoctors) {
|
|
170
|
-
this.
|
|
144
|
+
this.setState({
|
|
145
|
+
error: "No doctors at this address. Please select a different address.",
|
|
146
|
+
});
|
|
171
147
|
}
|
|
172
148
|
else {
|
|
173
|
-
this.
|
|
149
|
+
this.setState({
|
|
150
|
+
error: "No doctors available for the selected location(s).",
|
|
151
|
+
});
|
|
174
152
|
}
|
|
175
153
|
}
|
|
176
154
|
}
|
|
177
155
|
catch (e) {
|
|
178
|
-
this.
|
|
156
|
+
this.setState({
|
|
157
|
+
error: e.message || "Failed to load doctors for address",
|
|
158
|
+
});
|
|
179
159
|
}
|
|
180
160
|
finally {
|
|
181
|
-
this.
|
|
161
|
+
this.setState({ loading: false });
|
|
182
162
|
this.render();
|
|
183
163
|
}
|
|
184
164
|
}
|
|
185
165
|
async loadSlots() {
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
this.
|
|
166
|
+
const dateStr = formatDateToISO(this.state.selectedDate);
|
|
167
|
+
if (!this.state.workspaceId ||
|
|
168
|
+
!this.state.selectedAddress ||
|
|
169
|
+
!this.state.selectedDoctor ||
|
|
170
|
+
!dateStr) {
|
|
171
|
+
this.state.slots = [];
|
|
172
|
+
this.state.selectedSlot = null;
|
|
189
173
|
this.render();
|
|
190
174
|
return;
|
|
191
175
|
}
|
|
192
|
-
this.
|
|
193
|
-
this.setError(null);
|
|
176
|
+
this.setState({ loading: true, error: null });
|
|
194
177
|
try {
|
|
195
|
-
const s = await AppointmentService.fetchSlots(this.workspaceId, this.selectedAddress, this.selectedDoctor,
|
|
196
|
-
this.slots = s || [];
|
|
178
|
+
const s = await AppointmentService.fetchSlots(this.state.workspaceId, this.state.selectedAddress, this.state.selectedDoctor, dateStr);
|
|
179
|
+
this.state.slots = s || [];
|
|
197
180
|
}
|
|
198
181
|
catch (e) {
|
|
199
|
-
this.
|
|
182
|
+
this.setState({ error: e.message || "Failed to load slots" });
|
|
200
183
|
}
|
|
201
184
|
finally {
|
|
202
|
-
this.
|
|
185
|
+
this.setState({ loading: false });
|
|
203
186
|
this.render();
|
|
204
187
|
}
|
|
205
188
|
}
|
|
206
|
-
validatePhoneNumber(phone) {
|
|
207
|
-
const cleaned = phone.replace(/\D/g, "");
|
|
208
|
-
return cleaned.length >= 7 && cleaned.length <= 15;
|
|
209
|
-
}
|
|
210
|
-
validateCountryCode(code) {
|
|
211
|
-
return /^\+[1-9]\d{0,3}$/.test(code);
|
|
212
|
-
}
|
|
213
189
|
canProceedFromMergedStep() {
|
|
214
|
-
if (this.addresses.length === 0 || this.doctors.length === 0)
|
|
190
|
+
if (this.state.addresses.length === 0 || this.doctors.length === 0)
|
|
215
191
|
return false;
|
|
216
|
-
if (this.addresses.length > 1 && !this.selectedAddress)
|
|
192
|
+
if (this.state.addresses.length > 1 && !this.state.selectedAddress)
|
|
217
193
|
return false;
|
|
218
|
-
if (this.doctors.length > 1 && !this.selectedDoctor)
|
|
194
|
+
if (this.doctors.length > 1 && !this.state.selectedDoctor)
|
|
219
195
|
return false;
|
|
220
196
|
return true;
|
|
221
197
|
}
|
|
222
198
|
async sendOtp() {
|
|
223
|
-
this.
|
|
224
|
-
if (!this.countryCode) {
|
|
225
|
-
this.
|
|
199
|
+
this.setState({ error: null });
|
|
200
|
+
if (!this.state.countryCode) {
|
|
201
|
+
this.setState({ error: "Please enter country code." });
|
|
226
202
|
return;
|
|
227
203
|
}
|
|
228
|
-
if (!
|
|
229
|
-
this.
|
|
204
|
+
if (!validateCountryCode(this.state.countryCode)) {
|
|
205
|
+
this.setState({
|
|
206
|
+
error: "Please enter a valid country code (e.g., +91, +1).",
|
|
207
|
+
});
|
|
230
208
|
return;
|
|
231
209
|
}
|
|
232
|
-
if (!this.patientPhone) {
|
|
233
|
-
this.
|
|
210
|
+
if (!this.state.patientPhone) {
|
|
211
|
+
this.setState({ error: "Please enter phone number." });
|
|
234
212
|
return;
|
|
235
213
|
}
|
|
236
|
-
if (!
|
|
237
|
-
this.
|
|
214
|
+
if (!validatePhoneNumber(this.state.patientPhone)) {
|
|
215
|
+
this.setState({
|
|
216
|
+
error: "Please enter a valid phone number (7-15 digits).",
|
|
217
|
+
});
|
|
238
218
|
return;
|
|
239
219
|
}
|
|
240
|
-
this.otpSending
|
|
220
|
+
this.setState({ otpSending: true });
|
|
241
221
|
this.render();
|
|
242
222
|
try {
|
|
243
223
|
await PatientService.sendPhoneVerificationOtp({
|
|
244
|
-
countryCode: this.countryCode,
|
|
245
|
-
phoneNumber: this.patientPhone,
|
|
224
|
+
countryCode: this.state.countryCode,
|
|
225
|
+
phoneNumber: this.state.patientPhone,
|
|
246
226
|
});
|
|
247
|
-
this.otpSent
|
|
248
|
-
this.setError(null);
|
|
227
|
+
this.setState({ otpSent: true, error: null });
|
|
249
228
|
}
|
|
250
229
|
catch (e) {
|
|
251
230
|
const msg = e.message || "Failed to send OTP";
|
|
252
|
-
this.
|
|
231
|
+
this.setState({ error: msg });
|
|
253
232
|
this.options.onError?.(e);
|
|
254
233
|
}
|
|
255
234
|
finally {
|
|
256
|
-
this.otpSending
|
|
235
|
+
this.setState({ otpSending: false });
|
|
257
236
|
this.render();
|
|
258
237
|
}
|
|
259
238
|
}
|
|
260
239
|
async verifyOtp() {
|
|
261
|
-
this.
|
|
262
|
-
if (!this.countryCode ||
|
|
263
|
-
this.
|
|
240
|
+
this.setState({ error: null });
|
|
241
|
+
if (!this.state.countryCode ||
|
|
242
|
+
!this.state.patientPhone ||
|
|
243
|
+
!this.state.otpCode) {
|
|
244
|
+
this.setState({ error: "Please enter all required fields." });
|
|
264
245
|
return;
|
|
265
246
|
}
|
|
266
|
-
if (this.otpCode.length !== 6) {
|
|
267
|
-
this.
|
|
247
|
+
if (this.state.otpCode.length !== 6) {
|
|
248
|
+
this.setState({ error: "Please enter a 6-digit OTP code." });
|
|
268
249
|
return;
|
|
269
250
|
}
|
|
270
|
-
this.otpVerifying
|
|
251
|
+
this.setState({ otpVerifying: true });
|
|
271
252
|
this.render();
|
|
272
253
|
try {
|
|
273
254
|
await PatientService.verifyPhoneVerificationOtp({
|
|
274
|
-
countryCode: this.countryCode,
|
|
275
|
-
phoneNumber: this.patientPhone,
|
|
276
|
-
otpCode: this.otpCode,
|
|
255
|
+
countryCode: this.state.countryCode,
|
|
256
|
+
phoneNumber: this.state.patientPhone,
|
|
257
|
+
otpCode: this.state.otpCode,
|
|
277
258
|
});
|
|
278
|
-
this.otpVerified
|
|
279
|
-
this.setError(null);
|
|
259
|
+
this.setState({ otpVerified: true, error: null });
|
|
280
260
|
}
|
|
281
261
|
catch (e) {
|
|
282
262
|
const msg = e.message || "Invalid OTP code";
|
|
283
|
-
this.
|
|
263
|
+
this.setState({ error: msg });
|
|
284
264
|
this.options.onError?.(e);
|
|
285
265
|
}
|
|
286
266
|
finally {
|
|
287
|
-
this.otpVerifying
|
|
267
|
+
this.setState({ otpVerifying: false });
|
|
288
268
|
this.render();
|
|
289
269
|
}
|
|
290
270
|
}
|
|
291
271
|
async submitAppointment() {
|
|
292
|
-
this.
|
|
293
|
-
if (!this.selectedDoctor ||
|
|
294
|
-
!this.selectedSlot ||
|
|
295
|
-
!this.workspaceId ||
|
|
296
|
-
!this.selectedAddress ||
|
|
297
|
-
!this.patientAddress ||
|
|
298
|
-
!this.patientCity ||
|
|
299
|
-
!this.patientState ||
|
|
300
|
-
!this.patientCountry ||
|
|
301
|
-
!this.patientZipcode) {
|
|
302
|
-
this.
|
|
272
|
+
this.setState({ error: null });
|
|
273
|
+
if (!this.state.selectedDoctor ||
|
|
274
|
+
!this.state.selectedSlot ||
|
|
275
|
+
!this.state.workspaceId ||
|
|
276
|
+
!this.state.selectedAddress ||
|
|
277
|
+
!this.state.patientAddress ||
|
|
278
|
+
!this.state.patientCity ||
|
|
279
|
+
!this.state.patientState ||
|
|
280
|
+
!this.state.patientCountry ||
|
|
281
|
+
!this.state.patientZipcode) {
|
|
282
|
+
this.setState({
|
|
283
|
+
error: "Please ensure all required fields are complete.",
|
|
284
|
+
});
|
|
303
285
|
return;
|
|
304
286
|
}
|
|
305
|
-
if (!this.otpVerified) {
|
|
306
|
-
this.
|
|
287
|
+
if (!this.state.otpVerified) {
|
|
288
|
+
this.setState({ error: "Please verify your phone number first." });
|
|
307
289
|
return;
|
|
308
290
|
}
|
|
309
|
-
this.
|
|
291
|
+
this.setState({ loading: true });
|
|
310
292
|
this.render();
|
|
311
293
|
try {
|
|
312
|
-
const
|
|
313
|
-
const
|
|
314
|
-
const
|
|
315
|
-
const
|
|
316
|
-
const endDate = new Date(this.selectedSlot.end);
|
|
317
|
-
const appointmentDate = startDate.toISOString().split("T")[0];
|
|
294
|
+
const { firstName, lastName } = parsePatientName(this.state.patientName || "Patient");
|
|
295
|
+
const startDate = new Date(this.state.selectedSlot.start);
|
|
296
|
+
const endDate = new Date(this.state.selectedSlot.end);
|
|
297
|
+
const appointmentDate = formatDateToISO(startDate);
|
|
318
298
|
const formatTime = (date) => {
|
|
319
299
|
const hours = String(date.getHours()).padStart(2, "0");
|
|
320
300
|
const minutes = String(date.getMinutes()).padStart(2, "0");
|
|
@@ -323,112 +303,92 @@ class AppointmentCalendarWidget {
|
|
|
323
303
|
const fromDateTimeTs = formatTime(startDate);
|
|
324
304
|
const toDateTimeTs = formatTime(endDate);
|
|
325
305
|
const patientAddressPayload = {
|
|
326
|
-
addressLine1: this.patientAddress,
|
|
327
|
-
city: this.patientCity,
|
|
328
|
-
state: this.patientState,
|
|
329
|
-
country: this.patientCountry,
|
|
330
|
-
zipcode: this.patientZipcode,
|
|
331
|
-
landmark: this.patientLandmark || undefined,
|
|
306
|
+
addressLine1: this.state.patientAddress,
|
|
307
|
+
city: this.state.patientCity,
|
|
308
|
+
state: this.state.patientState,
|
|
309
|
+
country: this.state.patientCountry,
|
|
310
|
+
zipcode: this.state.patientZipcode,
|
|
311
|
+
landmark: this.state.patientLandmark || undefined,
|
|
332
312
|
};
|
|
333
313
|
await AppointmentService.createAppointment({
|
|
334
|
-
workspaceId: this.workspaceId,
|
|
335
|
-
workspaceAddressId: this.selectedAddress,
|
|
336
|
-
doctorId: this.selectedDoctor,
|
|
314
|
+
workspaceId: this.state.workspaceId,
|
|
315
|
+
workspaceAddressId: this.state.selectedAddress,
|
|
316
|
+
doctorId: this.state.selectedDoctor,
|
|
337
317
|
mode: "OFFLINE",
|
|
338
318
|
appointmentDate,
|
|
339
319
|
fromDateTimeTs,
|
|
340
320
|
toDateTimeTs,
|
|
341
|
-
consultationCharge: this.consultationCharge || "0",
|
|
321
|
+
consultationCharge: this.state.consultationCharge || "0",
|
|
342
322
|
type: "CONSULTATION",
|
|
343
323
|
source: "SDK_POWERED_WEBSITE",
|
|
344
324
|
patientPayload: {
|
|
345
325
|
firstName,
|
|
346
326
|
lastName,
|
|
347
|
-
email: this.patientEmail || undefined,
|
|
348
|
-
countryCode: this.countryCode,
|
|
349
|
-
phoneNumber: this.patientPhone,
|
|
350
|
-
age: this.
|
|
351
|
-
|
|
352
|
-
|
|
327
|
+
email: this.state.patientEmail || undefined,
|
|
328
|
+
countryCode: this.state.countryCode,
|
|
329
|
+
phoneNumber: this.state.patientPhone,
|
|
330
|
+
age: this.state.patientAge
|
|
331
|
+
? parseInt(this.state.patientAge, 10)
|
|
332
|
+
: undefined,
|
|
333
|
+
gender: this.state.patientGender
|
|
334
|
+
? this.state.patientGender.toUpperCase()
|
|
353
335
|
: undefined,
|
|
354
336
|
},
|
|
355
337
|
patientAddress: patientAddressPayload,
|
|
356
338
|
});
|
|
357
|
-
this.step = 5;
|
|
339
|
+
this.state.step = 5;
|
|
358
340
|
this.options.onSuccess?.();
|
|
359
341
|
}
|
|
360
342
|
catch (e) {
|
|
361
343
|
const msg = e.message || "Failed to create appointment";
|
|
362
|
-
this.
|
|
344
|
+
this.setState({ error: msg });
|
|
363
345
|
this.options.onError?.(e);
|
|
364
346
|
}
|
|
365
347
|
finally {
|
|
366
|
-
this.
|
|
348
|
+
this.setState({ loading: false });
|
|
367
349
|
this.render();
|
|
368
350
|
}
|
|
369
351
|
}
|
|
370
352
|
goToNext() {
|
|
371
|
-
if (this.step === 0) {
|
|
372
|
-
if (this.addresses.length > 1 && !this.selectedAddress)
|
|
353
|
+
if (this.state.step === 0) {
|
|
354
|
+
if (this.state.addresses.length > 1 && !this.state.selectedAddress)
|
|
373
355
|
return;
|
|
374
|
-
if (this.doctors.length > 1 && !this.selectedDoctor)
|
|
356
|
+
if (this.doctors.length > 1 && !this.state.selectedDoctor)
|
|
375
357
|
return;
|
|
376
|
-
this.step = 1;
|
|
358
|
+
this.state.step = 1;
|
|
377
359
|
this.render();
|
|
378
360
|
return;
|
|
379
361
|
}
|
|
380
|
-
|
|
381
|
-
|
|
362
|
+
const dateStr = formatDateToISO(this.state.selectedDate);
|
|
363
|
+
if (this.state.step === 1 && dateStr) {
|
|
364
|
+
this.state.step = 2;
|
|
382
365
|
this.loadSlots();
|
|
383
366
|
return;
|
|
384
367
|
}
|
|
385
|
-
if (this.step === 2 && this.selectedSlot) {
|
|
386
|
-
this.step = 3;
|
|
368
|
+
if (this.state.step === 2 && this.state.selectedSlot) {
|
|
369
|
+
this.state.step = 3;
|
|
387
370
|
this.render();
|
|
388
371
|
return;
|
|
389
372
|
}
|
|
390
|
-
if (this.step === 3 && this.otpVerified) {
|
|
391
|
-
this.step = 4;
|
|
373
|
+
if (this.state.step === 3 && this.state.otpVerified) {
|
|
374
|
+
this.state.step = 4;
|
|
392
375
|
this.render();
|
|
393
376
|
return;
|
|
394
377
|
}
|
|
395
|
-
this.step = Math.min(5, this.step + 1);
|
|
378
|
+
this.state.step = Math.min(5, this.state.step + 1);
|
|
396
379
|
this.render();
|
|
397
380
|
}
|
|
398
381
|
goBack() {
|
|
399
|
-
this.step = Math.max(0, this.step - 1);
|
|
382
|
+
this.state.step = Math.max(0, this.state.step - 1);
|
|
400
383
|
this.render();
|
|
401
384
|
}
|
|
402
385
|
reset() {
|
|
403
|
-
this.
|
|
404
|
-
this.
|
|
405
|
-
this.selectedDoctor = null;
|
|
406
|
-
this.date = "";
|
|
407
|
-
this.patientName = "";
|
|
408
|
-
this.patientAge = "";
|
|
409
|
-
this.patientAddress = "";
|
|
410
|
-
this.patientCity = "";
|
|
411
|
-
this.patientState = "";
|
|
412
|
-
this.patientCountry = "";
|
|
413
|
-
this.patientZipcode = "";
|
|
414
|
-
this.patientLandmark = "";
|
|
415
|
-
this.patientEmail = "";
|
|
416
|
-
this.patientGender = "";
|
|
417
|
-
this.problemFacing = "";
|
|
418
|
-
this.consultationCharge = "";
|
|
419
|
-
this.countryCode = "+91";
|
|
420
|
-
this.patientPhone = "";
|
|
421
|
-
this.otpCode = "";
|
|
422
|
-
this.otpSent = false;
|
|
423
|
-
this.otpVerified = false;
|
|
424
|
-
this.slots = [];
|
|
386
|
+
this.state = { ...INITIAL_STATE };
|
|
387
|
+
this.doctors = [];
|
|
425
388
|
this.render();
|
|
426
389
|
}
|
|
427
|
-
|
|
428
|
-
this.
|
|
429
|
-
}
|
|
430
|
-
setError(error) {
|
|
431
|
-
this.error = error;
|
|
390
|
+
setState(updates) {
|
|
391
|
+
this.state = { ...this.state, ...updates };
|
|
432
392
|
}
|
|
433
393
|
render() {
|
|
434
394
|
if (!this.mounted)
|
|
@@ -439,16 +399,20 @@ class AppointmentCalendarWidget {
|
|
|
439
399
|
<div class="medos-appointment-header">
|
|
440
400
|
<h2 class="medos-appointment-title">Book Appointment</h2>
|
|
441
401
|
<div class="medos-appointment-stepper">
|
|
442
|
-
<div class="medos-appointment-step-pill ${this.step === 0 ? "active" : ""}">1 Address</div>
|
|
443
|
-
<div class="medos-appointment-step-pill ${this.step === 1 ? "active" : ""}">2 Date</div>
|
|
444
|
-
<div class="medos-appointment-step-pill ${this.step === 2 ? "active" : ""}">3 Slot</div>
|
|
445
|
-
<div class="medos-appointment-step-pill ${this.step === 3 ? "active" : ""}">4 Phone</div>
|
|
446
|
-
<div class="medos-appointment-step-pill ${this.step === 4 ? "active" : ""}">5 Details</div>
|
|
402
|
+
<div class="medos-appointment-step-pill ${this.state.step === 0 ? "active" : ""}">1 Address</div>
|
|
403
|
+
<div class="medos-appointment-step-pill ${this.state.step === 1 ? "active" : ""}">2 Date</div>
|
|
404
|
+
<div class="medos-appointment-step-pill ${this.state.step === 2 ? "active" : ""}">3 Slot</div>
|
|
405
|
+
<div class="medos-appointment-step-pill ${this.state.step === 3 ? "active" : ""}">4 Phone</div>
|
|
406
|
+
<div class="medos-appointment-step-pill ${this.state.step === 4 ? "active" : ""}">5 Details</div>
|
|
447
407
|
</div>
|
|
448
408
|
</div>
|
|
449
409
|
|
|
450
|
-
${this.loading
|
|
451
|
-
|
|
410
|
+
${this.state.loading
|
|
411
|
+
? '<div class="medos-appointment-loading">Loading...</div>'
|
|
412
|
+
: ""}
|
|
413
|
+
${this.state.error
|
|
414
|
+
? `<div class="medos-appointment-error">${this.escapeHtml(this.state.error)}</div>`
|
|
415
|
+
: ""}
|
|
452
416
|
|
|
453
417
|
${this.renderStep()}
|
|
454
418
|
</div>
|
|
@@ -457,7 +421,7 @@ class AppointmentCalendarWidget {
|
|
|
457
421
|
this.attachEventListeners();
|
|
458
422
|
}
|
|
459
423
|
renderStep() {
|
|
460
|
-
switch (this.step) {
|
|
424
|
+
switch (this.state.step) {
|
|
461
425
|
case 0:
|
|
462
426
|
return this.renderStep0();
|
|
463
427
|
case 1:
|
|
@@ -481,14 +445,16 @@ class AppointmentCalendarWidget {
|
|
|
481
445
|
<div class="medos-appointment-form-grid-2col">
|
|
482
446
|
<div>
|
|
483
447
|
<label class="medos-appointment-label">Address</label>
|
|
484
|
-
${this.addresses.length === 0
|
|
448
|
+
${this.state.addresses.length === 0
|
|
485
449
|
? '<div class="medos-appointment-small-muted">No addresses available</div>'
|
|
486
|
-
: this.addresses.length === 1
|
|
487
|
-
? `<div class="medos-appointment-small-muted" style="font-weight: 600">${this.escapeHtml(this.addresses[0].label)}</div>`
|
|
450
|
+
: this.state.addresses.length === 1
|
|
451
|
+
? `<div class="medos-appointment-small-muted" style="font-weight: 600">${this.escapeHtml(this.state.addresses[0].label || "")}</div>`
|
|
488
452
|
: `
|
|
489
453
|
<select class="medos-appointment-select" id="medos-address-select">
|
|
490
454
|
<option value="">-- choose address --</option>
|
|
491
|
-
${this.addresses
|
|
455
|
+
${this.state.addresses
|
|
456
|
+
.map((a) => `<option value="${this.escapeHtml(a.id.toString())}" ${this.state.selectedAddress === a.id ? "selected" : ""}>${this.escapeHtml(a.label || "")}</option>`)
|
|
457
|
+
.join("")}
|
|
492
458
|
</select>
|
|
493
459
|
`}
|
|
494
460
|
</div>
|
|
@@ -497,11 +463,17 @@ class AppointmentCalendarWidget {
|
|
|
497
463
|
${this.doctors.length === 0
|
|
498
464
|
? '<div class="medos-appointment-small-muted">No doctors available</div>'
|
|
499
465
|
: this.doctors.length === 1
|
|
500
|
-
? `<div class="medos-appointment-small-muted" style="font-weight: 600">${this.escapeHtml(this.doctors[0].name)}${this.doctors[0].specialty
|
|
466
|
+
? `<div class="medos-appointment-small-muted" style="font-weight: 600">${this.escapeHtml(this.doctors[0].name)}${this.doctors[0].specialty
|
|
467
|
+
? ` • ${this.escapeHtml(this.doctors[0].specialty)}`
|
|
468
|
+
: ""}</div>`
|
|
501
469
|
: `
|
|
502
470
|
<select class="medos-appointment-select" id="medos-doctor-select">
|
|
503
471
|
<option value="">-- choose doctor --</option>
|
|
504
|
-
${this.doctors
|
|
472
|
+
${this.doctors
|
|
473
|
+
.map((d) => `<option value="${this.escapeHtml(d.id.toString())}" ${this.state.selectedDoctor === d.id ? "selected" : ""}>${this.escapeHtml(d.name)}${d.specialty
|
|
474
|
+
? ` (${this.escapeHtml(d.specialty)})`
|
|
475
|
+
: ""}</option>`)
|
|
476
|
+
.join("")}
|
|
505
477
|
</select>
|
|
506
478
|
`}
|
|
507
479
|
</div>
|
|
@@ -514,13 +486,14 @@ class AppointmentCalendarWidget {
|
|
|
514
486
|
`;
|
|
515
487
|
}
|
|
516
488
|
renderStep1() {
|
|
489
|
+
const dateStr = formatDateToISO(this.state.selectedDate);
|
|
517
490
|
return `
|
|
518
491
|
<div class="medos-appointment-section">
|
|
519
492
|
<label class="medos-appointment-label">Select Date</label>
|
|
520
|
-
<input type="date" class="medos-appointment-input" id="medos-date-input" value="${this.escapeHtml(
|
|
493
|
+
<input type="date" class="medos-appointment-input" id="medos-date-input" value="${this.escapeHtml(dateStr)}" />
|
|
521
494
|
<div class="medos-appointment-actions">
|
|
522
495
|
<button class="medos-appointment-btn medos-appointment-btn-secondary" id="medos-btn-back">Back</button>
|
|
523
|
-
<button class="medos-appointment-btn medos-appointment-btn-primary" id="medos-btn-next" ${!
|
|
496
|
+
<button class="medos-appointment-btn medos-appointment-btn-primary" id="medos-btn-next" ${!dateStr ? "disabled" : ""} style="opacity: ${dateStr ? 1 : 0.6}">Next</button>
|
|
524
497
|
</div>
|
|
525
498
|
</div>
|
|
526
499
|
`;
|
|
@@ -529,50 +502,63 @@ class AppointmentCalendarWidget {
|
|
|
529
502
|
return `
|
|
530
503
|
<div class="medos-appointment-section">
|
|
531
504
|
<label class="medos-appointment-label">Choose Time Slot</label>
|
|
532
|
-
${this.slots.length === 0
|
|
505
|
+
${this.state.slots.length === 0
|
|
533
506
|
? '<div class="medos-appointment-small-muted">No slots available for selected date</div>'
|
|
534
507
|
: `
|
|
535
508
|
<div class="medos-appointment-slot-grid">
|
|
536
|
-
${this.slots
|
|
537
|
-
|
|
538
|
-
const
|
|
539
|
-
|
|
509
|
+
${this.state.slots
|
|
510
|
+
.map((s) => {
|
|
511
|
+
const start = new Date(s.start).toLocaleTimeString([], {
|
|
512
|
+
hour: "2-digit",
|
|
513
|
+
minute: "2-digit",
|
|
514
|
+
});
|
|
515
|
+
const end = new Date(s.end).toLocaleTimeString([], {
|
|
516
|
+
hour: "2-digit",
|
|
517
|
+
minute: "2-digit",
|
|
518
|
+
});
|
|
519
|
+
const selected = this.state.selectedSlot?.start === s.start &&
|
|
520
|
+
this.state.selectedSlot?.end === s.end;
|
|
540
521
|
return `
|
|
541
522
|
<div class="medos-appointment-slot-card ${selected ? "selected" : ""}" data-slot-id="${this.escapeHtml(s.id || `${s.start}-${s.end}`)}" data-slot-start="${this.escapeHtml(s.start)}" data-slot-end="${this.escapeHtml(s.end)}">
|
|
542
523
|
<div class="medos-appointment-slot-time">${start} — ${end}</div>
|
|
543
524
|
</div>
|
|
544
525
|
`;
|
|
545
|
-
})
|
|
526
|
+
})
|
|
527
|
+
.join("")}
|
|
546
528
|
</div>
|
|
547
529
|
`}
|
|
548
530
|
<div class="medos-appointment-actions">
|
|
549
531
|
<button class="medos-appointment-btn medos-appointment-btn-secondary" id="medos-btn-back">Back</button>
|
|
550
|
-
<button class="medos-appointment-btn medos-appointment-btn-primary" id="medos-btn-next" ${!this.selectedSlot ? "disabled" : ""} style="opacity: ${this.selectedSlot ? 1 : 0.6}">Next</button>
|
|
532
|
+
<button class="medos-appointment-btn medos-appointment-btn-primary" id="medos-btn-next" ${!this.state.selectedSlot ? "disabled" : ""} style="opacity: ${this.state.selectedSlot ? 1 : 0.6}">Next</button>
|
|
551
533
|
</div>
|
|
552
534
|
</div>
|
|
553
535
|
`;
|
|
554
536
|
}
|
|
555
537
|
renderStep3() {
|
|
556
|
-
const countryCodeValid = this.countryCode &&
|
|
557
|
-
const phoneValid = this.patientPhone &&
|
|
558
|
-
const canSendOtp = countryCodeValid && phoneValid && !this.otpSending;
|
|
559
|
-
if (!this.otpSent) {
|
|
538
|
+
const countryCodeValid = this.state.countryCode && validateCountryCode(this.state.countryCode);
|
|
539
|
+
const phoneValid = this.state.patientPhone && validatePhoneNumber(this.state.patientPhone);
|
|
540
|
+
const canSendOtp = countryCodeValid && phoneValid && !this.state.otpSending;
|
|
541
|
+
if (!this.state.otpSent) {
|
|
560
542
|
return `
|
|
561
543
|
<div class="medos-appointment-section">
|
|
562
544
|
<label class="medos-appointment-label">Country Code</label>
|
|
563
|
-
<input type="text" class="medos-appointment-input" id="medos-country-code" placeholder="+91" value="${this.escapeHtml(this.countryCode)}" />
|
|
564
|
-
${this.countryCode && !countryCodeValid
|
|
545
|
+
<input type="text" class="medos-appointment-input" id="medos-country-code" placeholder="+91" value="${this.escapeHtml(this.state.countryCode)}" />
|
|
546
|
+
${this.state.countryCode && !countryCodeValid
|
|
547
|
+
? '<div class="medos-appointment-validation-error">Please enter a valid country code (e.g., +91, +1)</div>'
|
|
548
|
+
: ""}
|
|
565
549
|
<label class="medos-appointment-label" style="margin-top: 12px">Phone Number</label>
|
|
566
|
-
<input type="tel" class="medos-appointment-input" id="medos-phone" placeholder="9311840587" value="${this.escapeHtml(this.patientPhone)}" maxlength="15" />
|
|
567
|
-
${this.patientPhone && !phoneValid
|
|
550
|
+
<input type="tel" class="medos-appointment-input" id="medos-phone" placeholder="9311840587" value="${this.escapeHtml(this.state.patientPhone)}" maxlength="15" />
|
|
551
|
+
${this.state.patientPhone && !phoneValid
|
|
552
|
+
? '<div class="medos-appointment-validation-error">Phone number should be 7-15 digits</div>'
|
|
553
|
+
: ""}
|
|
568
554
|
<div class="medos-appointment-actions">
|
|
569
555
|
<button class="medos-appointment-btn medos-appointment-btn-secondary" id="medos-btn-back">Back</button>
|
|
570
|
-
<button class="medos-appointment-btn medos-appointment-btn-primary" id="medos-btn-send-otp" ${!canSendOtp ? "disabled" : ""} style="opacity: ${canSendOtp ? 1 : 0.6}">${this.otpSending ? "Sending..." : "Send OTP"}</button>
|
|
556
|
+
<button class="medos-appointment-btn medos-appointment-btn-primary" id="medos-btn-send-otp" ${!canSendOtp ? "disabled" : ""} style="opacity: ${canSendOtp ? 1 : 0.6}">${this.state.otpSending ? "Sending..." : "Send OTP"}</button>
|
|
571
557
|
</div>
|
|
572
558
|
</div>
|
|
573
559
|
`;
|
|
574
560
|
}
|
|
575
|
-
if (this.otpVerified) {
|
|
561
|
+
if (this.state.otpVerified) {
|
|
576
562
|
return `
|
|
577
563
|
<div class="medos-appointment-section">
|
|
578
564
|
<div class="medos-appointment-verified">✓ Phone verified successfully</div>
|
|
@@ -586,61 +572,69 @@ class AppointmentCalendarWidget {
|
|
|
586
572
|
return `
|
|
587
573
|
<div class="medos-appointment-section">
|
|
588
574
|
<label class="medos-appointment-label">Enter OTP</label>
|
|
589
|
-
<input type="text" class="medos-appointment-input" id="medos-otp" placeholder="Enter 6-digit OTP" value="${this.escapeHtml(this.otpCode)}" maxlength="6" />
|
|
590
|
-
<div class="medos-appointment-otp-info">OTP sent to ${this.escapeHtml(this.countryCode)} ${this.escapeHtml(this.patientPhone)}</div>
|
|
575
|
+
<input type="text" class="medos-appointment-input" id="medos-otp" placeholder="Enter 6-digit OTP" value="${this.escapeHtml(this.state.otpCode)}" maxlength="6" />
|
|
576
|
+
<div class="medos-appointment-otp-info">OTP sent to ${this.escapeHtml(this.state.countryCode)} ${this.escapeHtml(this.state.patientPhone)}</div>
|
|
591
577
|
<div class="medos-appointment-actions">
|
|
592
578
|
<button class="medos-appointment-btn medos-appointment-btn-secondary" id="medos-btn-change-number">Change Number</button>
|
|
593
|
-
<button class="medos-appointment-btn medos-appointment-btn-primary" id="medos-btn-verify-otp" ${this.otpCode.length !== 6 || this.
|
|
579
|
+
<button class="medos-appointment-btn medos-appointment-btn-primary" id="medos-btn-verify-otp" ${this.state.otpCode.length !== 6 || this.state.otpVerifying
|
|
580
|
+
? "disabled"
|
|
581
|
+
: ""} style="opacity: ${this.state.otpCode.length === 6 && !this.state.otpVerifying ? 1 : 0.6}">${this.state.otpVerifying ? "Verifying..." : "Verify OTP"}</button>
|
|
594
582
|
</div>
|
|
595
583
|
</div>
|
|
596
584
|
`;
|
|
597
585
|
}
|
|
598
586
|
renderStep4() {
|
|
599
|
-
const canSubmit = this.patientName &&
|
|
587
|
+
const canSubmit = this.state.patientName &&
|
|
588
|
+
this.state.patientAddress &&
|
|
589
|
+
this.state.patientCity &&
|
|
590
|
+
this.state.patientState &&
|
|
591
|
+
this.state.patientCountry &&
|
|
592
|
+
this.state.patientZipcode &&
|
|
593
|
+
this.state.otpVerified;
|
|
600
594
|
return `
|
|
601
595
|
<div class="medos-appointment-section">
|
|
602
596
|
<label class="medos-appointment-label">Patient Name</label>
|
|
603
|
-
<input type="text" class="medos-appointment-input" id="medos-patient-name" placeholder="Full name" value="${this.escapeHtml(this.patientName)}" />
|
|
597
|
+
<input type="text" class="medos-appointment-input" id="medos-patient-name" placeholder="Full name" value="${this.escapeHtml(this.state.patientName)}" />
|
|
604
598
|
<label class="medos-appointment-label" style="margin-top: 12px">Age</label>
|
|
605
|
-
<input type="number" class="medos-appointment-input" id="medos-patient-age" placeholder="Age" value="${this.escapeHtml(this.patientAge)}" />
|
|
599
|
+
<input type="number" class="medos-appointment-input" id="medos-patient-age" placeholder="Age" value="${this.escapeHtml(this.state.patientAge)}" />
|
|
606
600
|
<label class="medos-appointment-label" style="margin-top: 12px">Email (Optional)</label>
|
|
607
|
-
<input type="email" class="medos-appointment-input" id="medos-patient-email" placeholder="patient@example.com" value="${this.escapeHtml(this.patientEmail)}" />
|
|
601
|
+
<input type="email" class="medos-appointment-input" id="medos-patient-email" placeholder="patient@example.com" value="${this.escapeHtml(this.state.patientEmail)}" />
|
|
608
602
|
<label class="medos-appointment-label" style="margin-top: 12px">Gender (Optional)</label>
|
|
609
603
|
<select class="medos-appointment-select" id="medos-patient-gender">
|
|
610
604
|
<option value="">-- Select Gender --</option>
|
|
611
|
-
<option value="MALE" ${this.patientGender === "MALE" ? "selected" : ""}>Male</option>
|
|
612
|
-
<option value="FEMALE" ${this.patientGender === "FEMALE" ? "selected" : ""}>Female</option>
|
|
613
|
-
<option value="OTHER" ${this.patientGender === "OTHER" ? "selected" : ""}>Other</option>
|
|
605
|
+
<option value="MALE" ${this.state.patientGender === "MALE" ? "selected" : ""}>Male</option>
|
|
606
|
+
<option value="FEMALE" ${this.state.patientGender === "FEMALE" ? "selected" : ""}>Female</option>
|
|
607
|
+
<option value="OTHER" ${this.state.patientGender === "OTHER" ? "selected" : ""}>Other</option>
|
|
614
608
|
</select>
|
|
615
609
|
<label class="medos-appointment-label" style="margin-top: 12px">Address Line 1 *</label>
|
|
616
|
-
<input type="text" class="medos-appointment-input" id="medos-patient-address" placeholder="Street address, building name, etc." value="${this.escapeHtml(this.patientAddress)}" />
|
|
610
|
+
<input type="text" class="medos-appointment-input" id="medos-patient-address" placeholder="Street address, building name, etc." value="${this.escapeHtml(this.state.patientAddress)}" />
|
|
617
611
|
<div class="medos-appointment-form-grid">
|
|
618
612
|
<div>
|
|
619
613
|
<label class="medos-appointment-label">City *</label>
|
|
620
|
-
<input type="text" class="medos-appointment-input" id="medos-patient-city" placeholder="City" value="${this.escapeHtml(this.patientCity)}" />
|
|
614
|
+
<input type="text" class="medos-appointment-input" id="medos-patient-city" placeholder="City" value="${this.escapeHtml(this.state.patientCity)}" />
|
|
621
615
|
</div>
|
|
622
616
|
<div>
|
|
623
617
|
<label class="medos-appointment-label">State *</label>
|
|
624
|
-
<input type="text" class="medos-appointment-input" id="medos-patient-state" placeholder="State" value="${this.escapeHtml(this.patientState)}" />
|
|
618
|
+
<input type="text" class="medos-appointment-input" id="medos-patient-state" placeholder="State" value="${this.escapeHtml(this.state.patientState)}" />
|
|
625
619
|
</div>
|
|
626
620
|
</div>
|
|
627
621
|
<div class="medos-appointment-form-grid">
|
|
628
622
|
<div>
|
|
629
623
|
<label class="medos-appointment-label">Country *</label>
|
|
630
|
-
<input type="text" class="medos-appointment-input" id="medos-patient-country" placeholder="Country" value="${this.escapeHtml(this.patientCountry)}" />
|
|
624
|
+
<input type="text" class="medos-appointment-input" id="medos-patient-country" placeholder="Country" value="${this.escapeHtml(this.state.patientCountry)}" />
|
|
631
625
|
</div>
|
|
632
626
|
<div>
|
|
633
627
|
<label class="medos-appointment-label">Zipcode *</label>
|
|
634
|
-
<input type="text" class="medos-appointment-input" id="medos-patient-zipcode" placeholder="Zipcode" value="${this.escapeHtml(this.patientZipcode)}" />
|
|
628
|
+
<input type="text" class="medos-appointment-input" id="medos-patient-zipcode" placeholder="Zipcode" value="${this.escapeHtml(this.state.patientZipcode)}" />
|
|
635
629
|
</div>
|
|
636
630
|
</div>
|
|
637
631
|
<label class="medos-appointment-label" style="margin-top: 12px">Landmark (Optional)</label>
|
|
638
|
-
<input type="text" class="medos-appointment-input" id="medos-patient-landmark" placeholder="Nearby landmark" value="${this.escapeHtml(this.patientLandmark)}" />
|
|
632
|
+
<input type="text" class="medos-appointment-input" id="medos-patient-landmark" placeholder="Nearby landmark" value="${this.escapeHtml(this.state.patientLandmark)}" />
|
|
639
633
|
<label class="medos-appointment-label" style="margin-top: 12px">Problem Facing</label>
|
|
640
|
-
<textarea class="medos-appointment-textarea" id="medos-problem-facing" placeholder="Describe the problem you're facing">${this.escapeHtml(this.
|
|
634
|
+
<textarea class="medos-appointment-textarea" id="medos-problem-facing" placeholder="Describe the problem you're facing">${this.escapeHtml(this.state.patientName)}</textarea>
|
|
641
635
|
<div class="medos-appointment-actions">
|
|
642
636
|
<button class="medos-appointment-btn medos-appointment-btn-secondary" id="medos-btn-back">Back</button>
|
|
643
|
-
<button class="medos-appointment-btn medos-appointment-btn-primary" id="medos-btn-submit" ${!canSubmit || this.loading ? "disabled" : ""} style="opacity: ${canSubmit && !this.loading ? 1 : 0.6}">${this.loading ? "Booking..." : "Book Appointment"}</button>
|
|
637
|
+
<button class="medos-appointment-btn medos-appointment-btn-primary" id="medos-btn-submit" ${!canSubmit || this.state.loading ? "disabled" : ""} style="opacity: ${canSubmit && !this.state.loading ? 1 : 0.6}">${this.state.loading ? "Booking..." : "Book Appointment"}</button>
|
|
644
638
|
</div>
|
|
645
639
|
</div>
|
|
646
640
|
`;
|
|
@@ -651,7 +645,7 @@ class AppointmentCalendarWidget {
|
|
|
651
645
|
<div class="medos-appointment-success-card">
|
|
652
646
|
<div class="medos-appointment-success-icon">✓</div>
|
|
653
647
|
<div class="medos-appointment-success-title">Appointment Confirmed</div>
|
|
654
|
-
<div class="medos-appointment-small-muted">Thank you, ${this.escapeHtml(this.patientName || "Patient")}. Your appointment is confirmed.</div>
|
|
648
|
+
<div class="medos-appointment-small-muted">Thank you, ${this.escapeHtml(this.state.patientName || "Patient")}. Your appointment is confirmed.</div>
|
|
655
649
|
</div>
|
|
656
650
|
<div style="margin-top: 14px; display: flex; justify-content: center">
|
|
657
651
|
<button class="medos-appointment-btn medos-appointment-btn-primary" id="medos-btn-book-another" style="width: 160px">Book Another</button>
|
|
@@ -664,14 +658,14 @@ class AppointmentCalendarWidget {
|
|
|
664
658
|
if (addressSelect) {
|
|
665
659
|
addressSelect.addEventListener("change", (e) => {
|
|
666
660
|
const target = e.target;
|
|
667
|
-
this.handleAddressChange(target.value || null);
|
|
661
|
+
this.handleAddressChange(Number(target.value) || null);
|
|
668
662
|
});
|
|
669
663
|
}
|
|
670
664
|
const doctorSelect = this.container.querySelector("#medos-doctor-select");
|
|
671
665
|
if (doctorSelect) {
|
|
672
666
|
doctorSelect.addEventListener("change", (e) => {
|
|
673
667
|
const target = e.target;
|
|
674
|
-
this.selectedDoctor = target.value || null;
|
|
668
|
+
this.state.selectedDoctor = Number(target.value) || null;
|
|
675
669
|
this.render();
|
|
676
670
|
});
|
|
677
671
|
}
|
|
@@ -679,18 +673,22 @@ class AppointmentCalendarWidget {
|
|
|
679
673
|
if (dateInput) {
|
|
680
674
|
dateInput.addEventListener("change", (e) => {
|
|
681
675
|
const target = e.target;
|
|
682
|
-
this.
|
|
676
|
+
this.state.selectedDate = new Date(target.value);
|
|
683
677
|
this.render();
|
|
684
678
|
});
|
|
685
679
|
}
|
|
686
680
|
const slotCards = this.container.querySelectorAll(".medos-appointment-slot-card");
|
|
687
|
-
slotCards.forEach(card => {
|
|
681
|
+
slotCards.forEach((card) => {
|
|
688
682
|
card.addEventListener("click", () => {
|
|
689
683
|
const slotId = card.getAttribute("data-slot-id");
|
|
690
684
|
const slotStart = card.getAttribute("data-slot-start");
|
|
691
685
|
const slotEnd = card.getAttribute("data-slot-end");
|
|
692
686
|
if (slotStart && slotEnd) {
|
|
693
|
-
this.selectedSlot = {
|
|
687
|
+
this.state.selectedSlot = {
|
|
688
|
+
start: slotStart,
|
|
689
|
+
end: slotEnd,
|
|
690
|
+
id: slotId || undefined,
|
|
691
|
+
};
|
|
694
692
|
this.render();
|
|
695
693
|
}
|
|
696
694
|
});
|
|
@@ -704,7 +702,7 @@ class AppointmentCalendarWidget {
|
|
|
704
702
|
value = "+" + value;
|
|
705
703
|
}
|
|
706
704
|
value = value.replace(/[^\d+]/g, "");
|
|
707
|
-
this.countryCode = value;
|
|
705
|
+
this.state.countryCode = value;
|
|
708
706
|
target.value = value;
|
|
709
707
|
this.render();
|
|
710
708
|
});
|
|
@@ -713,8 +711,8 @@ class AppointmentCalendarWidget {
|
|
|
713
711
|
if (phoneInput) {
|
|
714
712
|
phoneInput.addEventListener("input", (e) => {
|
|
715
713
|
const target = e.target;
|
|
716
|
-
this.patientPhone = target.value.replace(/\D/g, "");
|
|
717
|
-
target.value = this.patientPhone;
|
|
714
|
+
this.state.patientPhone = target.value.replace(/\D/g, "");
|
|
715
|
+
target.value = this.state.patientPhone;
|
|
718
716
|
this.render();
|
|
719
717
|
});
|
|
720
718
|
}
|
|
@@ -722,7 +720,7 @@ class AppointmentCalendarWidget {
|
|
|
722
720
|
if (otpInput) {
|
|
723
721
|
otpInput.addEventListener("input", (e) => {
|
|
724
722
|
const target = e.target;
|
|
725
|
-
this.otpCode = target.value;
|
|
723
|
+
this.state.otpCode = target.value;
|
|
726
724
|
this.render();
|
|
727
725
|
});
|
|
728
726
|
}
|
|
@@ -730,77 +728,70 @@ class AppointmentCalendarWidget {
|
|
|
730
728
|
if (patientNameInput) {
|
|
731
729
|
patientNameInput.addEventListener("input", (e) => {
|
|
732
730
|
const target = e.target;
|
|
733
|
-
this.patientName = target.value;
|
|
731
|
+
this.state.patientName = target.value;
|
|
734
732
|
});
|
|
735
733
|
}
|
|
736
734
|
const patientAgeInput = this.container.querySelector("#medos-patient-age");
|
|
737
735
|
if (patientAgeInput) {
|
|
738
736
|
patientAgeInput.addEventListener("input", (e) => {
|
|
739
737
|
const target = e.target;
|
|
740
|
-
this.patientAge = target.value;
|
|
738
|
+
this.state.patientAge = target.value;
|
|
741
739
|
});
|
|
742
740
|
}
|
|
743
741
|
const patientEmailInput = this.container.querySelector("#medos-patient-email");
|
|
744
742
|
if (patientEmailInput) {
|
|
745
743
|
patientEmailInput.addEventListener("input", (e) => {
|
|
746
744
|
const target = e.target;
|
|
747
|
-
this.patientEmail = target.value;
|
|
745
|
+
this.state.patientEmail = target.value;
|
|
748
746
|
});
|
|
749
747
|
}
|
|
750
748
|
const patientGenderSelect = this.container.querySelector("#medos-patient-gender");
|
|
751
749
|
if (patientGenderSelect) {
|
|
752
750
|
patientGenderSelect.addEventListener("change", (e) => {
|
|
753
751
|
const target = e.target;
|
|
754
|
-
this.patientGender = target.value;
|
|
752
|
+
this.state.patientGender = target.value;
|
|
755
753
|
});
|
|
756
754
|
}
|
|
757
755
|
const patientAddressInput = this.container.querySelector("#medos-patient-address");
|
|
758
756
|
if (patientAddressInput) {
|
|
759
757
|
patientAddressInput.addEventListener("input", (e) => {
|
|
760
758
|
const target = e.target;
|
|
761
|
-
this.patientAddress = target.value;
|
|
759
|
+
this.state.patientAddress = target.value;
|
|
762
760
|
});
|
|
763
761
|
}
|
|
764
762
|
const patientCityInput = this.container.querySelector("#medos-patient-city");
|
|
765
763
|
if (patientCityInput) {
|
|
766
764
|
patientCityInput.addEventListener("input", (e) => {
|
|
767
765
|
const target = e.target;
|
|
768
|
-
this.patientCity = target.value;
|
|
766
|
+
this.state.patientCity = target.value;
|
|
769
767
|
});
|
|
770
768
|
}
|
|
771
769
|
const patientStateInput = this.container.querySelector("#medos-patient-state");
|
|
772
770
|
if (patientStateInput) {
|
|
773
771
|
patientStateInput.addEventListener("input", (e) => {
|
|
774
772
|
const target = e.target;
|
|
775
|
-
this.patientState = target.value;
|
|
773
|
+
this.state.patientState = target.value;
|
|
776
774
|
});
|
|
777
775
|
}
|
|
778
776
|
const patientCountryInput = this.container.querySelector("#medos-patient-country");
|
|
779
777
|
if (patientCountryInput) {
|
|
780
778
|
patientCountryInput.addEventListener("input", (e) => {
|
|
781
779
|
const target = e.target;
|
|
782
|
-
this.patientCountry = target.value;
|
|
780
|
+
this.state.patientCountry = target.value;
|
|
783
781
|
});
|
|
784
782
|
}
|
|
785
783
|
const patientZipcodeInput = this.container.querySelector("#medos-patient-zipcode");
|
|
786
784
|
if (patientZipcodeInput) {
|
|
787
785
|
patientZipcodeInput.addEventListener("input", (e) => {
|
|
788
786
|
const target = e.target;
|
|
789
|
-
this.patientZipcode = target.value;
|
|
787
|
+
this.state.patientZipcode = target.value;
|
|
790
788
|
});
|
|
791
789
|
}
|
|
792
790
|
const patientLandmarkInput = this.container.querySelector("#medos-patient-landmark");
|
|
793
791
|
if (patientLandmarkInput) {
|
|
794
792
|
patientLandmarkInput.addEventListener("input", (e) => {
|
|
795
793
|
const target = e.target;
|
|
796
|
-
this.patientLandmark = target.value;
|
|
797
|
-
});
|
|
798
|
-
}
|
|
799
|
-
const problemFacingInput = this.container.querySelector("#medos-problem-facing");
|
|
800
|
-
if (problemFacingInput) {
|
|
801
|
-
problemFacingInput.addEventListener("input", (e) => {
|
|
802
|
-
const target = e.target;
|
|
803
|
-
this.problemFacing = target.value;
|
|
794
|
+
this.state.patientLandmark = target.value;
|
|
804
795
|
});
|
|
805
796
|
}
|
|
806
797
|
const nextBtn = this.container.querySelector("#medos-btn-next");
|
|
@@ -822,9 +813,7 @@ class AppointmentCalendarWidget {
|
|
|
822
813
|
const changeNumberBtn = this.container.querySelector("#medos-btn-change-number");
|
|
823
814
|
if (changeNumberBtn) {
|
|
824
815
|
changeNumberBtn.addEventListener("click", () => {
|
|
825
|
-
this.otpSent
|
|
826
|
-
this.otpCode = "";
|
|
827
|
-
this.otpVerified = false;
|
|
816
|
+
this.setState({ otpSent: false, otpCode: "", otpVerified: false });
|
|
828
817
|
this.render();
|
|
829
818
|
});
|
|
830
819
|
}
|