medos-sdk 1.0.3 → 1.1.1

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 (95) hide show
  1. package/README.md +80 -8
  2. package/dist/client/MedosClient.d.ts +2 -4
  3. package/dist/client/MedosClient.js +4 -4
  4. package/dist/components/AppointmentCalender.js +43 -2
  5. package/dist/components/AppointmentDateTimeModal.js +45 -31
  6. package/dist/components/ContactInformationStep.d.ts +13 -0
  7. package/dist/components/ContactInformationStep.js +14 -0
  8. package/dist/components/ContactPreferenceStep.d.ts +9 -0
  9. package/dist/components/ContactPreferenceStep.js +16 -0
  10. package/dist/components/DoctorSelectModal.js +30 -17
  11. package/dist/components/EnquiryForm.d.ts +7 -0
  12. package/dist/components/EnquiryForm.js +212 -0
  13. package/dist/components/Icons/ConsultationType.js +1 -1
  14. package/dist/components/Icons/Date&TimeIcon.js +1 -1
  15. package/dist/components/Icons/MapIcon.js +1 -1
  16. package/dist/components/Icons/PaymentMethodIcon.js +1 -1
  17. package/dist/components/Icons/UserIcon.js +1 -1
  18. package/dist/components/InquiryDetailsStep.d.ts +10 -0
  19. package/dist/components/InquiryDetailsStep.js +15 -0
  20. package/dist/components/PatientDetailsStep.js +9 -1
  21. package/dist/components/PhoneVerificationStep.js +22 -12
  22. package/dist/components/SuccessStep.d.ts +3 -1
  23. package/dist/components/SuccessStep.js +7 -15
  24. package/dist/components/custom-calendar.js +42 -24
  25. package/dist/components/theme-styles.d.ts +12 -0
  26. package/dist/components/theme-styles.js +319 -0
  27. package/dist/components/types.d.ts +0 -1
  28. package/dist/core/index.d.ts +4 -0
  29. package/dist/core/index.js +4 -0
  30. package/dist/core/theme/index.d.ts +3 -0
  31. package/dist/core/theme/index.js +3 -0
  32. package/dist/core/theme/themes.d.ts +8 -0
  33. package/dist/core/theme/themes.js +178 -0
  34. package/dist/core/theme/types.d.ts +106 -0
  35. package/dist/core/theme/types.js +1 -0
  36. package/dist/core/theme/utils.d.ts +8 -0
  37. package/dist/core/theme/utils.js +135 -0
  38. package/dist/enquiry-form/index.d.ts +4 -0
  39. package/dist/enquiry-form/index.js +4 -0
  40. package/dist/enquiry-form/provider.d.ts +3 -0
  41. package/dist/enquiry-form/provider.js +9 -0
  42. package/dist/enquiry-form/serialization.d.ts +4 -0
  43. package/dist/enquiry-form/serialization.js +57 -0
  44. package/dist/enquiry-form/types.d.ts +38 -0
  45. package/dist/enquiry-form/types.js +1 -0
  46. package/dist/enquiry-form/validation.d.ts +6 -0
  47. package/dist/enquiry-form/validation.js +21 -0
  48. package/dist/index.d.ts +9 -0
  49. package/dist/index.js +7 -0
  50. package/dist/react/ThemeProvider.d.ts +18 -0
  51. package/dist/react/ThemeProvider.js +45 -0
  52. package/dist/react/hooks/useTheme.d.ts +1 -0
  53. package/dist/react/hooks/useTheme.js +1 -0
  54. package/dist/react/index.d.ts +5 -0
  55. package/dist/react/index.js +3 -0
  56. package/dist/services/EnquiryService.d.ts +5 -0
  57. package/dist/services/EnquiryService.js +30 -0
  58. package/dist/vanilla/AppointmentCalendarWidget.js +0 -2
  59. package/dist/vanilla/EnquiryFormWidget.d.ts +35 -0
  60. package/dist/vanilla/EnquiryFormWidget.js +425 -0
  61. package/dist/vanilla/client/MedosClient.d.ts +2 -4
  62. package/dist/vanilla/components/ContactInformationStep.d.ts +13 -0
  63. package/dist/vanilla/components/ContactPreferenceStep.d.ts +9 -0
  64. package/dist/vanilla/components/EnquiryForm.d.ts +7 -0
  65. package/dist/vanilla/components/InquiryDetailsStep.d.ts +10 -0
  66. package/dist/vanilla/components/SuccessStep.d.ts +3 -1
  67. package/dist/vanilla/components/theme-styles.d.ts +12 -0
  68. package/dist/vanilla/components/types.d.ts +0 -1
  69. package/dist/vanilla/core/index.d.ts +4 -0
  70. package/dist/vanilla/core/theme/index.d.ts +3 -0
  71. package/dist/vanilla/core/theme/themes.d.ts +8 -0
  72. package/dist/vanilla/core/theme/types.d.ts +106 -0
  73. package/dist/vanilla/core/theme/utils.d.ts +8 -0
  74. package/dist/vanilla/enquiry-form/index.d.ts +4 -0
  75. package/dist/vanilla/enquiry-form/provider.d.ts +3 -0
  76. package/dist/vanilla/enquiry-form/serialization.d.ts +4 -0
  77. package/dist/vanilla/enquiry-form/types.d.ts +38 -0
  78. package/dist/vanilla/enquiry-form/validation.d.ts +6 -0
  79. package/dist/vanilla/enquiry-widget.js +4650 -0
  80. package/dist/vanilla/index.d.ts +9 -0
  81. package/dist/vanilla/index.js +3 -1
  82. package/dist/vanilla/react/ThemeProvider.d.ts +18 -0
  83. package/dist/vanilla/react/hooks/useTheme.d.ts +1 -0
  84. package/dist/vanilla/react/index.d.ts +5 -0
  85. package/dist/vanilla/services/EnquiryService.d.ts +5 -0
  86. package/dist/vanilla/theme-injector.d.ts +6 -0
  87. package/dist/vanilla/theme-injector.js +44 -0
  88. package/dist/vanilla/vanilla/EnquiryFormWidget.d.ts +35 -0
  89. package/dist/vanilla/vanilla/index.d.ts +3 -1
  90. package/dist/vanilla/vanilla/theme-injector.d.ts +6 -0
  91. package/dist/vanilla/vanilla/widget.d.ts +6 -1
  92. package/dist/vanilla/widget.css +173 -0
  93. package/dist/vanilla/widget.d.ts +6 -1
  94. package/dist/vanilla/widget.js +489 -12
  95. package/package.json +13 -2
package/README.md CHANGED
@@ -1,13 +1,87 @@
1
1
  # Medos SDK
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/medos-sdk.svg)](https://www.npmjs.com/package/medos-sdk)
4
+ [![License](https://img.shields.io/badge/license-UNLICENSED-red.svg)](LICENSE)
5
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.9%2B-blue.svg)](https://www.typescriptlang.org/)
6
+
3
7
  A JavaScript/TypeScript SDK for integrating healthcare appointment booking into your applications. Built for clinics, hospitals, and healthcare platforms.
4
8
 
9
+ ## ✨ Features
10
+
11
+ - 🎨 **Powerful Theming System** - Customize appearance with built-in themes or create your own
12
+ - ⚛️ **React & Vanilla JS** - Works with React or pure JavaScript
13
+ - 🔒 **Secure Authentication** - API key and session token support
14
+ - 📱 **Responsive Design** - Mobile-first, works on all screen sizes
15
+ - 🎯 **TypeScript-First** - Full type safety and IntelliSense support
16
+ - ♿ **Accessible** - WCAG compliant components
17
+
5
18
  ## Installation
6
19
 
7
20
  ```bash
8
21
  npm install medos-sdk
9
22
  ```
10
23
 
24
+ ## Table of Contents
25
+
26
+ - [Quick Start](#quick-start)
27
+ - [Getting Started](#getting-started)
28
+ - [Usage](#usage)
29
+ - [React Integration](#react-integration)
30
+ - [Vanilla JavaScript](#vanilla-javascript--html-integration)
31
+ - [TypeScript](#typescript)
32
+ - [API Reference](#api-reference)
33
+ - [Error Handling](#error-handling)
34
+ - [Requirements](#requirements)
35
+
36
+ ## Quick Start
37
+
38
+ ### React
39
+
40
+ ```tsx
41
+ import { MedosThemeProvider, AppointmentCalender } from "medos-sdk/react";
42
+
43
+ function App() {
44
+ return (
45
+ <MedosThemeProvider theme="modern">
46
+ <AppointmentCalender onError={(err) => console.error(err)} />
47
+ </MedosThemeProvider>
48
+ );
49
+ }
50
+ ```
51
+
52
+ ### Vanilla JavaScript
53
+
54
+ ```javascript
55
+ import { initAppointmentCalendar } from "medos-sdk/vanilla";
56
+
57
+ initAppointmentCalendar({
58
+ containerId: "appointment-widget",
59
+ apiKey: "your-api-key",
60
+ theme: "modern", // or 'default'
61
+ });
62
+ ```
63
+
64
+ ### HTML/CSS/JS
65
+
66
+ ```html
67
+ <!DOCTYPE html>
68
+ <html>
69
+ <head>
70
+ <link rel="stylesheet" href="path/to/widget.css" />
71
+ </head>
72
+ <body>
73
+ <div id="appointment-calendar"></div>
74
+ <script src="path/to/widget.js"></script>
75
+ <script>
76
+ window.MedosAppointmentCalendar.init({
77
+ containerId: "appointment-calendar",
78
+ apiKey: "your-api-key",
79
+ });
80
+ </script>
81
+ </body>
82
+ </html>
83
+ ```
84
+
11
85
  ## Getting Started
12
86
 
13
87
  ### Authentication
@@ -394,7 +468,7 @@ See `dist/vanilla/widget.css` for all available classes.
394
468
 
395
469
  ### Package Exports
396
470
 
397
- The SDK provides multiple entry points:
471
+ The SDK provides multiple entry points for different use cases:
398
472
 
399
473
  - `medos-sdk` - Default export (includes React components)
400
474
  - `medos-sdk/react` - React-specific exports
@@ -402,8 +476,6 @@ The SDK provides multiple entry points:
402
476
  - `medos-sdk/core` - Core services only (no framework dependencies)
403
477
  - `medos-sdk/widget` - Widget bundle with CSS
404
478
 
405
- For more details, see [Vanilla Widget Documentation](./docs/VANILLA_WIDGET.md).
406
-
407
479
  ## API Reference
408
480
 
409
481
  ### MedosClient
@@ -551,18 +623,18 @@ try {
551
623
  - **React:** v19 or higher (only required for React components, not for vanilla widget)
552
624
  - **TypeScript:** v5 or higher (optional)
553
625
 
554
- ## License
555
-
556
- UNLICENSED
557
-
558
626
  ## Support
559
627
 
560
628
  For bug reports and feature requests, please visit our [GitHub Issues](https://github.com/MediLaunch/medos-sdk-react/issues).
561
629
 
630
+ ## License
631
+
632
+ UNLICENSED
633
+
562
634
  ## Author
563
635
 
564
636
  Pooranjoy Bhattacharya
565
637
 
566
638
  ---
567
639
 
568
- _Built for healthcare providers worldwide._
640
+ **Built for healthcare providers worldwide.**
@@ -3,11 +3,9 @@ import { AddressesResponse } from "../services/AppointmentService";
3
3
  import { SendPhoneVerificationOtpPayload, VerifyPhoneVerificationOtpPayload } from "../services/PatientService";
4
4
  interface MedosClientConfig {
5
5
  apiKey: string;
6
- baseURL?: string;
7
6
  }
8
7
  interface MedosClientSessionConfig {
9
8
  sessionToken: string;
10
- baseURL?: string;
11
9
  }
12
10
  declare class MedosClient {
13
11
  private static instance;
@@ -16,8 +14,8 @@ declare class MedosClient {
16
14
  private static isRefreshing;
17
15
  private static pendingRequests;
18
16
  private static initPromise;
19
- static init({ apiKey, baseURL, }: MedosClientConfig): Promise<void>;
20
- static initWithSession({ sessionToken, baseURL, }: MedosClientSessionConfig): Promise<void>;
17
+ static init({ apiKey }: MedosClientConfig): Promise<void>;
18
+ static initWithSession({ sessionToken }: MedosClientSessionConfig): Promise<void>;
21
19
  private static initializeAxiosInstance;
22
20
  static fetchAllAddressesAndDoctors(): Promise<AddressesResponse>;
23
21
  static fetchAppointments(workspaceId: number, addressId: number, doctorId: number, appointmentDate: string): Promise<any[]>;
@@ -3,7 +3,7 @@ import { AuthService } from "../services/AuthService";
3
3
  import { AppointmentService, } from "../services/AppointmentService";
4
4
  import { PatientService, } from "../services/PatientService";
5
5
  class MedosClient {
6
- static async init({ apiKey, baseURL = "https://api.medos.one", }) {
6
+ static async init({ apiKey }) {
7
7
  if (!apiKey) {
8
8
  throw new Error("MedosClient.init() requires 'apiKey'");
9
9
  }
@@ -13,7 +13,7 @@ class MedosClient {
13
13
  this.initPromise = (async () => {
14
14
  try {
15
15
  const sessionToken = await AuthService.init(apiKey);
16
- this.initializeAxiosInstance(sessionToken, baseURL);
16
+ this.initializeAxiosInstance(sessionToken, "https://api.medos.one");
17
17
  }
18
18
  catch (e) {
19
19
  this.initPromise = null;
@@ -22,7 +22,7 @@ class MedosClient {
22
22
  })();
23
23
  return this.initPromise;
24
24
  }
25
- static async initWithSession({ sessionToken, baseURL = "https://api-dev.medapi.in/v1", }) {
25
+ static async initWithSession({ sessionToken }) {
26
26
  if (!sessionToken) {
27
27
  throw new Error("MedosClient.initWithSession() requires 'sessionToken'");
28
28
  }
@@ -31,7 +31,7 @@ class MedosClient {
31
31
  }
32
32
  this.initPromise = (async () => {
33
33
  try {
34
- this.initializeAxiosInstance(sessionToken, baseURL);
34
+ this.initializeAxiosInstance(sessionToken, "https://api.medos.one");
35
35
  }
36
36
  catch (e) {
37
37
  this.initPromise = null;
@@ -8,9 +8,9 @@ import { PhoneVerificationStep } from "./PhoneVerificationStep";
8
8
  import { PatientDetailsStep } from "./PatientDetailsStep";
9
9
  import { SuccessStep } from "./SuccessStep";
10
10
  import { INITIAL_STATE, } from "./types";
11
- import { CONTAINER_STYLES } from "./styles";
12
11
  import { validatePhoneNumber, validateCountryCode } from "./validation";
13
12
  import { formatDateToISO, parsePatientName } from "./utils";
13
+ import { useTheme } from "../react/hooks/useTheme";
14
14
  const appointmentReducer = (state, action) => {
15
15
  switch (action.type) {
16
16
  case "SET_STEP":
@@ -362,5 +362,46 @@ export const AppointmentCalender = ({ onError, }) => {
362
362
  dispatch({ type: "SET_CONSULTATION_CHARGE", payload: charge });
363
363
  dispatch({ type: "SET_STEP", payload: 3 });
364
364
  }, []);
365
- return (_jsx("div", { style: CONTAINER_STYLES.container, children: _jsxs("div", { style: CONTAINER_STYLES.card, children: [_jsx("div", { style: CONTAINER_STYLES.header, children: _jsx("h2", { style: CONTAINER_STYLES.title, children: "Book Appointment" }) }), state.loading && _jsx("div", { style: { marginBottom: 12 }, children: "Loading..." }), state.error && (_jsx("div", { style: CONTAINER_STYLES.errorMessage, children: state.error })), state.step === 0 && (_jsx(DoctorSelectModal, { onCancel: () => dispatch({ type: "SET_STEP", payload: 0 }), onContinue: handleDoctorSelect })), state.step === 1 && (_jsx(AppointmentDateTimeModal, { onlineFee: 500, offlineFee: 300, slots: state.slots, onCancel: goBack, onDateChange: handleDateChange, onContinue: handleDateTimeModalContinue })), state.step === 3 && (_jsx(PhoneVerificationStep, { state: state, dispatch: dispatch, onSendOtp: sendOtp, onVerifyOtp: verifyOtp, onBack: goBack, onContinue: goToNext })), state.step === 4 && (_jsx(PatientDetailsStep, { state: state, dispatch: dispatch, onBack: goBack, onSubmit: submitAppointment })), state.step === 5 && _jsx(SuccessStep, { state: state, onReset: resetForm })] }) }));
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 })] })] }) }));
366
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
+ });
@@ -4,6 +4,7 @@ import { CustomCalendarWithDateSelector } from "./custom-calendar";
4
4
  import { PaymentMethodIcon } from "./Icons/PaymentMethodIcon";
5
5
  import { DateTimeIcon } from "./Icons/Date&TimeIcon";
6
6
  import { ConsultationTypeIcon } from "./Icons/ConsultationType";
7
+ import { useTheme } from "../react/hooks/useTheme";
7
8
  const MONTHS = [
8
9
  "January",
9
10
  "February",
@@ -39,6 +40,7 @@ const formatDate = (date) => {
39
40
  return `${day}${getOrdinalSuffix(day)} ${month} ${year}`;
40
41
  };
41
42
  export const AppointmentDateTimeModal = ({ onlineFee, offlineFee, slots, onCancel, onContinue, onDateChange, }) => {
43
+ const theme = useTheme();
42
44
  const [mode, setMode] = useState("OFFLINE");
43
45
  const [consultationCharge, setConsultationCharge] = useState("");
44
46
  const [paymentMode, setPaymentMode] = useState("CASH");
@@ -72,47 +74,56 @@ export const AppointmentDateTimeModal = ({ onlineFee, offlineFee, slots, onCance
72
74
  const isSlotSelected = useCallback((slot) => selectedSlot?.start === slot.start && selectedSlot?.end === slot.end, [selectedSlot]);
73
75
  const formattedDate = useMemo(() => formatDate(selectedDate), [selectedDate]);
74
76
  const isFormValid = selectedDate && selectedSlot && consultationCharge;
75
- return (_jsxs("div", { style: STYLES.modalWrapper, children: [_jsx(ConsultationTypeSection, { mode: mode, onlineFee: onlineFee, offlineFee: offlineFee, onModeChange: setMode }), _jsx(ChargesSection, { charge: consultationCharge }), _jsx(DateTimeSection, { selectedDate: selectedDate, formattedDate: formattedDate, slots: slots, selectedSlot: selectedSlot, onDateSelect: handleDateSelect, onSlotSelect: setSelectedSlot, isSlotSelected: isSlotSelected }), _jsx(FooterSection, { onCancel: onCancel, onContinue: handleContinue, isValid: !!isFormValid })] }));
77
+ const STYLES = getStyles(theme);
78
+ return (_jsxs("div", { style: STYLES.modalWrapper, children: [_jsx(ConsultationTypeSection, { mode: mode, onlineFee: onlineFee, offlineFee: offlineFee, onModeChange: setMode, STYLES: STYLES }), _jsx(ChargesSection, { charge: consultationCharge, STYLES: STYLES }), _jsx(DateTimeSection, { selectedDate: selectedDate, formattedDate: formattedDate, slots: slots, selectedSlot: selectedSlot, onDateSelect: handleDateSelect, onSlotSelect: setSelectedSlot, isSlotSelected: isSlotSelected, STYLES: STYLES }), _jsx(FooterSection, { onCancel: onCancel, onContinue: handleContinue, isValid: !!isFormValid, STYLES: STYLES })] }));
76
79
  };
77
- const ConsultationTypeSection = ({ mode, onlineFee, offlineFee, onModeChange }) => (_jsxs("div", { style: STYLES.sectionCard, children: [_jsxs("div", { style: STYLES.sectionHeader, children: [_jsx(ConsultationTypeIcon, {}), _jsx("span", { style: STYLES.sectionTitle, children: "Consultation Type" })] }), _jsxs("div", { style: STYLES.sectionBody, children: [_jsx("p", { style: STYLES.label, children: "Consultation Mode" }), _jsx("div", { style: STYLES.modeContainer, children: ["ONLINE", "OFFLINE"].map((item) => {
80
+ const ConsultationTypeSection = ({ mode, onlineFee, offlineFee, onModeChange, STYLES }) => (_jsxs("div", { style: STYLES.sectionCard, children: [_jsxs("div", { style: STYLES.sectionHeader, children: [_jsx(ConsultationTypeIcon, {}), _jsx("span", { style: STYLES.sectionTitle, children: "Consultation Type" })] }), _jsxs("div", { style: STYLES.sectionBody, children: [_jsx("p", { style: STYLES.label, children: "Consultation Mode" }), _jsx("div", { style: STYLES.modeContainer, children: ["ONLINE", "OFFLINE"].map((item) => {
78
81
  const disabled = (item === "ONLINE" && (!onlineFee || onlineFee <= 0)) ||
79
82
  (item === "OFFLINE" && (!offlineFee || offlineFee <= 0));
80
83
  return (_jsxs("label", { style: { ...STYLES.radioLabel, opacity: disabled ? 0.5 : 1 }, children: [_jsx("input", { type: "radio", name: "mode", value: item, checked: mode === item, disabled: disabled, onChange: () => !disabled && onModeChange(item), style: STYLES.radioInput }), item === "ONLINE" ? "Online" : "Offline"] }, item));
81
84
  }) })] })] }));
82
- const ChargesSection = ({ charge }) => (_jsxs("div", { style: STYLES.sectionCard, children: [_jsxs("div", { style: STYLES.sectionHeader, children: [_jsx(PaymentMethodIcon, {}), _jsx("span", { style: STYLES.sectionTitle, children: "Charges & Mode of Payment" })] }), _jsx("div", { style: STYLES.sectionBody, children: _jsxs("span", { style: STYLES.rupee, children: ["\u20B9 ", charge] }) })] }));
83
- const DateTimeSection = ({ selectedDate, formattedDate, slots, selectedSlot, onDateSelect, onSlotSelect, isSlotSelected, }) => (_jsxs("div", { style: STYLES.sectionCard, children: [_jsxs("div", { style: STYLES.sectionHeader, children: [_jsx(DateTimeIcon, {}), _jsx("span", { style: STYLES.sectionTitle, children: "Date & Time" })] }), _jsx("div", { style: STYLES.sectionBody, children: _jsxs("div", { style: STYLES.dateTimeContainer, children: [_jsx("div", { style: STYLES.calendarBox, children: _jsx(CustomCalendarWithDateSelector, { selectedDate: selectedDate, onSelect: onDateSelect, pastDisabled: true }) }), _jsxs("div", { style: STYLES.timesContainer, children: [_jsxs("p", { style: STYLES.timesLabel, children: ["Available times ", _jsx("br", {}), _jsx("span", { style: STYLES.dateLabel, children: formattedDate })] }), slots.length === 0 ? (_jsx("p", { style: STYLES.noSlots, children: "No available slots" })) : (_jsx("div", { style: STYLES.slotGrid, children: slots.map((slot) => (_jsx(SlotButton, { slot: slot, isSelected: isSlotSelected(slot), onSelect: onSlotSelect }, slot.id))) }))] })] }) })] }));
85
+ const ChargesSection = ({ charge, STYLES }) => (_jsxs("div", { style: STYLES.sectionCard, children: [_jsxs("div", { style: STYLES.sectionHeader, children: [_jsx(PaymentMethodIcon, {}), _jsx("span", { style: STYLES.sectionTitle, children: "Charges & Mode of Payment" })] }), _jsx("div", { style: STYLES.sectionBody, children: _jsxs("span", { style: STYLES.rupee, children: ["\u20B9 ", charge] }) })] }));
86
+ const DateTimeSection = ({ selectedDate, formattedDate, slots, selectedSlot, onDateSelect, onSlotSelect, isSlotSelected, STYLES, }) => (_jsxs("div", { style: STYLES.sectionCard, children: [_jsxs("div", { style: STYLES.sectionHeader, children: [_jsx(DateTimeIcon, {}), _jsx("span", { style: STYLES.sectionTitle, children: "Date & Time" })] }), _jsx("div", { style: STYLES.sectionBody, children: _jsxs("div", { style: STYLES.dateTimeContainer, children: [_jsx("div", { style: STYLES.calendarBox, children: _jsx(CustomCalendarWithDateSelector, { selectedDate: selectedDate, onSelect: onDateSelect, pastDisabled: true }) }), _jsxs("div", { style: STYLES.timesContainer, children: [_jsxs("p", { style: STYLES.timesLabel, children: ["Available times ", _jsx("br", {}), _jsx("span", { style: STYLES.dateLabel, children: formattedDate })] }), slots.length === 0 ? (_jsx("p", { style: STYLES.noSlots, children: "No available slots" })) : (_jsx("div", { style: STYLES.slotGrid, children: slots.map((slot) => (_jsx(SlotButton, { slot: slot, isSelected: isSlotSelected(slot), onSelect: onSlotSelect }, slot.id))) }))] })] }) })] }));
84
87
  const SlotButton = ({ slot, isSelected, onSelect }) => {
85
88
  const startTime = useMemo(() => new Date(slot.start).toLocaleTimeString([], {
86
89
  hour: "2-digit",
87
90
  minute: "2-digit",
88
91
  }), [slot.start]);
92
+ const theme = useTheme();
93
+ const STYLES = getStyles(theme);
89
94
  return (_jsx("button", { onClick: () => onSelect(slot), style: {
90
95
  ...STYLES.slotButton,
91
- background: isSelected ? "#009b4d" : "#fff",
92
- color: isSelected ? "#fff" : "#009b4d",
96
+ background: isSelected
97
+ ? theme.colors.secondary
98
+ : theme.colors.background,
99
+ color: isSelected
100
+ ? theme.colors.textOnSecondary
101
+ : theme.colors.secondary,
102
+ borderColor: theme.colors.secondary,
93
103
  }, children: startTime }));
94
104
  };
95
- const FooterSection = ({ onCancel, onContinue, isValid }) => (_jsxs("div", { style: STYLES.footer, children: [_jsx("button", { style: STYLES.backBtn, onClick: onCancel, children: "Back" }), _jsx("button", { style: { ...STYLES.continueBtn, opacity: isValid ? 1 : 0.6 }, onClick: onContinue, disabled: !isValid, children: "Continue" })] }));
96
- const STYLES = {
105
+ const FooterSection = ({ onCancel, onContinue, isValid, STYLES }) => (_jsxs("div", { style: STYLES.footer, children: [_jsx("button", { style: STYLES.backBtn, onClick: onCancel, children: "Back" }), _jsx("button", { style: { ...STYLES.continueBtn, opacity: isValid ? 1 : 0.6 }, onClick: onContinue, disabled: !isValid, children: "Continue" })] }));
106
+ const getStyles = (theme) => ({
97
107
  modalWrapper: {
98
- backgroundColor: "#fff",
99
- borderRadius: 12,
108
+ backgroundColor: theme.colors.background,
109
+ borderRadius: theme.radii.lg,
100
110
  padding: "20px 24px 24px 24px",
101
111
  maxWidth: 800,
102
112
  margin: "0 auto",
103
- fontFamily: "'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial",
104
- color: "#111827",
113
+ fontFamily: theme.typography.fontFamily,
114
+ color: theme.colors.text,
105
115
  boxSizing: "border-box",
106
116
  },
107
117
  sectionCard: {
108
- border: "1px solid #e5e7eb",
109
- borderRadius: 10,
118
+ border: `1px solid ${theme.colors.border}`,
119
+ borderRadius: theme.radii.lg,
110
120
  marginBottom: 20,
111
121
  position: "relative",
112
122
  },
113
123
  sectionHeader: {
114
- background: "#f9fafb",
115
- borderBottom: "1px solid #e5e7eb",
124
+ background: theme.colors.primary,
125
+ color: theme.colors.textOnPrimary,
126
+ borderBottom: `1px solid ${theme.colors.border}`,
116
127
  padding: "12px 16px",
117
128
  display: "flex",
118
129
  alignItems: "center",
@@ -141,7 +152,7 @@ const STYLES = {
141
152
  radioInput: {
142
153
  width: 18,
143
154
  height: 18,
144
- accentColor: "#218838",
155
+ accentColor: theme.colors.secondary,
145
156
  cursor: "pointer",
146
157
  },
147
158
  rupee: {
@@ -151,15 +162,18 @@ const STYLES = {
151
162
  },
152
163
  dateTimeContainer: { display: "flex", gap: 24 },
153
164
  calendarBox: {
154
- border: "1px solid #e5e7eb",
155
- borderRadius: 10,
165
+ border: `1px solid ${theme.colors.border}`,
166
+ borderRadius: theme.radii.lg,
156
167
  padding: 8,
157
168
  },
158
169
  timesContainer: { flexGrow: 1 },
159
170
  timesLabel: { fontWeight: 500 },
160
- dateLabel: { color: "#6b7280", fontSize: 13 },
171
+ dateLabel: {
172
+ color: theme.colors.textSecondary,
173
+ fontSize: 13,
174
+ },
161
175
  noSlots: {
162
- color: "#6b7280",
176
+ color: theme.colors.textSecondary,
163
177
  fontSize: 14,
164
178
  marginTop: 8,
165
179
  },
@@ -170,9 +184,9 @@ const STYLES = {
170
184
  marginTop: 12,
171
185
  },
172
186
  slotButton: {
173
- borderRadius: 8,
187
+ borderRadius: theme.radii.md,
174
188
  padding: "8px 10px",
175
- border: "1px solid #009b4d",
189
+ border: `1px solid ${theme.colors.secondary}`,
176
190
  fontSize: 13,
177
191
  fontWeight: 500,
178
192
  cursor: "pointer",
@@ -186,21 +200,21 @@ const STYLES = {
186
200
  gap: 12,
187
201
  },
188
202
  backBtn: {
189
- border: "1px solid #218838",
190
- background: "#fff",
191
- color: "#218838",
192
- borderRadius: 6,
203
+ border: `1px solid ${theme.colors.primary}`,
204
+ background: theme.colors.background,
205
+ color: theme.colors.primary,
206
+ borderRadius: theme.radii.md,
193
207
  fontWeight: 600,
194
208
  padding: "8px 20px",
195
209
  cursor: "pointer",
196
210
  },
197
211
  continueBtn: {
198
- background: "#218838",
199
- color: "#fff",
212
+ background: theme.colors.secondary,
213
+ color: theme.colors.textOnSecondary,
200
214
  border: "none",
201
- borderRadius: 6,
215
+ borderRadius: theme.radii.md,
202
216
  fontWeight: 600,
203
217
  padding: "8px 20px",
204
218
  cursor: "pointer",
205
219
  },
206
- };
220
+ });
@@ -0,0 +1,13 @@
1
+ import React from "react";
2
+ export interface ContactInformationStepProps {
3
+ patientName: string;
4
+ patientEmail: string;
5
+ countryCode: string;
6
+ patientPhone: string;
7
+ onNameChange: (value: string) => void;
8
+ onEmailChange: (value: string) => void;
9
+ onCountryCodeChange: (value: string) => void;
10
+ onPhoneChange: (value: string) => void;
11
+ onNext: () => void;
12
+ }
13
+ export declare const ContactInformationStep: React.FC<ContactInformationStepProps>;
@@ -0,0 +1,14 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useTheme } from "../react/hooks/useTheme";
3
+ import { getContainerStyles, getPhoneVerifyStyles, getButtonStyles, } from "./theme-styles";
4
+ export const ContactInformationStep = ({ patientName, patientEmail, countryCode, patientPhone, onNameChange, onEmailChange, onCountryCodeChange, onPhoneChange, onNext, }) => {
5
+ const theme = useTheme();
6
+ const PHONE_VERIFY_STYLES = getPhoneVerifyStyles(theme);
7
+ const BUTTON_STYLES = getButtonStyles(theme);
8
+ const CONTAINER_STYLES = getContainerStyles(theme);
9
+ return (_jsxs("div", { children: [_jsxs("div", { style: { marginBottom: "16px" }, children: [_jsx("label", { style: PHONE_VERIFY_STYLES.label, children: "Name" }), _jsx("input", { type: "text", value: patientName, onChange: (e) => onNameChange(e.target.value), placeholder: "Enter your full name", style: PHONE_VERIFY_STYLES.phoneInput })] }), _jsxs("div", { style: { marginBottom: "16px" }, children: [_jsx("label", { style: PHONE_VERIFY_STYLES.label, children: "Email" }), _jsx("input", { type: "email", value: patientEmail, onChange: (e) => onEmailChange(e.target.value), placeholder: "Enter your email", style: PHONE_VERIFY_STYLES.phoneInput })] }), _jsxs("div", { style: { marginBottom: "16px" }, children: [_jsx("label", { style: PHONE_VERIFY_STYLES.label, children: "Phone Number" }), _jsxs("div", { style: PHONE_VERIFY_STYLES.phoneInputContainer, children: [_jsx("input", { type: "text", value: countryCode, onChange: (e) => onCountryCodeChange(e.target.value), placeholder: "+91", style: {
10
+ ...PHONE_VERIFY_STYLES.phoneInput,
11
+ width: "80px",
12
+ flex: "none",
13
+ } }), _jsx("input", { type: "tel", value: patientPhone, onChange: (e) => onPhoneChange(e.target.value), placeholder: "Enter phone number", style: PHONE_VERIFY_STYLES.phoneInput })] })] }), _jsx("div", { style: CONTAINER_STYLES.actions, children: _jsx("button", { onClick: onNext, style: BUTTON_STYLES.primary, children: "Next" }) })] }));
14
+ };
@@ -0,0 +1,9 @@
1
+ import React from "react";
2
+ export interface ContactPreferenceStepProps {
3
+ preferredContactMethod: "PHONE" | "EMAIL" | "BOTH";
4
+ onContactMethodChange: (method: "PHONE" | "EMAIL" | "BOTH") => void;
5
+ onBack: () => void;
6
+ onSubmit: () => void;
7
+ isLoading?: boolean;
8
+ }
9
+ export declare const ContactPreferenceStep: React.FC<ContactPreferenceStepProps>;
@@ -0,0 +1,16 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useTheme } from "../react/hooks/useTheme";
3
+ import { getPhoneVerifyStyles, getButtonStyles, getContainerStyles, } from "./theme-styles";
4
+ export const ContactPreferenceStep = ({ preferredContactMethod, onContactMethodChange, onBack, onSubmit, isLoading = false, }) => {
5
+ const theme = useTheme();
6
+ const PHONE_VERIFY_STYLES = getPhoneVerifyStyles(theme);
7
+ const BUTTON_STYLES = getButtonStyles(theme);
8
+ const CONTAINER_STYLES = getContainerStyles(theme);
9
+ return (_jsxs("div", { children: [_jsxs("div", { style: { marginBottom: "16px" }, children: [_jsx("label", { style: PHONE_VERIFY_STYLES.label, children: "Preferred Contact Method" }), _jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "8px" }, children: [_jsxs("label", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [_jsx("input", { type: "radio", name: "contactMethod", value: "PHONE", checked: preferredContactMethod === "PHONE", onChange: (e) => onContactMethodChange(e.target.value) }), "Phone"] }), _jsxs("label", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [_jsx("input", { type: "radio", name: "contactMethod", value: "EMAIL", checked: preferredContactMethod === "EMAIL", onChange: (e) => onContactMethodChange(e.target.value) }), "Email"] }), _jsxs("label", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [_jsx("input", { type: "radio", name: "contactMethod", value: "BOTH", checked: preferredContactMethod === "BOTH", onChange: (e) => onContactMethodChange(e.target.value) }), "Both"] })] })] }), _jsxs("div", { style: CONTAINER_STYLES.actions, children: [_jsx("button", { onClick: onBack, style: BUTTON_STYLES.secondary, children: "Back" }), _jsx("button", { onClick: onSubmit, disabled: isLoading, style: {
10
+ ...BUTTON_STYLES.primary,
11
+ backgroundColor: isLoading
12
+ ? "#ccc"
13
+ : BUTTON_STYLES.primary.backgroundColor,
14
+ cursor: isLoading ? "not-allowed" : "pointer",
15
+ }, children: isLoading ? "Submitting..." : "Submit" })] })] }));
16
+ };
@@ -1,8 +1,10 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useState, useEffect } from "react";
3
- import { AppointmentService } from "../services/AppointmentService";
3
+ import { AppointmentService, } from "../services/AppointmentService";
4
4
  import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem, } from "./uiComponents/SelectDropdown";
5
+ import { useTheme } from "../react/hooks/useTheme";
5
6
  export const DoctorSelectModal = ({ onCancel, onContinue, }) => {
7
+ const theme = useTheme();
6
8
  const [addresses, setAddresses] = useState([]);
7
9
  const [addressDoctorsMap, setAddressDoctorsMap] = useState({});
8
10
  const [selectedAddressId, setSelectedAddressId] = useState("");
@@ -19,24 +21,30 @@ export const DoctorSelectModal = ({ onCancel, onContinue, }) => {
19
21
  setAddressDoctorsMap(map);
20
22
  })();
21
23
  }, []);
22
- const doctorsForAddress = selectedAddressId ? addressDoctorsMap[Number(selectedAddressId)] || [] : [];
24
+ const doctorsForAddress = selectedAddressId
25
+ ? addressDoctorsMap[Number(selectedAddressId)] || []
26
+ : [];
23
27
  const canContinue = !!selectedAddressId && !!selectedDoctorId;
24
- return (_jsx("div", { style: { border: "1px solid #e5e7eb", borderRadius: "2px" }, children: _jsxs("div", { style: styles.modalWrapper, children: [_jsx("div", { style: styles.headerWrapper, children: _jsx("h3", { style: styles.title, children: "Location & Doctor" }) }), _jsx("div", { style: { borderBottom: "1px solid #e5e7eb" } }), _jsxs("div", { style: styles.contentWrapper, children: [_jsxs("div", { style: styles.fieldGroup, children: [_jsxs("label", { style: styles.label, children: ["Preferred Location ", _jsx("span", { style: styles.mandatory, children: "*" })] }), _jsxs(Select, { value: selectedAddressId, onValueChange: (value) => {
28
+ const styles = getStyles(theme);
29
+ return (_jsx("div", { style: {
30
+ border: `1px solid ${theme.colors.border}`,
31
+ borderRadius: "2px",
32
+ }, children: _jsxs("div", { style: styles.modalWrapper, children: [_jsx("div", { style: styles.headerWrapper, children: _jsx("h3", { style: styles.title, children: "Location & Doctor" }) }), _jsx("div", { style: { borderBottom: `1px solid ${theme.colors.border}` } }), _jsxs("div", { style: styles.contentWrapper, children: [_jsxs("div", { style: styles.fieldGroup, children: [_jsxs("label", { style: styles.label, children: ["Preferred Location ", _jsx("span", { style: styles.mandatory, children: "*" })] }), _jsxs(Select, { value: selectedAddressId, onValueChange: (value) => {
25
33
  setSelectedAddressId(value);
26
34
  setSelectedDoctorId("");
27
- }, children: [_jsx(SelectTrigger, { style: { width: '100%' }, children: _jsx(SelectValue, { placeholder: "Select Address" }) }), _jsx(SelectContent, { children: addresses.map((a) => (_jsx(SelectItem, { value: String(a.id), children: a.completeAddress || a.label || `Address ${a.id}` }, a.id))) })] })] }), _jsxs("div", { style: styles.fieldGroup, children: [_jsxs("label", { style: styles.label, children: ["Preferred Doctor ", _jsx("span", { style: styles.mandatory, children: "*" })] }), _jsxs(Select, { value: selectedDoctorId, onValueChange: setSelectedDoctorId, disabled: !selectedAddressId, children: [_jsx(SelectTrigger, { style: { width: '100%' }, children: _jsx(SelectValue, { placeholder: "Select Doctor" }) }), _jsx(SelectContent, { children: doctorsForAddress.length > 0 ? (doctorsForAddress.map((d) => (_jsxs(SelectItem, { value: String(d.id), children: [d.name, " ", d.specialty ? `- ${d.specialty}` : ""] }, d.id)))) : (_jsx(SelectItem, { value: "no-doctors", disabled: true, children: "No doctors available" })) })] })] }), _jsxs("div", { style: styles.fieldGroup, children: [_jsxs("label", { style: styles.label, children: ["Chief Complaint ", _jsx("span", { style: styles.optional, children: "(optional)" })] }), _jsx("textarea", { style: styles.textarea, placeholder: "Enter Chief Complaint or Appointment Notes", value: appointmentNotes, onChange: (e) => setAppointmentNotes(e.target.value) })] }), _jsx("div", { style: styles.footer, children: _jsx("button", { style: {
35
+ }, children: [_jsx(SelectTrigger, { style: { width: "100%" }, children: _jsx(SelectValue, { placeholder: "Select Address" }) }), _jsx(SelectContent, { children: addresses.map((a) => (_jsx(SelectItem, { value: String(a.id), children: a.completeAddress || a.label || `Address ${a.id}` }, a.id))) })] })] }), _jsxs("div", { style: styles.fieldGroup, children: [_jsxs("label", { style: styles.label, children: ["Preferred Doctor ", _jsx("span", { style: styles.mandatory, children: "*" })] }), _jsxs(Select, { value: selectedDoctorId, onValueChange: setSelectedDoctorId, disabled: !selectedAddressId, children: [_jsx(SelectTrigger, { style: { width: "100%" }, children: _jsx(SelectValue, { placeholder: "Select Doctor" }) }), _jsx(SelectContent, { children: doctorsForAddress.length > 0 ? (doctorsForAddress.map((d) => (_jsxs(SelectItem, { value: String(d.id), children: [d.name, " ", d.specialty ? `- ${d.specialty}` : ""] }, d.id)))) : (_jsx(SelectItem, { value: "no-doctors", disabled: true, children: "No doctors available" })) })] })] }), _jsxs("div", { style: styles.fieldGroup, children: [_jsxs("label", { style: styles.label, children: ["Chief Complaint ", _jsx("span", { style: styles.optional, children: "(optional)" })] }), _jsx("textarea", { style: styles.textarea, placeholder: "Enter Chief Complaint or Appointment Notes", value: appointmentNotes, onChange: (e) => setAppointmentNotes(e.target.value) })] }), _jsx("div", { style: styles.footer, children: _jsx("button", { style: {
28
36
  ...styles.continueBtn,
29
37
  opacity: canContinue ? 1 : 0.6,
30
38
  }, disabled: !canContinue, onClick: () => onContinue(Number(selectedAddressId), Number(selectedDoctorId), appointmentNotes), children: "Next" }) })] })] }) }));
31
39
  };
32
- const styles = {
40
+ const getStyles = (theme) => ({
33
41
  modalWrapper: {
34
- backgroundColor: "#fff",
42
+ backgroundColor: theme.colors.background,
35
43
  borderRadius: 12,
36
44
  maxWidth: 840,
37
45
  margin: "0 auto",
38
- fontFamily: "'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial",
39
- color: "#111827",
46
+ fontFamily: theme.typography.fontFamily,
47
+ color: theme.colors.text,
40
48
  },
41
49
  headerWrapper: {
42
50
  padding: "20px 24px 10px 24px",
@@ -47,12 +55,17 @@ const styles = {
47
55
  title: { fontSize: 20, fontWeight: 600, margin: 0 },
48
56
  fieldGroup: { marginBottom: 18 },
49
57
  label: { fontSize: 15, fontWeight: 500, marginBottom: 6, display: "block" },
50
- mandatory: { color: "red", marginLeft: 4 },
51
- optional: { color: "#9ca3af", fontWeight: 400, marginLeft: 4, fontSize: 13 },
58
+ mandatory: { color: theme.colors.error, marginLeft: 4 },
59
+ optional: {
60
+ color: theme.colors.textSecondary,
61
+ fontWeight: 400,
62
+ marginLeft: 4,
63
+ fontSize: 13,
64
+ },
52
65
  textarea: {
53
66
  width: "100%",
54
- border: "1px solid #e5e7eb",
55
- borderRadius: 6,
67
+ border: `1px solid ${theme.colors.border}`,
68
+ borderRadius: theme.radii.md,
56
69
  minHeight: 80,
57
70
  padding: "10px",
58
71
  fontSize: 14,
@@ -67,14 +80,14 @@ const styles = {
67
80
  alignItems: "center",
68
81
  gap: 10,
69
82
  },
70
- brand: { color: "#009b4d", fontWeight: 600 },
83
+ brand: { color: theme.colors.primary, fontWeight: 600 },
71
84
  continueBtn: {
72
- background: "#218838",
73
- color: "#fff",
85
+ background: theme.colors.secondary,
86
+ color: theme.colors.textOnSecondary,
74
87
  border: "none",
75
- borderRadius: 6,
88
+ borderRadius: theme.radii.md,
76
89
  fontWeight: 600,
77
90
  padding: "8px 22px",
78
91
  cursor: "pointer",
79
92
  },
80
- };
93
+ });
@@ -0,0 +1,7 @@
1
+ import React from "react";
2
+ import { EnquirySubmission } from "../enquiry-form/types";
3
+ export interface EnquiryFormProps {
4
+ onSuccess?: (enquiry: EnquirySubmission) => void;
5
+ onError?: (error: Error) => void;
6
+ }
7
+ export declare const EnquiryForm: React.FC<EnquiryFormProps>;