@patientos/website-kit 0.2.9 → 0.2.10

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.
@@ -5,7 +5,7 @@ import {
5
5
  UnsupportedCartLineList,
6
6
  quoteSourceQuantities,
7
7
  useCartQuote
8
- } from "./chunk-BHF7S6UQ.js";
8
+ } from "./chunk-LW2LLAL4.js";
9
9
  import {
10
10
  createStoreClient
11
11
  } from "./chunk-7BUDUYXN.js";
@@ -24,7 +24,8 @@ function CartClient({ storeApiOrigin }) {
24
24
  );
25
25
  const [cart, setCart] = React.useState(() => readCart());
26
26
  const [mutationUnavailable, setMutationUnavailable] = React.useState(false);
27
- const { quote, quoteSourceCart, failed } = useCartQuote(client, cart);
27
+ const { quote, quoteSourceCart, failed, pending } = useCartQuote(client, cart);
28
+ const liveQuantities = React.useMemo(() => quoteSourceQuantities(cart), [cart]);
28
29
  async function runMutation(mutation) {
29
30
  const result = await mutation;
30
31
  setMutationUnavailable(result.status === "cart_storage_unavailable");
@@ -61,6 +62,8 @@ function CartClient({ storeApiOrigin }) {
61
62
  {
62
63
  lines: quote.lines,
63
64
  sourceQuantities: quoteSourceQuantities(quoteSourceCart),
65
+ liveQuantities,
66
+ pending,
64
67
  onMutate: (m) => void runMutation(m)
65
68
  }
66
69
  ),
@@ -3,16 +3,16 @@ import {
3
3
  CertificateFunnelClient,
4
4
  PortalAccountClient,
5
5
  PortalPanelClient
6
- } from "./chunk-4GKSSM5N.js";
6
+ } from "./chunk-SYXA5WP7.js";
7
7
  import {
8
8
  mountIslandsWith
9
9
  } from "./chunk-THMT43MV.js";
10
10
  import {
11
11
  CartClient
12
- } from "./chunk-U6FJ6XYX.js";
12
+ } from "./chunk-55W3KKJD.js";
13
13
  import {
14
14
  CartButtonClient
15
- } from "./chunk-ORGTP5GB.js";
15
+ } from "./chunk-Q5DIBKWX.js";
16
16
  import {
17
17
  CheckoutClient
18
18
  } from "./chunk-S3ZQQOQT.js";
@@ -50,9 +50,11 @@ function UnsupportedCartLineList({
50
50
  function CartLineList({
51
51
  lines,
52
52
  sourceQuantities,
53
- onMutate
53
+ onMutate,
54
+ liveQuantities,
55
+ pending
54
56
  }) {
55
- return /* @__PURE__ */ jsx("ul", { className: "sk-cart__lines", children: lines.map((line) => /* @__PURE__ */ jsxs("li", { className: "sk-cart__line", children: [
57
+ return /* @__PURE__ */ jsx("ul", { className: "sk-cart__lines", "aria-busy": pending || void 0, children: lines.map((line) => /* @__PURE__ */ jsxs("li", { className: "sk-cart__line", children: [
56
58
  /* @__PURE__ */ jsx("a", { className: "sk-cart__line-title", href: `/store/${line.productHandle}`, children: line.title }),
57
59
  /* @__PURE__ */ jsxs("span", { className: "sk-cart__line-unit", children: [
58
60
  formatMoney(line.unitPrice),
@@ -75,7 +77,7 @@ function CartLineList({
75
77
  children: "\u2212"
76
78
  }
77
79
  ),
78
- /* @__PURE__ */ jsx("span", { className: "sk-cart__qty-value", "aria-live": "polite", children: line.quantity }),
80
+ /* @__PURE__ */ jsx("span", { className: "sk-cart__qty-value", "aria-live": "polite", children: (pending ? liveQuantities?.get(line.variantId) : void 0) ?? line.quantity }),
79
81
  /* @__PURE__ */ jsx(
80
82
  "button",
81
83
  {
@@ -125,22 +127,38 @@ function useCartQuote(client, cart, enabled = true) {
125
127
  const [state, setState] = React.useState({
126
128
  quote: null,
127
129
  quoteSourceCart: null,
128
- failed: false
130
+ failed: false,
131
+ pending: false
129
132
  });
130
133
  React.useEffect(() => {
131
134
  if (!enabled || cart.lines.length === 0) {
132
- setState({ quote: null, quoteSourceCart: null, failed: false });
135
+ setState({
136
+ quote: null,
137
+ quoteSourceCart: null,
138
+ failed: false,
139
+ pending: false
140
+ });
133
141
  return;
134
142
  }
135
143
  let alive = true;
136
- setState({ quote: null, quoteSourceCart: null, failed: false });
144
+ setState((previous) => ({ ...previous, failed: false, pending: true }));
137
145
  void client.post("/api/store/quote", { lines: cartToWireLines(cart) }).then((result) => {
138
146
  if (!alive) return;
139
147
  if (!result.ok) {
140
- setState({ quote: null, quoteSourceCart: null, failed: true });
148
+ setState({
149
+ quote: null,
150
+ quoteSourceCart: null,
151
+ failed: true,
152
+ pending: false
153
+ });
141
154
  return;
142
155
  }
143
- setState({ quote: result.data, quoteSourceCart: cart, failed: false });
156
+ setState({
157
+ quote: result.data,
158
+ quoteSourceCart: cart,
159
+ failed: false,
160
+ pending: false
161
+ });
144
162
  });
145
163
  return () => {
146
164
  alive = false;
@@ -5,7 +5,7 @@ import {
5
5
  UnsupportedCartLineList,
6
6
  quoteSourceQuantities,
7
7
  useCartQuote
8
- } from "./chunk-BHF7S6UQ.js";
8
+ } from "./chunk-LW2LLAL4.js";
9
9
  import {
10
10
  createStoreClient
11
11
  } from "./chunk-7BUDUYXN.js";
@@ -87,7 +87,12 @@ function CartDrawer({
87
87
  window.addEventListener("keydown", onKeyDown);
88
88
  return () => window.removeEventListener("keydown", onKeyDown);
89
89
  }, [open, onClose]);
90
- const { quote, quoteSourceCart, failed } = useCartQuote(client, cart, open);
90
+ const { quote, quoteSourceCart, failed, pending } = useCartQuote(
91
+ client,
92
+ cart,
93
+ open
94
+ );
95
+ const liveQuantities = React.useMemo(() => quoteSourceQuantities(cart), [cart]);
91
96
  async function runMutation(mutation) {
92
97
  const result = await mutation;
93
98
  setMutationUnavailable(result.status === "cart_storage_unavailable");
@@ -144,6 +149,8 @@ function CartDrawer({
144
149
  {
145
150
  lines: quote.lines,
146
151
  sourceQuantities: quoteSourceQuantities(quoteSourceCart),
152
+ liveQuantities,
153
+ pending,
147
154
  onMutate: (m) => void runMutation(m)
148
155
  }
149
156
  ),
@@ -11062,6 +11062,23 @@ function PortalConsultPanel({
11062
11062
  ] });
11063
11063
  }
11064
11064
 
11065
+ // src/portal-account-cache.ts
11066
+ var snapshots = /* @__PURE__ */ new Map();
11067
+ function rememberPortalAccount(surface, state) {
11068
+ if (state.kind === "signed-in") {
11069
+ snapshots.set(surface, state);
11070
+ return;
11071
+ }
11072
+ if (state.kind === "loading" || state.kind === "link-sent") return;
11073
+ clearPortalAccountCache();
11074
+ }
11075
+ function recallPortalAccount(surface) {
11076
+ return snapshots.get(surface) ?? null;
11077
+ }
11078
+ function clearPortalAccountCache() {
11079
+ snapshots.clear();
11080
+ }
11081
+
11065
11082
  // src/portal-account.client.tsx
11066
11083
  import { Fragment as Fragment11, jsx as jsx26, jsxs as jsxs23 } from "react/jsx-runtime";
11067
11084
  var APPOINTMENTS_URL = "/portal/api/appointments";
@@ -11220,21 +11237,36 @@ function PortalAccountSurfaceClient({
11220
11237
  const portalClient = usePortalClient();
11221
11238
  const fetchImpl = React21.useMemo(() => portalFetchForClient(portalClient), [portalClient]);
11222
11239
  const [state, setState] = React21.useState(
11223
- // `readPortalSignedInHint` is in the initialiser (not an effect) on purpose: the
11240
+ // STALE-WHILE-REVALIDATE (PAT-949). Three ways to start, in falling order of how
11241
+ // much we already know:
11242
+ // 1. the caller drove it (tests, a future SSR seed);
11243
+ // 2. this document already resolved this surface — render that body NOW and
11244
+ // revalidate behind it, so returning to a surface is not a redraw. In-memory
11245
+ // only; see `./portal-account-cache` for the PHI rules;
11246
+ // 3. genuinely cold — a skeleton, shaped like the rows about to land.
11247
+ // `readPortalSignedInHint` stays in the initialiser (not an effect) on purpose: the
11224
11248
  // whole point is that the FIRST paint already knows, so there is no placeholder
11225
11249
  // swap. It reads one boolean out of sessionStorage — no PHI, no network.
11226
- () => initialState ?? { kind: "loading", hinted: readPortalSignedInHint() }
11250
+ () => initialState ?? recallPortalAccount(surface) ?? { kind: "loading", hinted: readPortalSignedInHint() }
11227
11251
  );
11228
11252
  const [signingOut, setSigningOut] = React21.useState(false);
11229
- const commit = React21.useCallback((next) => {
11230
- writePortalSignedInHint(next.kind === "signed-in" || next.kind === "not-available");
11231
- setState(next);
11232
- }, []);
11253
+ const [revalidating, setRevalidating] = React21.useState(false);
11254
+ const commit = React21.useCallback(
11255
+ (next) => {
11256
+ writePortalSignedInHint(next.kind === "signed-in" || next.kind === "not-available");
11257
+ rememberPortalAccount(surface, next);
11258
+ setState(next);
11259
+ },
11260
+ [surface]
11261
+ );
11233
11262
  React21.useEffect(() => {
11234
11263
  if (initialState) return;
11235
11264
  let live = true;
11265
+ setRevalidating(true);
11236
11266
  void loadPortalAccount(surface, fetchImpl).then((next) => {
11237
- if (live) commit(next);
11267
+ if (!live) return;
11268
+ setRevalidating(false);
11269
+ commit(next);
11238
11270
  });
11239
11271
  return () => {
11240
11272
  live = false;
@@ -11246,6 +11278,7 @@ function PortalAccountSurfaceClient({
11246
11278
  async function onSignOut() {
11247
11279
  if (signingOut) return;
11248
11280
  setSigningOut(true);
11281
+ clearPortalAccountCache();
11249
11282
  await signOutOfPortal(fetchImpl);
11250
11283
  const next = await loadPortalAccount(surface, fetchImpl);
11251
11284
  setSigningOut(false);
@@ -11253,91 +11286,119 @@ function PortalAccountSurfaceClient({
11253
11286
  }
11254
11287
  function replaceData(data) {
11255
11288
  const refetched = refetchedGivenName(data);
11256
- setState(
11257
- (prev) => prev.kind === "signed-in" ? { ...prev, data, givenName: refetched === void 0 ? prev.givenName : refetched } : prev
11258
- );
11289
+ setState((prev) => {
11290
+ if (prev.kind !== "signed-in") return prev;
11291
+ const next = {
11292
+ ...prev,
11293
+ data,
11294
+ givenName: refetched === void 0 ? prev.givenName : refetched
11295
+ };
11296
+ rememberPortalAccount(surface, next);
11297
+ return next;
11298
+ });
11259
11299
  }
11260
- return /* @__PURE__ */ jsxs23("section", { className: "sk-portal-account__body", "aria-busy": state.kind === "loading", children: [
11261
- /* @__PURE__ */ jsx26("h2", { className: "sk-portal-account__heading", children: heading2 }),
11262
- state.kind === "loading" && !state.hinted ? /* @__PURE__ */ jsx26("p", { className: "sk-portal-account__placeholder", children: "Checking your sign-in\u2026" }) : null,
11263
- state.kind === "loading" && state.hinted ? /* @__PURE__ */ jsx26(PortalAccountSkeleton, {}) : null,
11264
- state.kind === "link-out" ? /* @__PURE__ */ jsx26("p", { className: "sk-portal-account__placeholder", children: /* @__PURE__ */ jsx26("a", { className: "sk-portal-link", href: base2, children: "Sign in to your patient portal" }) }) : null,
11265
- state.kind === "signed-out" ? /* @__PURE__ */ jsx26(
11266
- PortalSignInRamp,
11267
- {
11268
- idSuffix: `account-${surface}`,
11269
- submit: (email) => portalClient.requestMagicLink(email).then((ok) => ({ ok })),
11270
- onResult: ({ ok, email }) => setState(ok ? { kind: "link-sent", email } : { kind: "link-out" })
11271
- }
11272
- ) : null,
11273
- state.kind === "link-sent" ? /* @__PURE__ */ jsx26(PortalLinkSentNotice, { email: state.email }) : null,
11274
- state.kind === "not-available" || state.kind === "signed-in" ? /* @__PURE__ */ jsxs23("div", { className: "sk-portal-account__header", children: [
11275
- /* @__PURE__ */ jsxs23("span", { className: "sk-portal-account__who", children: [
11276
- "Signed in as ",
11277
- state.givenName ?? "you"
11278
- ] }),
11279
- /* @__PURE__ */ jsx26(
11280
- "button",
11281
- {
11282
- type: "button",
11283
- className: "sk-portal-button sk-portal-button--ghost",
11284
- onClick: () => void onSignOut(),
11285
- disabled: signingOut,
11286
- children: signingOut ? "Signing out\u2026" : "Sign out"
11287
- }
11288
- )
11289
- ] }) : null,
11290
- state.kind === "not-available" ? /* @__PURE__ */ jsx26("p", { className: "sk-portal-account__note", role: "note", children: "This isn't available online at this clinic." }) : null,
11291
- state.kind === "signed-in" && surface === "appointments" ? /* @__PURE__ */ jsx26(
11292
- PortalAccountAppointments,
11293
- {
11294
- data: state.data,
11295
- base: base2,
11296
- bookHref: book,
11297
- fetchImpl,
11298
- onReloaded: replaceData
11299
- }
11300
- ) : null,
11301
- state.kind === "signed-in" && surface === "addresses" ? /* @__PURE__ */ jsx26(
11302
- PortalAccountAddresses,
11303
- {
11304
- data: state.data,
11305
- client: portalClient,
11306
- fetchImpl,
11307
- onReloaded: replaceData
11308
- }
11309
- ) : null,
11310
- state.kind === "signed-in" && surface === "documents" ? /* @__PURE__ */ jsx26(
11311
- PortalAccountDocuments,
11312
- {
11313
- data: state.data,
11314
- fetchImpl,
11315
- openUrl
11316
- }
11317
- ) : null,
11318
- state.kind === "signed-in" && surface === "orders" ? /* @__PURE__ */ jsx26(
11319
- PortalAccountOrders,
11320
- {
11321
- data: state.data,
11322
- fetchImpl,
11323
- openUrl
11324
- }
11325
- ) : null,
11326
- state.kind === "signed-in" && surface === "profile" ? /* @__PURE__ */ jsx26(
11327
- PortalAccountProfile,
11328
- {
11329
- data: state.data,
11330
- fetchImpl,
11331
- onSaved: replaceData
11332
- }
11333
- ) : null
11334
- ] });
11300
+ return /* @__PURE__ */ jsxs23(
11301
+ "section",
11302
+ {
11303
+ className: "sk-portal-account__body",
11304
+ "aria-busy": state.kind === "loading" || revalidating,
11305
+ children: [
11306
+ /* @__PURE__ */ jsx26("h2", { className: "sk-portal-account__heading", children: heading2 }),
11307
+ state.kind === "loading" && !state.hinted ? /* @__PURE__ */ jsx26("p", { className: "sk-portal-account__placeholder", role: "status", children: "Checking your sign-in\u2026" }) : null,
11308
+ state.kind === "loading" && state.hinted ? /* @__PURE__ */ jsx26(PortalAccountSkeleton, { surface }) : null,
11309
+ state.kind === "link-out" ? /* @__PURE__ */ jsx26("p", { className: "sk-portal-account__placeholder", children: /* @__PURE__ */ jsx26("a", { className: "sk-portal-link", href: base2, children: "Sign in to your patient portal" }) }) : null,
11310
+ state.kind === "signed-out" ? /* @__PURE__ */ jsx26(
11311
+ PortalSignInRamp,
11312
+ {
11313
+ idSuffix: `account-${surface}`,
11314
+ submit: (email) => portalClient.requestMagicLink(email).then((ok) => ({ ok })),
11315
+ onResult: ({ ok, email }) => setState(ok ? { kind: "link-sent", email } : { kind: "link-out" })
11316
+ }
11317
+ ) : null,
11318
+ state.kind === "link-sent" ? /* @__PURE__ */ jsx26(PortalLinkSentNotice, { email: state.email }) : null,
11319
+ state.kind === "not-available" || state.kind === "signed-in" ? /* @__PURE__ */ jsxs23("div", { className: "sk-portal-account__header", children: [
11320
+ /* @__PURE__ */ jsxs23("span", { className: "sk-portal-account__who", children: [
11321
+ "Signed in as ",
11322
+ state.givenName ?? "you"
11323
+ ] }),
11324
+ /* @__PURE__ */ jsx26(
11325
+ "button",
11326
+ {
11327
+ type: "button",
11328
+ className: "sk-portal-button sk-portal-button--ghost",
11329
+ onClick: () => void onSignOut(),
11330
+ disabled: signingOut,
11331
+ children: signingOut ? "Signing out\u2026" : "Sign out"
11332
+ }
11333
+ )
11334
+ ] }) : null,
11335
+ state.kind === "not-available" ? /* @__PURE__ */ jsx26("p", { className: "sk-portal-account__note", role: "note", children: "This isn't available online at this clinic." }) : null,
11336
+ state.kind === "signed-in" && surface === "appointments" ? /* @__PURE__ */ jsx26(
11337
+ PortalAccountAppointments,
11338
+ {
11339
+ data: state.data,
11340
+ base: base2,
11341
+ bookHref: book,
11342
+ fetchImpl,
11343
+ onReloaded: replaceData
11344
+ }
11345
+ ) : null,
11346
+ state.kind === "signed-in" && surface === "addresses" ? /* @__PURE__ */ jsx26(
11347
+ PortalAccountAddresses,
11348
+ {
11349
+ data: state.data,
11350
+ client: portalClient,
11351
+ fetchImpl,
11352
+ onReloaded: replaceData
11353
+ }
11354
+ ) : null,
11355
+ state.kind === "signed-in" && surface === "documents" ? /* @__PURE__ */ jsx26(
11356
+ PortalAccountDocuments,
11357
+ {
11358
+ data: state.data,
11359
+ fetchImpl,
11360
+ openUrl
11361
+ }
11362
+ ) : null,
11363
+ state.kind === "signed-in" && surface === "orders" ? /* @__PURE__ */ jsx26(
11364
+ PortalAccountOrders,
11365
+ {
11366
+ data: state.data,
11367
+ fetchImpl,
11368
+ openUrl
11369
+ }
11370
+ ) : null,
11371
+ state.kind === "signed-in" && surface === "profile" ? /* @__PURE__ */ jsx26(
11372
+ PortalAccountProfile,
11373
+ {
11374
+ data: state.data,
11375
+ fetchImpl,
11376
+ onSaved: replaceData
11377
+ }
11378
+ ) : null
11379
+ ]
11380
+ }
11381
+ );
11335
11382
  }
11336
- function PortalAccountSkeleton() {
11337
- return /* @__PURE__ */ jsxs23("div", { className: "sk-portal-account__skeleton", "aria-hidden": "true", children: [
11338
- /* @__PURE__ */ jsx26("span", { className: "sk-portal-account__skeleton-row" }),
11339
- /* @__PURE__ */ jsx26("span", { className: "sk-portal-account__skeleton-row" }),
11340
- /* @__PURE__ */ jsx26("span", { className: "sk-portal-account__skeleton-row" })
11383
+ var SKELETON_ROWS = 3;
11384
+ function PortalAccountSkeleton({
11385
+ surface
11386
+ }) {
11387
+ const grouped = surface === "appointments";
11388
+ return /* @__PURE__ */ jsxs23(Fragment11, { children: [
11389
+ /* @__PURE__ */ jsx26("p", { className: "sk-portal-account__placeholder sk-portal-account__placeholder--sr", role: "status", children: "Loading your account\u2026" }),
11390
+ /* @__PURE__ */ jsxs23("div", { className: "sk-portal-account__skeleton", "aria-hidden": "true", children: [
11391
+ /* @__PURE__ */ jsxs23("div", { className: "sk-portal-account__header sk-portal-account__header--skeleton", children: [
11392
+ /* @__PURE__ */ jsx26("span", { className: "sk-portal-account__skeleton-line sk-portal-account__skeleton-line--who" }),
11393
+ /* @__PURE__ */ jsx26("span", { className: "sk-portal-account__skeleton-line sk-portal-account__skeleton-line--button" })
11394
+ ] }),
11395
+ grouped ? /* @__PURE__ */ jsx26("p", { className: "sk-portal-account__group-title sk-portal-account__group-title--skeleton", children: /* @__PURE__ */ jsx26("span", { className: "sk-portal-account__skeleton-line sk-portal-account__skeleton-line--group" }) }) : null,
11396
+ /* @__PURE__ */ jsx26("ul", { className: "sk-portal-account__list", children: Array.from({ length: SKELETON_ROWS }, (_, index) => /* @__PURE__ */ jsxs23("li", { className: "sk-portal-account__item sk-portal-account__item--skeleton", children: [
11397
+ /* @__PURE__ */ jsx26("span", { className: "sk-portal-account__skeleton-line sk-portal-account__skeleton-line--title" }),
11398
+ /* @__PURE__ */ jsx26("span", { className: "sk-portal-account__skeleton-line sk-portal-account__skeleton-line--meta" }),
11399
+ grouped ? /* @__PURE__ */ jsx26("span", { className: "sk-portal-account__skeleton-line sk-portal-account__skeleton-line--meta" }) : null
11400
+ ] }, index)) })
11401
+ ] })
11341
11402
  ] });
11342
11403
  }
11343
11404
  function StatusBadge({ status }) {
@@ -11598,8 +11659,11 @@ function PortalAccountOrders({
11598
11659
  initialUi
11599
11660
  }) {
11600
11661
  const [expandedId, setExpandedId] = React21.useState(initialUi?.expandedId ?? null);
11601
- const [detail, setDetail] = React21.useState(initialUi?.detail ?? null);
11602
- const [loading, setLoading] = React21.useState(false);
11662
+ const [details, setDetails] = React21.useState(
11663
+ () => initialUi?.detail && initialUi.expandedId ? { [initialUi.expandedId]: initialUi.detail } : {}
11664
+ );
11665
+ const [loadingId, setLoadingId] = React21.useState(null);
11666
+ const [failedIds, setFailedIds] = React21.useState([]);
11603
11667
  const [error2, setError] = React21.useState(initialUi?.error ?? null);
11604
11668
  async function toggle(id) {
11605
11669
  if (expandedId === id) {
@@ -11607,12 +11671,13 @@ function PortalAccountOrders({
11607
11671
  return;
11608
11672
  }
11609
11673
  setExpandedId(id);
11610
- setDetail(null);
11611
11674
  setError(null);
11612
- setLoading(true);
11675
+ if (details[id]) return;
11676
+ setLoadingId(id);
11613
11677
  const next = await loadPortalOrderDetail(id, fetchImpl);
11614
- setLoading(false);
11615
- setDetail(next);
11678
+ setLoadingId((current) => current === id ? null : current);
11679
+ if (next) setDetails((prev) => ({ ...prev, [id]: next }));
11680
+ else setFailedIds((prev) => prev.includes(id) ? prev : [...prev, id]);
11616
11681
  }
11617
11682
  async function receipt(id) {
11618
11683
  setError(null);
@@ -11626,6 +11691,9 @@ function PortalAccountOrders({
11626
11691
  /* @__PURE__ */ jsx26(ErrorNote, { message: error2 }),
11627
11692
  /* @__PURE__ */ jsx26("ul", { className: "sk-portal-account__list", children: data.items.map((order) => {
11628
11693
  const open = expandedId === order.id;
11694
+ const detail = details[order.id] ?? null;
11695
+ const loading = loadingId === order.id && !detail;
11696
+ const failed = !detail && failedIds.includes(order.id);
11629
11697
  return /* @__PURE__ */ jsxs23("li", { className: "sk-portal-account__item", children: [
11630
11698
  /* @__PURE__ */ jsxs23(
11631
11699
  "button",
@@ -11645,7 +11713,7 @@ function PortalAccountOrders({
11645
11713
  ]
11646
11714
  }
11647
11715
  ),
11648
- open ? /* @__PURE__ */ jsx26("div", { className: "sk-portal-account__detail", children: loading ? /* @__PURE__ */ jsx26("p", { className: "sk-portal-account__placeholder", role: "status", children: "Loading this order\u2026" }) : !detail ? /* @__PURE__ */ jsx26("p", { className: "sk-portal-account__note", role: "note", children: "We couldn't load this order just now." }) : /* @__PURE__ */ jsxs23(Fragment11, { children: [
11716
+ open ? /* @__PURE__ */ jsx26("div", { className: "sk-portal-account__detail", "aria-busy": loading, children: loading ? /* @__PURE__ */ jsx26("p", { className: "sk-portal-account__placeholder", role: "status", children: "Loading this order\u2026" }) : failed || !detail ? /* @__PURE__ */ jsx26("p", { className: "sk-portal-account__note", role: "note", children: "We couldn't load this order just now." }) : /* @__PURE__ */ jsxs23(Fragment11, { children: [
11649
11717
  /* @__PURE__ */ jsx26("ul", { className: "sk-portal-account__lines", children: (detail.lines ?? []).map((line) => /* @__PURE__ */ jsxs23("li", { className: "sk-portal-account__line", children: [
11650
11718
  /* @__PURE__ */ jsxs23("span", { children: [
11651
11719
  line.title ?? "\u2014",
package/dist/index.d.ts CHANGED
@@ -217,6 +217,6 @@ interface PortalSession {
217
217
  */
218
218
  declare function usePortalSession(): PortalSession;
219
219
 
220
- declare const WEBSITE_KIT_VERSION = "0.2.9";
220
+ declare const WEBSITE_KIT_VERSION = "0.2.10";
221
221
 
222
222
  export { Button, type ButtonProps, type ButtonVariant, Container, type ContainerProps, FAQ, type FAQProps, type FaqItem, Hero, type HeroProps, Hours, type HoursProps, type HoursRow, MapEmbed, type MapEmbedProps, PortalClient, PortalPrefill, PortalProvider, type PortalProviderProps, type PortalSession, type PortalSessionStatus, PublicClaim, Row, type RowProps, Section, type SectionProps, type Service, ServiceGrid, type ServiceGridProps, Stack, type StackProps, TeamGrid, type TeamGridProps, type TeamMember, ThemeProvider, type ThemeProviderProps, WEBSITE_KIT_VERSION, WebsiteShell, type WebsiteShellProps, type WebsiteTheme, WhoAmI, defaultTheme, resolveTheme, themeToCssVars, usePortalClient, usePortalSession };
package/dist/index.js CHANGED
@@ -11,7 +11,7 @@ import {
11
11
  import {
12
12
  ISLANDS,
13
13
  mountIslands
14
- } from "./chunk-DP2HJMVB.js";
14
+ } from "./chunk-FW2X2SUZ.js";
15
15
  import {
16
16
  PortalAccount,
17
17
  PortalPanel,
@@ -23,7 +23,7 @@ import {
23
23
  getThemeTokens,
24
24
  usePortalClient,
25
25
  usePortalSession
26
- } from "./chunk-4GKSSM5N.js";
26
+ } from "./chunk-SYXA5WP7.js";
27
27
  import "./chunk-3T7UWKAH.js";
28
28
  import {
29
29
  evaluateShowWhen,
@@ -36,9 +36,9 @@ import {
36
36
  mountIslandsWith,
37
37
  readIslandProps
38
38
  } from "./chunk-THMT43MV.js";
39
- import "./chunk-U6FJ6XYX.js";
40
- import "./chunk-ORGTP5GB.js";
41
- import "./chunk-BHF7S6UQ.js";
39
+ import "./chunk-55W3KKJD.js";
40
+ import "./chunk-Q5DIBKWX.js";
41
+ import "./chunk-LW2LLAL4.js";
42
42
  import "./chunk-S3ZQQOQT.js";
43
43
  import {
44
44
  BPOINT_SCRIPT_ORIGIN,
@@ -511,7 +511,7 @@ function Checkout({
511
511
  }
512
512
 
513
513
  // src/index.ts
514
- var WEBSITE_KIT_VERSION = "0.2.9";
514
+ var WEBSITE_KIT_VERSION = "0.2.10";
515
515
  export {
516
516
  BPOINT_SCRIPT_ORIGIN,
517
517
  BPOINT_SCRIPT_URL,
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  CartButtonClient,
3
3
  CartDrawer
4
- } from "../chunk-ORGTP5GB.js";
5
- import "../chunk-BHF7S6UQ.js";
4
+ } from "../chunk-Q5DIBKWX.js";
5
+ import "../chunk-LW2LLAL4.js";
6
6
  import "../chunk-7BUDUYXN.js";
7
7
  import "../chunk-4NF7SSIX.js";
8
8
  import "../chunk-FZ4WKWIE.js";
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  CartClient
3
- } from "../chunk-U6FJ6XYX.js";
4
- import "../chunk-BHF7S6UQ.js";
3
+ } from "../chunk-55W3KKJD.js";
4
+ import "../chunk-LW2LLAL4.js";
5
5
  import "../chunk-7BUDUYXN.js";
6
6
  import "../chunk-4NF7SSIX.js";
7
7
  import "../chunk-FZ4WKWIE.js";
@@ -3,17 +3,17 @@ import {
3
3
  CertificateFunnelClient,
4
4
  PortalAccountClient,
5
5
  PortalPanelClient
6
- } from "./chunk-4GKSSM5N.js";
6
+ } from "./chunk-SYXA5WP7.js";
7
7
  import "./chunk-3T7UWKAH.js";
8
8
  import "./chunk-QDO3SJWR.js";
9
9
  import "./chunk-THMT43MV.js";
10
10
  import {
11
11
  CartClient
12
- } from "./chunk-U6FJ6XYX.js";
12
+ } from "./chunk-55W3KKJD.js";
13
13
  import {
14
14
  CartButtonClient
15
- } from "./chunk-ORGTP5GB.js";
16
- import "./chunk-BHF7S6UQ.js";
15
+ } from "./chunk-Q5DIBKWX.js";
16
+ import "./chunk-LW2LLAL4.js";
17
17
  import {
18
18
  CheckoutClient
19
19
  } from "./chunk-S3ZQQOQT.js";
@@ -1,14 +1,14 @@
1
1
  import {
2
2
  ISLANDS,
3
3
  mountIslands
4
- } from "./chunk-DP2HJMVB.js";
5
- import "./chunk-4GKSSM5N.js";
4
+ } from "./chunk-FW2X2SUZ.js";
5
+ import "./chunk-SYXA5WP7.js";
6
6
  import "./chunk-3T7UWKAH.js";
7
7
  import "./chunk-QDO3SJWR.js";
8
8
  import "./chunk-THMT43MV.js";
9
- import "./chunk-U6FJ6XYX.js";
10
- import "./chunk-ORGTP5GB.js";
11
- import "./chunk-BHF7S6UQ.js";
9
+ import "./chunk-55W3KKJD.js";
10
+ import "./chunk-Q5DIBKWX.js";
11
+ import "./chunk-LW2LLAL4.js";
12
12
  import "./chunk-S3ZQQOQT.js";
13
13
  import "./chunk-ZOF22TJA.js";
14
14
  import "./chunk-UTAMB2SK.js";
@@ -2414,16 +2414,78 @@ select.sk-identity__input[data-empty] {
2414
2414
  * to earn its keep, and it buys nothing here — this is on screen for a few hundred ms.
2415
2415
  * This is NOT the no-JS fallback below; that one is real content and must stay real. */
2416
2416
  .sk-portal-account__skeleton {
2417
- display: flex;
2418
- flex-direction: column;
2419
- gap: var(--sk-space, 0.5rem);
2420
- margin-bottom: calc(var(--sk-space, 0.5rem) * 2);
2417
+ display: block;
2418
+ margin-bottom: 0;
2421
2419
  }
2422
2420
 
2423
- .sk-portal-account__skeleton-row {
2424
- height: 2.75rem;
2421
+ /* PAT-949 — the skeleton borrows `__header` and `__item` for its boxes (see the
2422
+ * component), so the only thing defined here is the INK inside them and the two
2423
+ * differences a placeholder needs: it cannot be clicked, and its header carries no
2424
+ * text to give it height, so it gets the height back explicitly. */
2425
+ .sk-portal-account__header--skeleton {
2426
+ min-height: 2.25rem;
2427
+ }
2428
+
2429
+ .sk-portal-account__item--skeleton {
2430
+ pointer-events: none;
2431
+ }
2432
+
2433
+ .sk-portal-account__skeleton-line {
2434
+ display: block;
2435
+ height: 0.875rem;
2425
2436
  border-radius: var(--sk-radius, 0.625rem);
2426
- background: color-mix(in srgb, var(--sk-neutral-foreground, #1b2733) 7%, transparent);
2437
+ background: color-mix(in srgb, var(--sk-neutral-foreground, #1b2733) 12%, transparent);
2438
+ }
2439
+
2440
+ /* Widths chosen to sit under the real strings they stand in for: "Signed in as Jane",
2441
+ * a "Sign out" ghost button, an item title and its date · clinician · amount line. */
2442
+ .sk-portal-account__skeleton-line--who {
2443
+ width: 10rem;
2444
+ }
2445
+
2446
+ .sk-portal-account__skeleton-line--button {
2447
+ width: 5.5rem;
2448
+ height: 2rem;
2449
+ }
2450
+
2451
+ .sk-portal-account__skeleton-line--title {
2452
+ width: 60%;
2453
+ }
2454
+
2455
+ .sk-portal-account__skeleton-line--meta {
2456
+ width: 40%;
2457
+ height: 0.75rem;
2458
+ }
2459
+
2460
+ /* Stands in for a group heading ("UPCOMING"), so the list does not slide up under one
2461
+ * when the real body lands. */
2462
+ .sk-portal-account__group-title--skeleton {
2463
+ display: block;
2464
+ }
2465
+
2466
+ .sk-portal-account__skeleton-line--group {
2467
+ width: 6rem;
2468
+ height: 0.75rem;
2469
+ }
2470
+
2471
+ @media (prefers-reduced-motion: no-preference) {
2472
+ .sk-portal-account__skeleton-line {
2473
+ animation: sk-skeleton-pulse 1.4s ease-in-out infinite;
2474
+ }
2475
+ }
2476
+
2477
+ /* The screen-reader-only status line the skeleton announces itself with — the bars
2478
+ * themselves are `aria-hidden`. Same recipe as the store block's (PAT-944). */
2479
+ .sk-portal-account__placeholder--sr {
2480
+ position: absolute;
2481
+ width: 1px;
2482
+ height: 1px;
2483
+ padding: 0;
2484
+ margin: -1px;
2485
+ overflow: hidden;
2486
+ clip-path: inset(50%);
2487
+ white-space: nowrap;
2488
+ border: 0;
2427
2489
  }
2428
2490
 
2429
2491
  /* The build-time no-JS fallback: REAL visible content, never a hidden skeleton. */
@@ -2887,6 +2949,18 @@ select.sk-identity__input[data-empty] {
2887
2949
  border-radius: var(--sk-radius, 0.625rem);
2888
2950
  font-size: 0.9375rem;
2889
2951
  }
2952
+ .sk-cart__lines[aria-busy='true'] {
2953
+ /* A re-quote is in flight over prices that are still correct enough to read. Dim the
2954
+ money a touch rather than tearing the list down — the teardown is what read as the
2955
+ cart reloading (PAT-949). Steppers stay live: the cart write already succeeded. */
2956
+ opacity: 0.7;
2957
+ transition: opacity 120ms ease-out;
2958
+ }
2959
+ @media (prefers-reduced-motion: reduce) {
2960
+ .sk-cart__lines[aria-busy='true'] {
2961
+ transition: none;
2962
+ }
2963
+ }
2890
2964
  .sk-cart__lines {
2891
2965
  list-style: none;
2892
2966
  margin: 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@patientos/website-kit",
3
- "version": "0.2.9",
3
+ "version": "0.2.10",
4
4
  "description": "PatientOS clinic website components and patient-facing interactive islands.",
5
5
  "type": "module",
6
6
  "sideEffects": [