pih-appointment-widget 0.0.9 → 0.0.12

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.
@@ -6,18 +6,125 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.default = void 0;
7
7
  var _react = _interopRequireWildcard(require("react"));
8
8
  var _appointmentService = require("../services/appointmentService");
9
+ var _httpService = require("../services/httpService");
9
10
  var _apiConfig = require("../constants/apiConfig");
10
11
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
12
+ // Decode JWT payload (no verification; for display only). Returns {} on invalid/missing.
13
+ function getJwtPayload(token) {
14
+ if (!token || typeof token !== "string") return {};
15
+ try {
16
+ const parts = token.split(".");
17
+ if (parts.length !== 3) return {};
18
+ const base64 = parts[1].replace(/-/g, "+").replace(/_/g, "/");
19
+ const padded = base64.padEnd(base64.length + (4 - base64.length % 4) % 4, "=");
20
+ return JSON.parse(atob(padded));
21
+ } catch (_unused) {
22
+ return {};
23
+ }
24
+ }
25
+ const STORAGE_KEY_ID_TOKEN = "pih_appointment_idToken";
26
+ const STORAGE_KEY_EMAIL = "pih_appointment_email";
27
+ const STORAGE_KEY_APP_TOKEN = "pih_appointment_appToken";
28
+ const STORAGE_KEY_DOCTOR_ID = "pih_appointment_doctorId";
29
+ const STORAGE_KEY_USER_NAME = "pih_appointment_userName";
30
+
31
+ // Extract from SSO login response (not from appToken JWT — these are only in login response)
32
+ function extractAppToken(response) {
33
+ var _ref, _ref2, _ref3, _response$data$access, _response$data, _response$data2;
34
+ if (!response || response.err) return null;
35
+ return (_ref = (_ref2 = (_ref3 = (_response$data$access = (_response$data = response.data) === null || _response$data === void 0 ? void 0 : _response$data.access_token) !== null && _response$data$access !== void 0 ? _response$data$access : (_response$data2 = response.data) === null || _response$data2 === void 0 ? void 0 : _response$data2.token) !== null && _ref3 !== void 0 ? _ref3 : response.token) !== null && _ref2 !== void 0 ? _ref2 : response.accessToken) !== null && _ref !== void 0 ? _ref : null;
36
+ }
37
+ function extractDoctorIdFromLoginResponse(response) {
38
+ var _ref4, _response$data$doctor;
39
+ if (!(response !== null && response !== void 0 && response.data)) return null;
40
+ const id = (_ref4 = (_response$data$doctor = response.data.doctor_id) !== null && _response$data$doctor !== void 0 ? _response$data$doctor : response.data.doctorId) !== null && _ref4 !== void 0 ? _ref4 : null;
41
+ return id != null ? String(id) : null;
42
+ }
43
+ function extractUserNameFromLoginResponse(response) {
44
+ var _ref5, _ref6, _response$data$name;
45
+ if (!(response !== null && response !== void 0 && response.data)) return null;
46
+ const name = (_ref5 = (_ref6 = (_response$data$name = response.data.name) !== null && _response$data$name !== void 0 ? _response$data$name : response.data.doctor_name) !== null && _ref6 !== void 0 ? _ref6 : response.data.userName) !== null && _ref5 !== void 0 ? _ref5 : null;
47
+ return name != null ? String(name) : null;
48
+ }
49
+
11
50
  // SDK Component - accepts configuration from parent app
12
- const AppointmentPage = _ref => {
51
+ const AppointmentPage = _ref7 => {
13
52
  let {
14
53
  config = {}
15
- } = _ref;
16
- // Extract config with fallbacks to hardcoded defaults from apiConfig.js
54
+ } = _ref7;
17
55
  const apiBaseUrl = config.apiBaseUrl || _apiConfig.API_BASE_URL;
18
56
  const hospitalId = config.hospitalId || _apiConfig.DEFAULT_PARAMS.hospitalId;
19
- const doctorId = config.doctorId || _apiConfig.DEFAULT_PARAMS.doctorId;
20
- const joinCallUrl = config.joinCallUrl || _apiConfig.JOIN_CALL_URL;
57
+
58
+ // idToken/email: config first, then sessionStorage (so refresh keeps auth when used from another React app)
59
+ const [storedIdToken, setStoredIdToken] = (0, _react.useState)(() => {
60
+ try {
61
+ return typeof sessionStorage !== "undefined" ? sessionStorage.getItem(STORAGE_KEY_ID_TOKEN) : null;
62
+ } catch (_unused2) {
63
+ return null;
64
+ }
65
+ });
66
+ const [storedEmail, setStoredEmail] = (0, _react.useState)(() => {
67
+ try {
68
+ return typeof sessionStorage !== "undefined" ? sessionStorage.getItem(STORAGE_KEY_EMAIL) : null;
69
+ } catch (_unused3) {
70
+ return null;
71
+ }
72
+ });
73
+ const idToken = config.idToken || config.token || storedIdToken;
74
+ const email = config.email || storedEmail;
75
+
76
+ // Persist idToken/email when parent passes them (for refresh)
77
+ (0, _react.useEffect)(() => {
78
+ try {
79
+ if (typeof sessionStorage === "undefined") return;
80
+ if (config.idToken || config.token) {
81
+ sessionStorage.setItem(STORAGE_KEY_ID_TOKEN, config.idToken || config.token);
82
+ setStoredIdToken(config.idToken || config.token);
83
+ }
84
+ if (config.email) {
85
+ sessionStorage.setItem(STORAGE_KEY_EMAIL, config.email);
86
+ setStoredEmail(config.email);
87
+ }
88
+ } catch (e) {}
89
+ }, [config.idToken, config.token, config.email]);
90
+ const [appToken, setAppToken] = (0, _react.useState)(() => {
91
+ try {
92
+ return typeof sessionStorage !== "undefined" ? sessionStorage.getItem(STORAGE_KEY_APP_TOKEN) : null;
93
+ } catch (_unused4) {
94
+ return null;
95
+ }
96
+ });
97
+ const [doctorIdFromLogin, setDoctorIdFromLogin] = (0, _react.useState)(() => {
98
+ try {
99
+ return typeof sessionStorage !== "undefined" ? sessionStorage.getItem(STORAGE_KEY_DOCTOR_ID) : null;
100
+ } catch (_unused5) {
101
+ return null;
102
+ }
103
+ });
104
+ const [userName, setUserName] = (0, _react.useState)(() => {
105
+ try {
106
+ return typeof sessionStorage !== "undefined" ? sessionStorage.getItem(STORAGE_KEY_USER_NAME) : null;
107
+ } catch (_unused6) {
108
+ return null;
109
+ }
110
+ });
111
+ const [tokenLoading, setTokenLoading] = (0, _react.useState)(() => {
112
+ try {
113
+ if (typeof sessionStorage === "undefined") return false;
114
+ const hasApp = sessionStorage.getItem(STORAGE_KEY_APP_TOKEN);
115
+ const hasId = sessionStorage.getItem(STORAGE_KEY_ID_TOKEN);
116
+ const hasEmail = sessionStorage.getItem(STORAGE_KEY_EMAIL);
117
+ return !hasApp && !!(hasId && hasEmail);
118
+ } catch (_unused7) {
119
+ return false;
120
+ }
121
+ });
122
+ const [tokenError, setTokenError] = (0, _react.useState)(null);
123
+ const [refreshLoginTrigger, setRefreshLoginTrigger] = (0, _react.useState)(0);
124
+ const joinCallUrlBase = config.joinCallUrl || _apiConfig.JOIN_CALL_URL;
125
+ const joinCallUrl = appToken ? joinCallUrlBase.replace(/\/?$/, "/") + appToken : "";
126
+ const displayName = userName || getJwtPayload(idToken).name || getJwtPayload(idToken).sub || "User";
127
+
21
128
  // Helper function to get today's date in YYYY-MM-DD format
22
129
  const getTodayDate = () => {
23
130
  const today = new Date();
@@ -198,27 +305,36 @@ const AppointmentPage = _ref => {
198
305
  setSelectedAppointment(sortedAppointments[0]);
199
306
  }
200
307
  };
201
-
202
- // Fetch appointments based on active tab and date filter
203
308
  const fetchAppointments = (0, _react.useCallback)(async () => {
309
+ if (!appToken) return;
310
+ const doctorId = doctorIdFromLogin || _apiConfig.DEFAULT_PARAMS.doctorId;
204
311
  setLoading(true);
205
312
  setError(null);
206
313
  try {
207
- // Pass SDK configuration to service
208
314
  const serviceConfig = {
209
315
  apiBaseUrl,
210
316
  hospitalId,
211
- doctorId
317
+ doctorId,
318
+ token: appToken
212
319
  };
213
-
214
- // Fetch all appointments (no pagination params to BE)
215
320
  const response = await (0, _appointmentService.getAppointmentsByStatus)(activeTab, fromDate, toDate, serviceConfig);
216
-
217
- // Handle different response structures
321
+ if (response.status === 401) {
322
+ try {
323
+ if (typeof sessionStorage !== "undefined") {
324
+ sessionStorage.removeItem(STORAGE_KEY_APP_TOKEN);
325
+ sessionStorage.removeItem(STORAGE_KEY_DOCTOR_ID);
326
+ sessionStorage.removeItem(STORAGE_KEY_USER_NAME);
327
+ }
328
+ } catch (e) {}
329
+ setAppToken(null);
330
+ setDoctorIdFromLogin(null);
331
+ setUserName(null);
332
+ setRefreshLoginTrigger(t => t + 1);
333
+ setError("Session expired. Re-authenticating...");
334
+ return;
335
+ }
218
336
  const appointmentsData = response.data || response.appointments || response || [];
219
337
  setAllAppointments(appointmentsData);
220
-
221
- // Auto-select first appointment if available, otherwise clear selection
222
338
  if (appointmentsData.length > 0) {
223
339
  setSelectedAppointment(appointmentsData[0]);
224
340
  } else {
@@ -230,7 +346,7 @@ const AppointmentPage = _ref => {
230
346
  } finally {
231
347
  setLoading(false);
232
348
  }
233
- }, [activeTab, fromDate, toDate, apiBaseUrl, hospitalId, doctorId]);
349
+ }, [activeTab, fromDate, toDate, apiBaseUrl, hospitalId, doctorIdFromLogin, appToken]);
234
350
 
235
351
  // Handle appointment selection - no API call, just show data from list
236
352
  const handleAppointmentSelect = appointment => {
@@ -424,15 +540,61 @@ const AppointmentPage = _ref => {
424
540
  return () => window.removeEventListener('resize', handleResize);
425
541
  }, [showPipVideo, isPipMinimized, isPipFullscreen, pipSize]);
426
542
 
543
+ // Call login API only when we have no appToken (or got 401 and need refresh). Otherwise use stored appToken.
544
+ (0, _react.useEffect)(() => {
545
+ const needLogin = idToken && email && (!appToken || refreshLoginTrigger > 0);
546
+ if (!needLogin) return;
547
+ let cancelled = false;
548
+ setTokenLoading(true);
549
+ setTokenError(null);
550
+ (0, _httpService.getTokenFromSso)(apiBaseUrl, hospitalId, idToken, email).then(response => {
551
+ if (cancelled) return;
552
+ const token = extractAppToken(response);
553
+ if (response.err || !token) {
554
+ setTokenError(response.err || "Failed to get token");
555
+ setAppToken(null);
556
+ setDoctorIdFromLogin(null);
557
+ setUserName(null);
558
+ try {
559
+ if (typeof sessionStorage !== "undefined") {
560
+ sessionStorage.removeItem(STORAGE_KEY_APP_TOKEN);
561
+ sessionStorage.removeItem(STORAGE_KEY_DOCTOR_ID);
562
+ sessionStorage.removeItem(STORAGE_KEY_USER_NAME);
563
+ }
564
+ } catch (e) {}
565
+ } else {
566
+ const doctorId = extractDoctorIdFromLoginResponse(response);
567
+ const name = extractUserNameFromLoginResponse(response);
568
+ setAppToken(token);
569
+ setDoctorIdFromLogin(doctorId);
570
+ setUserName(name);
571
+ setTokenError(null);
572
+ try {
573
+ if (typeof sessionStorage !== "undefined") {
574
+ sessionStorage.setItem(STORAGE_KEY_APP_TOKEN, token);
575
+ if (doctorId) sessionStorage.setItem(STORAGE_KEY_DOCTOR_ID, doctorId);
576
+ if (name) sessionStorage.setItem(STORAGE_KEY_USER_NAME, name);
577
+ }
578
+ } catch (e) {}
579
+ }
580
+ }).catch(() => {}).finally(() => {
581
+ if (!cancelled) setTokenLoading(false);
582
+ });
583
+ return () => {
584
+ cancelled = true;
585
+ };
586
+ }, [apiBaseUrl, hospitalId, idToken, email, refreshLoginTrigger]);
587
+
427
588
  // Reset page to 1 when filters change
428
589
  (0, _react.useEffect)(() => {
429
590
  setCurrentPage(1);
430
591
  }, [activeTab, selectedDate, fromDate, toDate, searchQuery]);
431
592
 
432
- // Fetch appointments on mount and tab change
593
+ // Fetch appointments when we have appToken (tab/date change triggers refetch via fetchAppointments deps)
433
594
  (0, _react.useEffect)(() => {
595
+ if (tokenLoading || !appToken) return;
434
596
  fetchAppointments();
435
- }, [fetchAppointments]);
597
+ }, [fetchAppointments, appToken, tokenLoading]);
436
598
 
437
599
  // Setup fonts and responsive styles
438
600
  (0, _react.useEffect)(() => {
@@ -471,6 +633,91 @@ const AppointmentPage = _ref => {
471
633
  };
472
634
  }, [showDatePicker]);
473
635
  let fontFamily = '"Nunito", serif';
636
+
637
+ // When token API fails, show only "Not authorized" / "Not found" — do not show the full Tele Consultation UI
638
+ if (tokenError) {
639
+ return /*#__PURE__*/_react.default.createElement("div", {
640
+ style: {
641
+ fontFamily,
642
+ background: "#F5F5F7",
643
+ boxSizing: "border-box",
644
+ height: "100vh",
645
+ display: "flex",
646
+ flexDirection: "column",
647
+ alignItems: "center",
648
+ justifyContent: "center",
649
+ padding: "24px",
650
+ textAlign: "center"
651
+ }
652
+ }, /*#__PURE__*/_react.default.createElement("div", {
653
+ style: {
654
+ maxWidth: "400px",
655
+ padding: "32px 24px",
656
+ background: "#FFFFFF",
657
+ borderRadius: "12px",
658
+ boxShadow: "0 4px 20px rgba(0,0,0,0.08)"
659
+ }
660
+ }, /*#__PURE__*/_react.default.createElement("div", {
661
+ style: {
662
+ fontSize: "48px",
663
+ marginBottom: "16px"
664
+ }
665
+ }, "\uD83D\uDD12"), /*#__PURE__*/_react.default.createElement("h2", {
666
+ style: {
667
+ fontSize: "20px",
668
+ fontWeight: 700,
669
+ color: "#1a1a1a",
670
+ margin: "0 0 8px 0"
671
+ }
672
+ }, "Not authorised"), /*#__PURE__*/_react.default.createElement("p", {
673
+ style: {
674
+ fontSize: "14px",
675
+ color: "#666",
676
+ margin: 0,
677
+ lineHeight: 1.5
678
+ }
679
+ }, tokenError), /*#__PURE__*/_react.default.createElement("p", {
680
+ style: {
681
+ fontSize: "13px",
682
+ color: "#999",
683
+ marginTop: "16px",
684
+ marginBottom: 0
685
+ }
686
+ }, "You are not authorised to use this service. Please sign in again or contact support.")));
687
+ }
688
+
689
+ // While checking token (SSO in progress), show a simple loading state instead of the full UI
690
+ if (tokenLoading && idToken && email) {
691
+ return /*#__PURE__*/_react.default.createElement("div", {
692
+ style: {
693
+ fontFamily,
694
+ background: "#F5F5F7",
695
+ boxSizing: "border-box",
696
+ height: "100vh",
697
+ display: "flex",
698
+ flexDirection: "column",
699
+ alignItems: "center",
700
+ justifyContent: "center",
701
+ padding: "24px"
702
+ }
703
+ }, /*#__PURE__*/_react.default.createElement("style", null, "@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }"), /*#__PURE__*/_react.default.createElement("div", {
704
+ style: {
705
+ width: "40px",
706
+ height: "40px",
707
+ border: "3px solid #f3f3f3",
708
+ borderTop: "3px solid #4C4DDC",
709
+ borderRadius: "50%",
710
+ animation: "spin 1s linear infinite",
711
+ marginBottom: "16px"
712
+ }
713
+ }), /*#__PURE__*/_react.default.createElement("p", {
714
+ style: {
715
+ fontSize: "14px",
716
+ color: "#666",
717
+ margin: 0
718
+ }
719
+ }, "Checking authorisation..."));
720
+ }
474
721
  return /*#__PURE__*/_react.default.createElement("div", {
475
722
  style: {
476
723
  fontFamily,
@@ -587,7 +834,7 @@ const AppointmentPage = _ref => {
587
834
  style: {
588
835
  color: "#1a1a1a"
589
836
  }
590
- }, "Sameera")), /*#__PURE__*/_react.default.createElement("img", {
837
+ }, displayName)), /*#__PURE__*/_react.default.createElement("img", {
591
838
  src: "https://w7.pngwing.com/pngs/340/946/png-transparent-avatar-user-computer-icons-software-developer-avatar-child-face-heroes-thumbnail.png",
592
839
  alt: "User",
593
840
  style: {
@@ -1029,7 +1276,29 @@ const AppointmentPage = _ref => {
1029
1276
  overflow: "auto",
1030
1277
  flex: 1
1031
1278
  }
1032
- }, loading ? /*#__PURE__*/_react.default.createElement("div", {
1279
+ }, tokenLoading ? /*#__PURE__*/_react.default.createElement("div", {
1280
+ style: {
1281
+ display: "flex",
1282
+ justifyContent: "center",
1283
+ alignItems: "center",
1284
+ padding: "40px",
1285
+ color: "#888"
1286
+ }
1287
+ }, /*#__PURE__*/_react.default.createElement("div", {
1288
+ style: {
1289
+ textAlign: "center"
1290
+ }
1291
+ }, /*#__PURE__*/_react.default.createElement("div", {
1292
+ style: {
1293
+ width: "40px",
1294
+ height: "40px",
1295
+ border: "3px solid #f3f3f3",
1296
+ borderTop: "3px solid #4C4DDC",
1297
+ borderRadius: "50%",
1298
+ animation: "spin 1s linear infinite",
1299
+ margin: "0 auto 10px"
1300
+ }
1301
+ }), "Getting token...")) : loading ? /*#__PURE__*/_react.default.createElement("div", {
1033
1302
  style: {
1034
1303
  display: "flex",
1035
1304
  justifyContent: "center",
@@ -29,4 +29,4 @@ const DEFAULT_PARAMS = exports.DEFAULT_PARAMS = {
29
29
  const REQUEST_TIMEOUT = exports.REQUEST_TIMEOUT = 10000;
30
30
 
31
31
  // Join call URL (will be dynamic from auth API later)
32
- const JOIN_CALL_URL = exports.JOIN_CALL_URL = "https://wbafedevittisalwe01-had2b3e0a7h6fyey.westeurope-01.azurewebsites.net/call/eyJhbGciOiJIUzI1NiJ9.eyJtZXRhZGF0YSI6InVzZXItcm9sZTpkb2N0b3IiLCJuYW1lIjoiRHIuIE1hZ2VkIEZhaG15IiwidmlkZW8iOnsicm9vbUpvaW4iOnRydWUsInJvb20iOiIxMzUwMDgiLCJjYW5QdWJsaXNoIjp0cnVlLCJjYW5TdWJzY3JpYmUiOnRydWUsImNhblB1Ymxpc2hEYXRhIjp0cnVlfSwiaXNzIjoiQVBJa2JTTDg5Y0hxVlhZIiwiZXhwIjoxNzcxMzQxNjYyLCJuYmYiOjAsInN1YiI6ImhpbmF2aW5tYXRoQGdtYWlsLmNvbSJ9.3iJCdselxCHZ6IPzzgG3fGrIwInSszgqLeSgo3EX5bs";
32
+ const JOIN_CALL_URL = exports.JOIN_CALL_URL = "https://wbafedevittisalwe01-had2b3e0a7h6fyey.westeurope-01.azurewebsites.net/call/";