@blackcode_sa/metaestetics-api 1.12.22 → 1.12.24

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.
@@ -1855,8 +1855,406 @@ var BaseMailingService = class {
1855
1855
  };
1856
1856
 
1857
1857
  // src/admin/mailing/appointment/appointment.mailing.service.ts
1858
- var patientAppointmentConfirmedTemplate = "<h1>Appointment Confirmed</h1><p>Dear {{patientName}},</p><p>Your appointment for {{procedureName}} on {{appointmentDate}} at {{appointmentTime}} with {{practitionerName}} at {{clinicName}} has been confirmed.</p><p>Thank you!</p>";
1859
- var clinicAppointmentRequestedTemplate = "<h1>New Appointment Request</h1><p>Hello {{clinicName}} Admin,</p><p>A new appointment for {{procedureName}} has been requested by {{patientName}} for {{appointmentDate}} at {{appointmentTime}} with {{practitionerName}}.</p><p>Please review and confirm in the admin panel.</p>";
1858
+ var patientAppointmentConfirmedTemplate = `
1859
+ <!DOCTYPE html>
1860
+ <html lang="en">
1861
+ <head>
1862
+ <meta charset="UTF-8">
1863
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
1864
+ <title>Appointment Confirmed</title>
1865
+ <style>
1866
+ body {
1867
+ margin: 0;
1868
+ padding: 0;
1869
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
1870
+ background: linear-gradient(135deg, #a48a76 0%, #67574A 100%);
1871
+ min-height: 100vh;
1872
+ }
1873
+ .email-container {
1874
+ max-width: 600px;
1875
+ margin: 0 auto;
1876
+ background: #ffffff;
1877
+ border-radius: 20px;
1878
+ overflow: hidden;
1879
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
1880
+ margin-top: 40px;
1881
+ margin-bottom: 40px;
1882
+ }
1883
+ .header {
1884
+ background: linear-gradient(135deg, #a48a76 0%, #67574A 100%);
1885
+ padding: 40px 30px;
1886
+ text-align: center;
1887
+ color: white;
1888
+ }
1889
+ .header h1 {
1890
+ margin: 0;
1891
+ font-size: 28px;
1892
+ font-weight: 300;
1893
+ letter-spacing: 1px;
1894
+ }
1895
+ .header .subtitle {
1896
+ margin: 10px 0 0 0;
1897
+ font-size: 16px;
1898
+ opacity: 0.9;
1899
+ font-weight: 300;
1900
+ }
1901
+ .content {
1902
+ padding: 40px 30px;
1903
+ }
1904
+ .greeting {
1905
+ font-size: 18px;
1906
+ color: #333;
1907
+ margin-bottom: 25px;
1908
+ font-weight: 400;
1909
+ }
1910
+ .appointment-card {
1911
+ background: linear-gradient(135deg, #f8f6f5 0%, #f5f3f2 100%);
1912
+ border-radius: 15px;
1913
+ padding: 30px;
1914
+ margin: 25px 0;
1915
+ border-left: 5px solid #a48a76;
1916
+ }
1917
+ .appointment-title {
1918
+ font-size: 20px;
1919
+ color: #a48a76;
1920
+ margin-bottom: 20px;
1921
+ font-weight: 600;
1922
+ }
1923
+ .appointment-details {
1924
+ display: grid;
1925
+ gap: 15px;
1926
+ }
1927
+ .detail-row {
1928
+ display: flex;
1929
+ align-items: center;
1930
+ padding: 8px 0;
1931
+ }
1932
+ .detail-label {
1933
+ font-weight: 600;
1934
+ color: #555;
1935
+ min-width: 120px;
1936
+ font-size: 14px;
1937
+ }
1938
+ .detail-value {
1939
+ color: #333;
1940
+ font-size: 16px;
1941
+ font-weight: 500;
1942
+ }
1943
+ .procedure-name {
1944
+ color: #67574A;
1945
+ font-weight: 600;
1946
+ }
1947
+ .clinic-name {
1948
+ color: #a48a76;
1949
+ font-weight: 600;
1950
+ }
1951
+ .success-icon {
1952
+ text-align: center;
1953
+ margin: 20px 0;
1954
+ font-size: 48px;
1955
+ }
1956
+ .footer {
1957
+ background: #f8f9fa;
1958
+ padding: 25px 30px;
1959
+ text-align: center;
1960
+ color: #666;
1961
+ font-size: 14px;
1962
+ border-top: 1px solid #eee;
1963
+ }
1964
+ .logo {
1965
+ font-size: 24px;
1966
+ font-weight: 700;
1967
+ color: white;
1968
+ margin-bottom: 5px;
1969
+ }
1970
+ .divider {
1971
+ height: 2px;
1972
+ background: linear-gradient(90deg, #a48a76, #67574A);
1973
+ margin: 25px 0;
1974
+ border-radius: 1px;
1975
+ }
1976
+ .thank-you {
1977
+ background: linear-gradient(135deg, #f0ede8 0%, #ebe6e0 100%);
1978
+ border-radius: 15px;
1979
+ padding: 25px;
1980
+ margin: 25px 0;
1981
+ text-align: center;
1982
+ border-left: 5px solid #4CAF50;
1983
+ }
1984
+ </style>
1985
+ </head>
1986
+ <body>
1987
+ <div class="email-container">
1988
+ <div class="header">
1989
+ <div class="logo">MetaEstetics</div>
1990
+ <h1>Appointment Confirmed</h1>
1991
+ <div class="subtitle">Your Beauty Journey Awaits</div>
1992
+ </div>
1993
+
1994
+ <div class="content">
1995
+ <div class="success-icon">\u2728</div>
1996
+
1997
+ <div class="greeting">
1998
+ Dear <strong>{{patientName}}</strong>,
1999
+ </div>
2000
+
2001
+ <p style="color: #555; font-size: 16px; line-height: 1.6; margin-bottom: 25px;">
2002
+ We're delighted to confirm your appointment! Your journey to enhanced beauty and confidence is just around the corner.
2003
+ </p>
2004
+
2005
+ <div class="appointment-card">
2006
+ <div class="appointment-title">\u{1F4C5} Your Appointment Details</div>
2007
+ <div class="appointment-details">
2008
+ <div class="detail-row">
2009
+ <div class="detail-label">Procedure:</div>
2010
+ <div class="detail-value procedure-name">{{procedureName}}</div>
2011
+ </div>
2012
+ <div class="detail-row">
2013
+ <div class="detail-label">Date:</div>
2014
+ <div class="detail-value">{{appointmentDate}}</div>
2015
+ </div>
2016
+ <div class="detail-row">
2017
+ <div class="detail-label">Time:</div>
2018
+ <div class="detail-value">{{appointmentTime}}</div>
2019
+ </div>
2020
+ <div class="detail-row">
2021
+ <div class="detail-label">Practitioner:</div>
2022
+ <div class="detail-value">{{practitionerName}}</div>
2023
+ </div>
2024
+ <div class="detail-row">
2025
+ <div class="detail-label">Location:</div>
2026
+ <div class="detail-value clinic-name">{{clinicName}}</div>
2027
+ </div>
2028
+ </div>
2029
+ </div>
2030
+
2031
+ <div class="divider"></div>
2032
+
2033
+ <div class="thank-you">
2034
+ <h3 style="margin: 0 0 10px 0; color: #4caf50; font-weight: 600;">Thank You for Choosing MetaEstetics!</h3>
2035
+ <p style="margin: 0; color: #555; font-size: 14px;">
2036
+ We look forward to providing you with exceptional care and outstanding results.
2037
+ </p>
2038
+ </div>
2039
+
2040
+ <p style="color: #555; font-size: 14px; line-height: 1.6; margin-top: 25px;">
2041
+ <strong>Important:</strong> Please arrive 15 minutes early for your appointment. If you need to reschedule or have any questions, please contact us as soon as possible.
2042
+ </p>
2043
+ </div>
2044
+
2045
+ <div class="footer">
2046
+ <p style="margin: 0 0 10px 0;">
2047
+ <strong>MetaEstetics</strong> - Premium Aesthetic Services
2048
+ </p>
2049
+ <p style="margin: 0; font-size: 12px; color: #999;">
2050
+ This is an automated message. Please do not reply to this email.
2051
+ </p>
2052
+ </div>
2053
+ </div>
2054
+ </body>
2055
+ </html>
2056
+ `;
2057
+ var clinicAppointmentRequestedTemplate = `
2058
+ <!DOCTYPE html>
2059
+ <html lang="en">
2060
+ <head>
2061
+ <meta charset="UTF-8">
2062
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
2063
+ <title>New Appointment Request</title>
2064
+ <style>
2065
+ body {
2066
+ margin: 0;
2067
+ padding: 0;
2068
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
2069
+ background: linear-gradient(135deg, #a48a76 0%, #67574A 100%);
2070
+ min-height: 100vh;
2071
+ }
2072
+ .email-container {
2073
+ max-width: 600px;
2074
+ margin: 0 auto;
2075
+ background: #ffffff;
2076
+ border-radius: 20px;
2077
+ overflow: hidden;
2078
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
2079
+ margin-top: 40px;
2080
+ margin-bottom: 40px;
2081
+ }
2082
+ .header {
2083
+ background: linear-gradient(135deg, #a48a76 0%, #67574A 100%);
2084
+ padding: 40px 30px;
2085
+ text-align: center;
2086
+ color: white;
2087
+ }
2088
+ .header h1 {
2089
+ margin: 0;
2090
+ font-size: 28px;
2091
+ font-weight: 300;
2092
+ letter-spacing: 1px;
2093
+ }
2094
+ .header .subtitle {
2095
+ margin: 10px 0 0 0;
2096
+ font-size: 16px;
2097
+ opacity: 0.9;
2098
+ font-weight: 300;
2099
+ }
2100
+ .content {
2101
+ padding: 40px 30px;
2102
+ }
2103
+ .greeting {
2104
+ font-size: 18px;
2105
+ color: #333;
2106
+ margin-bottom: 25px;
2107
+ font-weight: 400;
2108
+ }
2109
+ .appointment-card {
2110
+ background: linear-gradient(135deg, #f8f6f5 0%, #f5f3f2 100%);
2111
+ border-radius: 15px;
2112
+ padding: 30px;
2113
+ margin: 25px 0;
2114
+ border-left: 5px solid #a48a76;
2115
+ }
2116
+ .appointment-title {
2117
+ font-size: 20px;
2118
+ color: #a48a76;
2119
+ margin-bottom: 20px;
2120
+ font-weight: 600;
2121
+ }
2122
+ .appointment-details {
2123
+ display: grid;
2124
+ gap: 15px;
2125
+ }
2126
+ .detail-row {
2127
+ display: flex;
2128
+ align-items: center;
2129
+ padding: 8px 0;
2130
+ }
2131
+ .detail-label {
2132
+ font-weight: 600;
2133
+ color: #555;
2134
+ min-width: 120px;
2135
+ font-size: 14px;
2136
+ }
2137
+ .detail-value {
2138
+ color: #333;
2139
+ font-size: 16px;
2140
+ font-weight: 500;
2141
+ }
2142
+ .patient-name {
2143
+ color: #a48a76;
2144
+ font-weight: 600;
2145
+ }
2146
+ .procedure-name {
2147
+ color: #67574A;
2148
+ font-weight: 600;
2149
+ }
2150
+ .cta-section {
2151
+ text-align: center;
2152
+ margin: 35px 0;
2153
+ }
2154
+ .cta-button {
2155
+ display: inline-block;
2156
+ background: linear-gradient(135deg, #a48a76 0%, #67574A 100%);
2157
+ color: white;
2158
+ text-decoration: none;
2159
+ padding: 15px 35px;
2160
+ border-radius: 50px;
2161
+ font-weight: 600;
2162
+ font-size: 16px;
2163
+ letter-spacing: 0.5px;
2164
+ transition: transform 0.3s ease;
2165
+ box-shadow: 0 8px 25px rgba(164, 138, 118, 0.3);
2166
+ }
2167
+ .cta-button:hover {
2168
+ transform: translateY(-2px);
2169
+ }
2170
+ .footer {
2171
+ background: #f8f9fa;
2172
+ padding: 25px 30px;
2173
+ text-align: center;
2174
+ color: #666;
2175
+ font-size: 14px;
2176
+ border-top: 1px solid #eee;
2177
+ }
2178
+ .logo {
2179
+ font-size: 24px;
2180
+ font-weight: 700;
2181
+ color: white;
2182
+ margin-bottom: 5px;
2183
+ }
2184
+ .divider {
2185
+ height: 2px;
2186
+ background: linear-gradient(90deg, #a48a76, #67574A);
2187
+ margin: 25px 0;
2188
+ border-radius: 1px;
2189
+ }
2190
+ </style>
2191
+ </head>
2192
+ <body>
2193
+ <div class="email-container">
2194
+ <div class="header">
2195
+ <div class="logo">MetaEstetics</div>
2196
+ <h1>New Appointment Request</h1>
2197
+ <div class="subtitle">Requires Your Attention</div>
2198
+ </div>
2199
+
2200
+ <div class="content">
2201
+ <div class="greeting">
2202
+ Dear <strong>{{clinicName}}</strong> Team,
2203
+ </div>
2204
+
2205
+ <p style="color: #555; font-size: 16px; line-height: 1.6; margin-bottom: 25px;">
2206
+ We hope this message finds you well. You have received a new appointment request that requires your review and confirmation.
2207
+ </p>
2208
+
2209
+ <div class="appointment-card">
2210
+ <div class="appointment-title">\u{1F4C5} Appointment Details</div>
2211
+ <div class="appointment-details">
2212
+ <div class="detail-row">
2213
+ <div class="detail-label">Patient:</div>
2214
+ <div class="detail-value patient-name">{{patientName}}</div>
2215
+ </div>
2216
+ <div class="detail-row">
2217
+ <div class="detail-label">Procedure:</div>
2218
+ <div class="detail-value procedure-name">{{procedureName}}</div>
2219
+ </div>
2220
+ <div class="detail-row">
2221
+ <div class="detail-label">Date:</div>
2222
+ <div class="detail-value">{{appointmentDate}}</div>
2223
+ </div>
2224
+ <div class="detail-row">
2225
+ <div class="detail-label">Time:</div>
2226
+ <div class="detail-value">{{appointmentTime}}</div>
2227
+ </div>
2228
+ <div class="detail-row">
2229
+ <div class="detail-label">Practitioner:</div>
2230
+ <div class="detail-value">{{practitionerName}}</div>
2231
+ </div>
2232
+ </div>
2233
+ </div>
2234
+
2235
+ <div class="divider"></div>
2236
+
2237
+ <p style="color: #555; font-size: 16px; line-height: 1.6; margin-bottom: 30px;">
2238
+ Please review this appointment request and confirm availability in your admin panel. The patient is waiting for your confirmation to finalize their booking.
2239
+ </p>
2240
+
2241
+ <div class="cta-section">
2242
+ <a href="#" class="cta-button">Review & Confirm Appointment</a>
2243
+ </div>
2244
+ </div>
2245
+
2246
+ <div class="footer">
2247
+ <p style="margin: 0 0 10px 0;">
2248
+ <strong>MetaEstetics</strong> - Premium Aesthetic Services
2249
+ </p>
2250
+ <p style="margin: 0; font-size: 12px; color: #999;">
2251
+ This is an automated message. Please do not reply to this email.
2252
+ </p>
2253
+ </div>
2254
+ </div>
2255
+ </body>
2256
+ </html>
2257
+ `;
1860
2258
  var AppointmentMailingService = class extends BaseMailingService {
1861
2259
  constructor(firestore18, mailgunClient) {
1862
2260
  super(firestore18, mailgunClient);
@@ -1870,10 +2268,10 @@ var AppointmentMailingService = class extends BaseMailingService {
1870
2268
  );
1871
2269
  const recipientEmail = data.recipientProfile.email;
1872
2270
  if (!recipientEmail) {
1873
- Logger.error(
1874
- "[AppointmentMailingService] Recipient email not found for confirmation.",
1875
- { recipientId: data.recipientProfile.id, role: data.recipientRole }
1876
- );
2271
+ Logger.error("[AppointmentMailingService] Recipient email not found for confirmation.", {
2272
+ recipientId: data.recipientProfile.id,
2273
+ role: data.recipientRole
2274
+ });
1877
2275
  throw new Error("Recipient email address is missing.");
1878
2276
  }
1879
2277
  const templateVariables = {
@@ -1884,10 +2282,7 @@ var AppointmentMailingService = class extends BaseMailingService {
1884
2282
  practitionerName: data.appointment.practitionerInfo.name,
1885
2283
  clinicName: data.appointment.clinicInfo.name
1886
2284
  };
1887
- const html = this.renderTemplate(
1888
- patientAppointmentConfirmedTemplate,
1889
- templateVariables
1890
- );
2285
+ const html = this.renderTemplate(patientAppointmentConfirmedTemplate, templateVariables);
1891
2286
  const subject = ((_a = data.options) == null ? void 0 : _a.customSubject) || "Your Appointment is Confirmed!";
1892
2287
  const fromAddress = ((_b = data.options) == null ? void 0 : _b.fromAddress) || `MetaEstetics <no-reply@${((_c = data.options) == null ? void 0 : _c.mailgunDomain) || this.DEFAULT_MAILGUN_DOMAIN}>`;
1893
2288
  const domainToSendFrom = ((_d = data.options) == null ? void 0 : _d.mailgunDomain) || this.DEFAULT_MAILGUN_DOMAIN;
@@ -1938,10 +2333,7 @@ var AppointmentMailingService = class extends BaseMailingService {
1938
2333
  appointmentTime: data.appointment.appointmentStartTime.toDate().toLocaleTimeString(),
1939
2334
  practitionerName: data.appointment.practitionerInfo.name
1940
2335
  };
1941
- const html = this.renderTemplate(
1942
- clinicAppointmentRequestedTemplate,
1943
- templateVariables
1944
- );
2336
+ const html = this.renderTemplate(clinicAppointmentRequestedTemplate, templateVariables);
1945
2337
  const subject = ((_b = data.options) == null ? void 0 : _b.customSubject) || "New Appointment Request Received";
1946
2338
  const fromAddress = ((_c = data.options) == null ? void 0 : _c.fromAddress) || `MetaEstetics <no-reply@${((_d = data.options) == null ? void 0 : _d.mailgunDomain) || this.DEFAULT_MAILGUN_DOMAIN}>`;
1947
2339
  const domainToSendFrom = ((_e = data.options) == null ? void 0 : _e.mailgunDomain) || this.DEFAULT_MAILGUN_DOMAIN;
@@ -6186,8 +6578,8 @@ var BookingAvailabilityCalculator = class {
6186
6578
  */
6187
6579
  static createWorkingHoursIntervals(workingHours, startDate, endDate, tz) {
6188
6580
  const workingIntervals = [];
6189
- let start = DateTime.fromJSDate(startDate, { zone: tz });
6190
- const end = DateTime.fromJSDate(endDate, { zone: tz });
6581
+ let start = DateTime.fromMillis(startDate.getTime(), { zone: tz });
6582
+ const end = DateTime.fromMillis(endDate.getTime(), { zone: tz });
6191
6583
  while (start <= end) {
6192
6584
  const dayOfWeek = start.weekday;
6193
6585
  const dayName = [
@@ -6217,8 +6609,8 @@ var BookingAvailabilityCalculator = class {
6217
6609
  millisecond: 0
6218
6610
  });
6219
6611
  if (workEnd.toMillis() > startDate.getTime() && workStart.toMillis() < endDate.getTime()) {
6220
- const intervalStart = workStart < DateTime.fromJSDate(startDate, { zone: tz }) ? DateTime.fromJSDate(startDate, { zone: tz }) : workStart;
6221
- const intervalEnd = workEnd > DateTime.fromJSDate(endDate, { zone: tz }) ? DateTime.fromJSDate(endDate, { zone: tz }) : workEnd;
6612
+ const intervalStart = workStart < DateTime.fromMillis(startDate.getTime(), { zone: tz }) ? DateTime.fromMillis(startDate.getTime(), { zone: tz }) : workStart;
6613
+ const intervalEnd = workEnd > DateTime.fromMillis(endDate.getTime(), { zone: tz }) ? DateTime.fromMillis(endDate.getTime(), { zone: tz }) : workEnd;
6222
6614
  workingIntervals.push({
6223
6615
  start: Timestamp.fromMillis(intervalStart.toMillis()),
6224
6616
  end: Timestamp.fromMillis(intervalEnd.toMillis())
@@ -6325,8 +6717,8 @@ var BookingAvailabilityCalculator = class {
6325
6717
  */
6326
6718
  static createPractitionerWorkingHoursIntervals(workingHours, startDate, endDate, tz) {
6327
6719
  const workingIntervals = [];
6328
- let start = DateTime.fromJSDate(startDate, { zone: tz });
6329
- const end = DateTime.fromJSDate(endDate, { zone: tz });
6720
+ let start = DateTime.fromMillis(startDate.getTime(), { zone: tz });
6721
+ const end = DateTime.fromMillis(endDate.getTime(), { zone: tz });
6330
6722
  while (start <= end) {
6331
6723
  const dayOfWeek = start.weekday;
6332
6724
  const dayName = [
@@ -6349,8 +6741,8 @@ var BookingAvailabilityCalculator = class {
6349
6741
  });
6350
6742
  const workEnd = start.set({ hour: endHours, minute: endMinutes });
6351
6743
  if (workEnd.toMillis() > startDate.getTime() && workStart.toMillis() < endDate.getTime()) {
6352
- const intervalStart = workStart < DateTime.fromJSDate(startDate, { zone: tz }) ? DateTime.fromJSDate(startDate, { zone: tz }) : workStart;
6353
- const intervalEnd = workEnd > DateTime.fromJSDate(endDate, { zone: tz }) ? DateTime.fromJSDate(endDate, { zone: tz }) : workEnd;
6744
+ const intervalStart = workStart < DateTime.fromMillis(startDate.getTime(), { zone: tz }) ? DateTime.fromMillis(startDate.getTime(), { zone: tz }) : workStart;
6745
+ const intervalEnd = workEnd > DateTime.fromMillis(endDate.getTime(), { zone: tz }) ? DateTime.fromMillis(endDate.getTime(), { zone: tz }) : workEnd;
6354
6746
  workingIntervals.push({
6355
6747
  start: Timestamp.fromMillis(intervalStart.toMillis()),
6356
6748
  end: Timestamp.fromMillis(intervalEnd.toMillis())
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@blackcode_sa/metaestetics-api",
3
3
  "private": false,
4
- "version": "1.12.22",
4
+ "version": "1.12.24",
5
5
  "description": "Firebase authentication service with anonymous upgrade support",
6
6
  "main": "dist/index.js",
7
7
  "module": "dist/index.mjs",
@@ -150,8 +150,9 @@ export class BookingAvailabilityCalculator {
150
150
  tz: string
151
151
  ): TimeInterval[] {
152
152
  const workingIntervals: TimeInterval[] = [];
153
- let start = DateTime.fromJSDate(startDate, { zone: tz });
154
- const end = DateTime.fromJSDate(endDate, { zone: tz });
153
+ // FIXED: Use fromMillis instead of fromJSDate to avoid timezone reinterpretation
154
+ let start = DateTime.fromMillis(startDate.getTime(), { zone: tz });
155
+ const end = DateTime.fromMillis(endDate.getTime(), { zone: tz });
155
156
 
156
157
  while (start <= end) {
157
158
  const dayOfWeek = start.weekday; // 1 for Monday, 7 for Sunday
@@ -192,13 +193,14 @@ export class BookingAvailabilityCalculator {
192
193
  workEnd.toMillis() > startDate.getTime() &&
193
194
  workStart.toMillis() < endDate.getTime()
194
195
  ) {
196
+ // FIXED: Use fromMillis instead of fromJSDate
195
197
  const intervalStart =
196
- workStart < DateTime.fromJSDate(startDate, { zone: tz })
197
- ? DateTime.fromJSDate(startDate, { zone: tz })
198
+ workStart < DateTime.fromMillis(startDate.getTime(), { zone: tz })
199
+ ? DateTime.fromMillis(startDate.getTime(), { zone: tz })
198
200
  : workStart;
199
201
  const intervalEnd =
200
- workEnd > DateTime.fromJSDate(endDate, { zone: tz })
201
- ? DateTime.fromJSDate(endDate, { zone: tz })
202
+ workEnd > DateTime.fromMillis(endDate.getTime(), { zone: tz })
203
+ ? DateTime.fromMillis(endDate.getTime(), { zone: tz })
202
204
  : workEnd;
203
205
 
204
206
  workingIntervals.push({
@@ -353,8 +355,9 @@ export class BookingAvailabilityCalculator {
353
355
  tz: string
354
356
  ): TimeInterval[] {
355
357
  const workingIntervals: TimeInterval[] = [];
356
- let start = DateTime.fromJSDate(startDate, { zone: tz });
357
- const end = DateTime.fromJSDate(endDate, { zone: tz });
358
+ // FIXED: Use fromMillis instead of fromJSDate to avoid timezone reinterpretation
359
+ let start = DateTime.fromMillis(startDate.getTime(), { zone: tz });
360
+ const end = DateTime.fromMillis(endDate.getTime(), { zone: tz });
358
361
 
359
362
  while (start <= end) {
360
363
  const dayOfWeek = start.weekday;
@@ -386,13 +389,14 @@ export class BookingAvailabilityCalculator {
386
389
  workEnd.toMillis() > startDate.getTime() &&
387
390
  workStart.toMillis() < endDate.getTime()
388
391
  ) {
392
+ // FIXED: Use fromMillis instead of fromJSDate
389
393
  const intervalStart =
390
- workStart < DateTime.fromJSDate(startDate, { zone: tz })
391
- ? DateTime.fromJSDate(startDate, { zone: tz })
394
+ workStart < DateTime.fromMillis(startDate.getTime(), { zone: tz })
395
+ ? DateTime.fromMillis(startDate.getTime(), { zone: tz })
392
396
  : workStart;
393
397
  const intervalEnd =
394
- workEnd > DateTime.fromJSDate(endDate, { zone: tz })
395
- ? DateTime.fromJSDate(endDate, { zone: tz })
398
+ workEnd > DateTime.fromMillis(endDate.getTime(), { zone: tz })
399
+ ? DateTime.fromMillis(endDate.getTime(), { zone: tz })
396
400
  : workEnd;
397
401
 
398
402
  workingIntervals.push({