@mylikita/booking-widget 0.1.0 → 0.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.
package/README.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# @mylikita/booking-widget
|
|
2
2
|
|
|
3
|
+
**Live on npm:** [`@mylikita/booking-widget`](https://www.npmjs.com/package/@mylikita/booking-widget) ·
|
|
4
|
+
[unpkg CDN](https://unpkg.com/@mylikita/booking-widget) ·
|
|
5
|
+
[React wrapper `@mylikita/booking-widget-react`](https://www.npmjs.com/package/@mylikita/booking-widget-react)
|
|
6
|
+
|
|
3
7
|
Drop-in appointment booking for hospital websites built on MyLikita. A
|
|
4
8
|
zero-dependency vanilla-JS widget (no framework required) that:
|
|
5
9
|
|
|
@@ -8,8 +12,9 @@ zero-dependency vanilla-JS widget (no framework required) that:
|
|
|
8
12
|
3. polls the booking (`GET /v1/bookings/:ref`) until the hospital confirms,
|
|
9
13
|
cancels, reschedules, marks no-show, or the request expires.
|
|
10
14
|
|
|
11
|
-
It implements the
|
|
12
|
-
|
|
15
|
+
It implements the **MyLikita Website Booking API (v1)** exactly — same auth,
|
|
16
|
+
same fields, same idempotency rules, same statuses — so the hospital side
|
|
17
|
+
needs no custom integration work.
|
|
13
18
|
|
|
14
19
|
---
|
|
15
20
|
|
|
@@ -40,7 +45,7 @@ import { createBookingWidget } from '@mylikita/booking-widget';
|
|
|
40
45
|
createBookingWidget(document.getElementById('booking'), { relayUrl, websiteKey, facilityId });
|
|
41
46
|
```
|
|
42
47
|
|
|
43
|
-
**React sites:** use [`@mylikita/booking-widget-react`](/
|
|
48
|
+
**React sites:** use [`@mylikita/booking-widget-react`](https://www.npmjs.com/package/@mylikita/booking-widget-react) — the
|
|
44
49
|
same widget as a `<BookingWidget relayUrl=… websiteKey=… facilityId=… />`
|
|
45
50
|
component (all options are props, SSR-safe, imperative ref).
|
|
46
51
|
|
|
@@ -52,7 +57,7 @@ component (all options are props, SSR-safe, imperative ref).
|
|
|
52
57
|
| `websiteKey` | string | — | **required** — your public client id (Bearer on every request) |
|
|
53
58
|
| `facilityId` | string | — | **required** — the hospital's public facility id |
|
|
54
59
|
| `providers` | array | `[]` | `[{ external_id, label }]` — shown as a "Preferred doctor" select; unmapped slugs simply arrive unassigned (never block on them). When provided, it wins and no fetch happens |
|
|
55
|
-
| `loadProviders` | bool | `false` |
|
|
60
|
+
| `loadProviders` | bool | `false` | fetch the facility's mapped providers from the relay (`GET /v1/providers`) on mount and populate the doctor dropdown automatically. Ignored when `providers` is non-empty. A fetch failure is non-fatal: the widget keeps "No preference" and still works |
|
|
56
61
|
| `services` | array | `[]` | `['General consultation', …]` — shown as a service select (free-text input otherwise) |
|
|
57
62
|
| `durationMins` | number | `30` | sent with the booking |
|
|
58
63
|
| `pollIntervalMs` | number | `5000` | how often to poll the booking status |
|
|
@@ -140,25 +145,11 @@ createBookingWidget(el, {
|
|
|
140
145
|
});
|
|
141
146
|
```
|
|
142
147
|
|
|
143
|
-
##
|
|
144
|
-
|
|
145
|
-
`demo/demo.html` runs the widget **fully offline** against an in-page mock of
|
|
146
|
-
the relay — submit a booking and watch it flip to *confirmed* after two polls.
|
|
147
|
-
Open it in a browser directly:
|
|
148
|
-
|
|
149
|
-
```bash
|
|
150
|
-
open packages/booking-widget/demo/demo.html
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
## Development
|
|
154
|
-
|
|
155
|
-
```bash
|
|
156
|
-
npm run build # esbuild → dist/ (iife, iife.min, esm, cjs)
|
|
157
|
-
npm test # mocked-fetch suite for the core logic
|
|
158
|
-
```
|
|
148
|
+
## Support
|
|
159
149
|
|
|
160
|
-
|
|
161
|
-
|
|
150
|
+
- **Issues:** report through your MyLikita account manager or the MyLikita
|
|
151
|
+
support channel you were onboarded with.
|
|
152
|
+
- **The React wrapper:** [`@mylikita/booking-widget-react`](https://www.npmjs.com/package/@mylikita/booking-widget-react).
|
|
162
153
|
|
|
163
154
|
## Security notes
|
|
164
155
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! @mylikita/booking-widget v0.1.
|
|
1
|
+
/*! @mylikita/booking-widget v0.1.1 | MIT */
|
|
2
2
|
var MyLikitaBookingWidget=(()=>{var Y=Object.defineProperty;var pe=Object.getOwnPropertyDescriptor;var fe=Object.getOwnPropertyNames;var ge=Object.prototype.hasOwnProperty;var we=(i,e)=>{for(var t in e)Y(i,t,{get:e[t],enumerable:!0})},ye=(i,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let a of fe(e))!ge.call(i,a)&&a!==t&&Y(i,a,{get:()=>e[a],enumerable:!(r=pe(e,a))||r.enumerable});return i};var _e=i=>ye(Y({},"__esModule",{value:!0}),i);var Ee={};we(Ee,{DEFAULT_THEME:()=>W,STATUS_COPY:()=>C,TERMINAL_STATUSES:()=>ie,createBooking:()=>S,createBookingWidget:()=>oe,fetchProviders:()=>E,fetchStatus:()=>T,newExternalRef:()=>P,pollStatus:()=>N,resolveTheme:()=>$,statusCopy:()=>A});async function S({relayUrl:i,websiteKey:e,payload:t,signal:r}){let a=await fetch(`${H(i)}/v1/bookings`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${e}`},body:JSON.stringify(t),signal:r}),o=await F(a);if(a.status===409)return{ok:!0,duplicate:!0,booking_ref:o?.booking_ref,error:o?.message};if(!a.ok)throw O(a.status,o,"create");return{ok:!0,duplicate:!1,booking_ref:o?.booking_ref,status:o?.status||"pending_confirmation"}}async function E({relayUrl:i,websiteKey:e,signal:t}){let r=await fetch(`${H(i)}/v1/providers`,{headers:{Authorization:`Bearer ${e}`},signal:t}),a=await F(r);if(!r.ok)throw O(r.status,a,"providers");return(Array.isArray(a?.providers)?a.providers:[]).map(d=>({external_id:d.external_id,name:d.name||d.external_id,specialty:d.specialty||null,module:d.module||"general"}))}async function T({relayUrl:i,websiteKey:e,bookingRef:t,signal:r}){let a=await fetch(`${H(i)}/v1/bookings/${encodeURIComponent(t)}`,{headers:{Authorization:`Bearer ${e}`},signal:r}),o=await F(a);if(!a.ok)throw O(a.status,o,"status");return{booking_ref:o?.booking_ref,status:o?.status||"pending_confirmation",appt_ref:o?.appt_ref||null}}async function N(i,{intervalMs:e=5e3,maxTries:t=12,signal:r}={}){for(let a=0;a<t;a++){if(r?.aborted)return{status:"aborted",resolved:!1,data:null};let o=await i();if(o.status!=="pending_confirmation")return{status:o.status,resolved:!0,data:o};a<t-1&&await be(e,r)}return{status:"pending_confirmation",resolved:!1,data:null}}function P(){return`BK-${Date.now()}-${Math.random().toString(36).slice(2,6)}`}function H(i){return String(i||"").replace(/\/+$/,"")}function O(i,e,t){let r=new Error(e?.message||e?.error||`Relay ${t} failed (HTTP ${i})`);return r.code=e?.error||"http_error",r.status=i,r}async function F(i){try{return await i.json()}catch{return null}}function be(i,e){return new Promise(t=>{if(e?.aborted)return t();let r=()=>{clearTimeout(a),t()},a=setTimeout(()=>{e?.removeEventListener("abort",r),t()},i);e?.addEventListener("abort",r,{once:!0})})}var C={pending_confirmation:{title:"Request received",message:"We've received your booking request \u2014 we'll confirm shortly.",kind:"info"},confirmed:{title:"Appointment confirmed",message:"Your appointment is confirmed. See you at the clinic!",kind:"success"},cancelled:{title:"Appointment cancelled",message:"This appointment was cancelled. Please contact the clinic if this was unexpected.",kind:"danger"},rescheduled:{title:"Appointment rescheduled",message:"This appointment was moved \u2014 the new time was sent to you.",kind:"info"},no_show:{title:"Missed appointment",message:"This appointment was marked as missed.",kind:"danger"},expired:{title:"Request expired",message:"This booking request expired \u2014 please call the clinic to book.",kind:"danger"},poll_error:{title:"Something went wrong",message:"We could not check your booking right now. Please try again shortly.",kind:"danger"}};function A(i){return C[i]||C.pending_confirmation}var ie=["confirmed","cancelled","rescheduled","no_show","expired"];var W={primary:"#0d6efd",primaryDark:"#0b5ed7",primaryText:"#ffffff",bg:"#ffffff",text:"#1e293b",muted:"#64748b",border:"#e2e8f0",danger:"#dc3545",success:"#15803d",radius:10,font:"system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif"};function $(i={}){let e={...W,...i||{}};return{"--mlw-primary":e.primary,"--mlw-primary-dark":e.primaryDark,"--mlw-primary-text":e.primaryText,"--mlw-bg":e.bg,"--mlw-text":e.text,"--mlw-muted":e.muted,"--mlw-border":e.border,"--mlw-danger":e.danger,"--mlw-success":e.success,"--mlw-radius":`${e.radius}px`,"--mlw-font":e.font}}var re=`
|
|
3
3
|
.mylikita-widget {
|
|
4
4
|
--mlw-primary: #0d6efd;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mylikita/booking-widget",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Drop-in appointment booking widget for MyLikita hospital websites. Submits bookings through the MyLikita relay (POST /v1/bookings) and polls status (GET /v1/bookings/:ref). Themeable via CSS variables.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/mylikita-booking-widget.cjs",
|