@shoppexio/storefront-react 0.1.5 → 0.2.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/CHANGELOG.md +44 -0
- package/README.md +7 -1
- package/dist/account.js +35 -68
- package/dist/account.js.map +1 -1
- package/dist/{chunk-D6XKDVAV.js → chunk-ZEFZPUAW.js} +152 -2
- package/dist/chunk-ZEFZPUAW.js.map +1 -0
- package/dist/index.d.ts +205 -10
- package/dist/index.js +157 -4
- package/dist/index.js.map +1 -1
- package/eject/account/account-page.tsx +26 -6
- package/eject/account/favorites.tsx +3 -0
- package/package.json +1 -1
- package/dist/chunk-D6XKDVAV.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,49 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.1
|
|
4
|
+
|
|
5
|
+
- Favorites follow the account session without a reload. Signing in on the
|
|
6
|
+
account page reloads the favorites store, so the hearts on cards and the
|
|
7
|
+
product page work at once instead of sending the buyer back to Account.
|
|
8
|
+
Signing out reloads it too, which clears the previous buyer's saved set
|
|
9
|
+
instead of leaving it on the page for whoever uses the browser next.
|
|
10
|
+
(`announceCustomerSessionChange()`, `signOutEndedSession()`.)
|
|
11
|
+
|
|
12
|
+
## 0.2.0
|
|
13
|
+
|
|
14
|
+
Breaking:
|
|
15
|
+
|
|
16
|
+
- `settings.availableGateways` is gone. Use `settings.paymentMethods`: the
|
|
17
|
+
public payment methods the backend derives from the shop's live gateways,
|
|
18
|
+
each `{ id, label, kind }` with `kind` `card`, `wallet` or `crypto`,
|
|
19
|
+
already in display order. A theme no longer maps gateway codes itself.
|
|
20
|
+
- `resolveDeliveryPromise()` returns `{ kind: 'unspecified' }` when the
|
|
21
|
+
merchant set no delivery time. A missing delivery time used to read as
|
|
22
|
+
instant delivery; hosted checkout never promised that, and themes no longer
|
|
23
|
+
do. Handle the new kind in an exhaustive `switch`.
|
|
24
|
+
|
|
25
|
+
Added:
|
|
26
|
+
|
|
27
|
+
- Product meta: `shortDescription` (one plain-text line, at most 200
|
|
28
|
+
characters), the unit labels `unitLabelSingular` / `unitLabelPlural`, an
|
|
29
|
+
option's `description`, and `resolveYouReceive()` for a "You receive" line
|
|
30
|
+
that only states the real delivery type: keys for serial products, a
|
|
31
|
+
download for file products, nothing otherwise.
|
|
32
|
+
- Favorites: `<FavoritesProvider>` and `useFavorites()` for a heart on
|
|
33
|
+
cards and the product page. It shares one store with the account's
|
|
34
|
+
favorites tab (`FAVORITES_CHANGED_EVENT`) and reports `signed_out`
|
|
35
|
+
instead of failing when there is no customer session.
|
|
36
|
+
- Back-in-stock ("Notify me"): `canRequestStockAlert()` and
|
|
37
|
+
`requestStockAlert()`. `settings.stockAlertsEnabled` reflects the shop
|
|
38
|
+
toggle.
|
|
39
|
+
- Status page: `parseServiceStatus()` and `buildStatusPageModel()` for a
|
|
40
|
+
`/status` page. It lists only the products the merchant gave a status
|
|
41
|
+
(`operational`, `updating`, `detected`, `offline`).
|
|
42
|
+
|
|
43
|
+
Needs: a Shoppex backend that sends `payment_methods`,
|
|
44
|
+
`stock_alerts_enabled`, `short_description` and `service_status` in the
|
|
45
|
+
storefront payload. Parsing fails fast when a field is missing.
|
|
46
|
+
|
|
3
47
|
## 0.1.5
|
|
4
48
|
|
|
5
49
|
- `loadShopPage()` returns `error` for a failed request or render and `missing`
|
package/README.md
CHANGED
|
@@ -80,7 +80,10 @@ createRoot(document.getElementById('root')!).render(
|
|
|
80
80
|
|
|
81
81
|
- Catalog: product and bundle cards, categories, sorting, filtering, paging.
|
|
82
82
|
- Product pages: options, pricing per quantity, stock, badges, videos, the
|
|
83
|
-
rating breakdown.
|
|
83
|
+
rating breakdown, a short description and a "You receive" line.
|
|
84
|
+
- Favorites on cards and product pages, "Notify me" for sold-out products,
|
|
85
|
+
and a `/status` page from the statuses the merchant sets.
|
|
86
|
+
- Payment method marks from the shop's live payment methods.
|
|
84
87
|
- Cart: add, update, remove, coupons, quotes from the server, checkout
|
|
85
88
|
hand-off, per-product checkout fields.
|
|
86
89
|
- Reviews, shop pages, navigation menus and the contact form, from the SDK.
|
|
@@ -164,6 +167,9 @@ Most themes never need this.
|
|
|
164
167
|
| Product | The product page model: variants, rating, pricing, quantity bounds, badges, videos | `buildProductDetail()`, `calculateLinePricing()`, `badgesForProductPage()` |
|
|
165
168
|
| Cart | Cart state, quotes, coupons, checkout hand-off | `<CartProvider>`, `useCart()`, `buildCartView()`, `buildCartTotals()` |
|
|
166
169
|
| Checkout fields | Per-product fields a buyer fills before checkout | `resolveCheckoutFields()`, `readCheckoutNeedsDetails()` |
|
|
170
|
+
| Favorites | The signed-in customer's saved products, shared with the account tab | `<FavoritesProvider>`, `useFavorites()` |
|
|
171
|
+
| Back in stock | "Notify me" on a sold-out product or option | `canRequestStockAlert()`, `requestStockAlert()` |
|
|
172
|
+
| Status page | The merchant-set status of each product | `buildStatusPageModel()`, `parseServiceStatus()` |
|
|
167
173
|
| Reviews, pages, menus, contact | SDK-backed hooks | `usePagedShopReviews()`, `loadLatestWrittenReviews()`, `loadShopPage()`, `useNavigationLinks()`, `submitContactTicket()` |
|
|
168
174
|
| Merchant HTML | Sanitizing and flattening merchant rich text | `sanitizeMerchantHtml()`, `flattenMerchantHtmlToText()` |
|
|
169
175
|
| Account routing | Mounting the account and its lazy chunk | `ACCOUNT_PATH`, `ACCOUNT_PATH_ALIAS`, `accountRouteMessage()`, `ACCOUNT_STOREFRONT_ROUTES`, `ACCOUNT_ARTIFACT_CAPABILITIES` |
|
package/dist/account.js
CHANGED
|
@@ -4,9 +4,13 @@ import {
|
|
|
4
4
|
accountTabHref,
|
|
5
5
|
accountTabLabelKey,
|
|
6
6
|
canonicalAccountPath,
|
|
7
|
+
favoriteProductHref,
|
|
7
8
|
getShoppex,
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
narrowFavorites,
|
|
10
|
+
notifyFavoritesChanged,
|
|
11
|
+
resolveAccountView,
|
|
12
|
+
withoutFavorite
|
|
13
|
+
} from "./chunk-ZEFZPUAW.js";
|
|
10
14
|
|
|
11
15
|
// src/account/account-page.tsx
|
|
12
16
|
import { useEffect as useEffect5, useState as useState11 } from "react";
|
|
@@ -8618,52 +8622,6 @@ function CodeRow({ code }) {
|
|
|
8618
8622
|
// src/account/favorites.tsx
|
|
8619
8623
|
import { useEffect as useEffect2, useState as useState3 } from "react";
|
|
8620
8624
|
import { Link as Link3 } from "react-router-dom";
|
|
8621
|
-
|
|
8622
|
-
// src/account/account-favorites.ts
|
|
8623
|
-
function isRecord2(value) {
|
|
8624
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
8625
|
-
}
|
|
8626
|
-
function readNullableString(record, key) {
|
|
8627
|
-
const value = record[key];
|
|
8628
|
-
if (value === null || typeof value === "string") return value;
|
|
8629
|
-
return void 0;
|
|
8630
|
-
}
|
|
8631
|
-
function narrowFavorites(payload) {
|
|
8632
|
-
if (!isRecord2(payload) || !Array.isArray(payload.favorites)) return { ok: false };
|
|
8633
|
-
const favorites = [];
|
|
8634
|
-
for (const entry of payload.favorites) {
|
|
8635
|
-
if (!isRecord2(entry)) return { ok: false };
|
|
8636
|
-
const productUniqid = entry.product_uniqid;
|
|
8637
|
-
const title = entry.title;
|
|
8638
|
-
const slug = readNullableString(entry, "slug");
|
|
8639
|
-
const imageUrl = readNullableString(entry, "image_url");
|
|
8640
|
-
const priceDisplay = entry.price_display;
|
|
8641
|
-
const unavailable = entry.unavailable;
|
|
8642
|
-
const createdAt = entry.created_at;
|
|
8643
|
-
if (typeof productUniqid !== "string" || typeof title !== "string" || slug === void 0 || imageUrl === void 0 || typeof priceDisplay !== "number" || !Number.isFinite(priceDisplay) || typeof unavailable !== "boolean" || typeof createdAt !== "string") {
|
|
8644
|
-
return { ok: false };
|
|
8645
|
-
}
|
|
8646
|
-
favorites.push({
|
|
8647
|
-
product_uniqid: productUniqid,
|
|
8648
|
-
title,
|
|
8649
|
-
slug,
|
|
8650
|
-
image_url: imageUrl,
|
|
8651
|
-
price_display: priceDisplay,
|
|
8652
|
-
unavailable,
|
|
8653
|
-
created_at: createdAt
|
|
8654
|
-
});
|
|
8655
|
-
}
|
|
8656
|
-
return { ok: true, favorites };
|
|
8657
|
-
}
|
|
8658
|
-
function favoriteProductHref(favorite) {
|
|
8659
|
-
if (favorite.unavailable || !favorite.slug) return null;
|
|
8660
|
-
return `/product/${encodeURIComponent(favorite.slug)}`;
|
|
8661
|
-
}
|
|
8662
|
-
function withoutFavorite(favorites, productUniqid) {
|
|
8663
|
-
return favorites.filter((favorite) => favorite.product_uniqid !== productUniqid);
|
|
8664
|
-
}
|
|
8665
|
-
|
|
8666
|
-
// src/account/favorites.tsx
|
|
8667
8625
|
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
8668
8626
|
function AccountFavorites() {
|
|
8669
8627
|
const { state, reload } = useAccountResource("favorites", () => callCustomerApi((sdk) => sdk.favorites()));
|
|
@@ -8709,6 +8667,7 @@ function AccountFavorites() {
|
|
|
8709
8667
|
setRemoveError(result.message?.trim() || t("favorites.removeFailed"));
|
|
8710
8668
|
return;
|
|
8711
8669
|
}
|
|
8670
|
+
notifyFavoritesChanged();
|
|
8712
8671
|
setFavorites((current) => current ? withoutFavorite(current, favorite.product_uniqid) : current);
|
|
8713
8672
|
};
|
|
8714
8673
|
return /* @__PURE__ */ jsxs4("div", { children: [
|
|
@@ -11264,21 +11223,21 @@ function grantStatusLabel(status) {
|
|
|
11264
11223
|
import { useEffect as useEffect4, useId as useId2, useRef as useRef4, useState as useState8 } from "react";
|
|
11265
11224
|
|
|
11266
11225
|
// src/account/account-settings.ts
|
|
11267
|
-
function
|
|
11226
|
+
function isRecord2(value) {
|
|
11268
11227
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
11269
11228
|
}
|
|
11270
|
-
function
|
|
11229
|
+
function readNullableString(record, key) {
|
|
11271
11230
|
const value = record[key];
|
|
11272
11231
|
if (value === null || typeof value === "string") return value;
|
|
11273
11232
|
return void 0;
|
|
11274
11233
|
}
|
|
11275
11234
|
function narrowEmailPreferences(payload) {
|
|
11276
|
-
if (!
|
|
11235
|
+
if (!isRecord2(payload) || typeof payload.global_unsubscribed !== "boolean") return { ok: false };
|
|
11277
11236
|
if (!Array.isArray(payload.lists)) return { ok: false };
|
|
11278
11237
|
const lists = [];
|
|
11279
11238
|
for (const entry of payload.lists) {
|
|
11280
|
-
if (!
|
|
11281
|
-
const description =
|
|
11239
|
+
if (!isRecord2(entry)) return { ok: false };
|
|
11240
|
+
const description = readNullableString(entry, "description");
|
|
11282
11241
|
if (typeof entry.id !== "string" || typeof entry.name !== "string" || description === void 0 || typeof entry.subscribed !== "boolean") {
|
|
11283
11242
|
return { ok: false };
|
|
11284
11243
|
}
|
|
@@ -11287,12 +11246,12 @@ function narrowEmailPreferences(payload) {
|
|
|
11287
11246
|
return { ok: true, preferences: { global_unsubscribed: payload.global_unsubscribed, lists } };
|
|
11288
11247
|
}
|
|
11289
11248
|
function narrowCustomerSessions(payload) {
|
|
11290
|
-
if (!
|
|
11249
|
+
if (!isRecord2(payload) || !Array.isArray(payload.sessions)) return { ok: false };
|
|
11291
11250
|
const sessions = [];
|
|
11292
11251
|
for (const entry of payload.sessions) {
|
|
11293
|
-
if (!
|
|
11294
|
-
const ipAddress =
|
|
11295
|
-
const userAgent =
|
|
11252
|
+
if (!isRecord2(entry)) return { ok: false };
|
|
11253
|
+
const ipAddress = readNullableString(entry, "ip_address");
|
|
11254
|
+
const userAgent = readNullableString(entry, "user_agent");
|
|
11296
11255
|
if (typeof entry.id !== "string" || typeof entry.created_at !== "string" || typeof entry.expires_at !== "string" || ipAddress === void 0 || userAgent === void 0 || typeof entry.current !== "boolean") {
|
|
11297
11256
|
return { ok: false };
|
|
11298
11257
|
}
|
|
@@ -11857,7 +11816,7 @@ function billingMessage(plan) {
|
|
|
11857
11816
|
function cancelsAtPeriodEnd(gateway) {
|
|
11858
11817
|
return gateway.trim().toUpperCase() !== "PAYPAL";
|
|
11859
11818
|
}
|
|
11860
|
-
function
|
|
11819
|
+
function isRecord3(value) {
|
|
11861
11820
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
11862
11821
|
}
|
|
11863
11822
|
function readString(record, key) {
|
|
@@ -11865,10 +11824,10 @@ function readString(record, key) {
|
|
|
11865
11824
|
return typeof value === "string" ? value : null;
|
|
11866
11825
|
}
|
|
11867
11826
|
function narrowBillingHistory(payload) {
|
|
11868
|
-
if (!
|
|
11827
|
+
if (!isRecord3(payload) || !Array.isArray(payload.invoices)) return { ok: false };
|
|
11869
11828
|
const invoices = [];
|
|
11870
11829
|
for (const entry of payload.invoices) {
|
|
11871
|
-
if (!
|
|
11830
|
+
if (!isRecord3(entry)) return { ok: false };
|
|
11872
11831
|
const uniqid = readString(entry, "uniqid");
|
|
11873
11832
|
const total = readString(entry, "total");
|
|
11874
11833
|
const currency = readString(entry, "currency");
|
|
@@ -12135,10 +12094,10 @@ function BillingHistoryList({ payload, reload }) {
|
|
|
12135
12094
|
import { useId as useId3, useState as useState10 } from "react";
|
|
12136
12095
|
|
|
12137
12096
|
// src/account/account-support.ts
|
|
12138
|
-
function
|
|
12097
|
+
function isRecord4(value) {
|
|
12139
12098
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
12140
12099
|
}
|
|
12141
|
-
function
|
|
12100
|
+
function readNullableString2(record, key) {
|
|
12142
12101
|
const value = record[key];
|
|
12143
12102
|
if (value === null || typeof value === "string") return value;
|
|
12144
12103
|
return void 0;
|
|
@@ -12147,12 +12106,12 @@ function isSupportMessageRole(value) {
|
|
|
12147
12106
|
return value === "CUSTOMER" || value === "MERCHANT" || value === "SYSTEM";
|
|
12148
12107
|
}
|
|
12149
12108
|
function narrowSupportTicket(payload) {
|
|
12150
|
-
if (!
|
|
12109
|
+
if (!isRecord4(payload) || !isRecord4(payload.ticket)) return { ok: false };
|
|
12151
12110
|
const raw = payload.ticket;
|
|
12152
12111
|
const uniqid = raw.uniqid;
|
|
12153
|
-
const title =
|
|
12112
|
+
const title = readNullableString2(raw, "title");
|
|
12154
12113
|
const status = raw.status;
|
|
12155
|
-
const invoiceId =
|
|
12114
|
+
const invoiceId = readNullableString2(raw, "invoice_id");
|
|
12156
12115
|
const createdAt = raw.created_at;
|
|
12157
12116
|
const updatedAt = raw.updated_at;
|
|
12158
12117
|
if (typeof uniqid !== "string" || title === void 0 || typeof status !== "string" || invoiceId === void 0 || typeof createdAt !== "string" || typeof updatedAt !== "string" || !Array.isArray(raw.messages)) {
|
|
@@ -12160,7 +12119,7 @@ function narrowSupportTicket(payload) {
|
|
|
12160
12119
|
}
|
|
12161
12120
|
const messages = [];
|
|
12162
12121
|
for (const entry of raw.messages) {
|
|
12163
|
-
if (!
|
|
12122
|
+
if (!isRecord4(entry)) return { ok: false };
|
|
12164
12123
|
const { id, role, message, created_at: messageCreatedAt } = entry;
|
|
12165
12124
|
if (typeof id !== "string" || !isSupportMessageRole(role) || typeof message !== "string" || typeof messageCreatedAt !== "string") {
|
|
12166
12125
|
return { ok: false };
|
|
@@ -12196,7 +12155,7 @@ function messageAuthorKey(role) {
|
|
|
12196
12155
|
return "support.fromMerchant";
|
|
12197
12156
|
}
|
|
12198
12157
|
function readCreatedTicketUniqid(data) {
|
|
12199
|
-
if (!
|
|
12158
|
+
if (!isRecord4(data)) return null;
|
|
12200
12159
|
const uniqid = data.uniqid;
|
|
12201
12160
|
return typeof uniqid === "string" && uniqid.length > 0 ? uniqid : null;
|
|
12202
12161
|
}
|
|
@@ -12548,6 +12507,12 @@ function remainingSearch(search) {
|
|
|
12548
12507
|
const query = kept.toString();
|
|
12549
12508
|
return query ? `?${query}` : "";
|
|
12550
12509
|
}
|
|
12510
|
+
function announceCustomerSessionChange() {
|
|
12511
|
+
notifyFavoritesChanged();
|
|
12512
|
+
}
|
|
12513
|
+
function signOutEndedSession(result) {
|
|
12514
|
+
return result.success || result.status !== void 0;
|
|
12515
|
+
}
|
|
12551
12516
|
function AccountPage({ frame: Frame, notFound }) {
|
|
12552
12517
|
const location = useLocation();
|
|
12553
12518
|
const search = new URLSearchParams(location.search);
|
|
@@ -12586,11 +12551,12 @@ function AccountPage({ frame: Frame, notFound }) {
|
|
|
12586
12551
|
setSignOutError(null);
|
|
12587
12552
|
const result = await callCustomerApi((sdk) => sdk.logout());
|
|
12588
12553
|
setSigningOut(false);
|
|
12589
|
-
if (!result
|
|
12554
|
+
if (!signOutEndedSession(result)) {
|
|
12590
12555
|
setSignOutError(result.message?.trim() || t("account.signOutFailed"));
|
|
12591
12556
|
return;
|
|
12592
12557
|
}
|
|
12593
12558
|
setSession({ status: "signed-out" });
|
|
12559
|
+
announceCustomerSessionChange();
|
|
12594
12560
|
};
|
|
12595
12561
|
if (canonical !== null) return /* @__PURE__ */ jsx15(Navigate, { to: `${canonical}${remainingSearch(search)}`, replace: true });
|
|
12596
12562
|
if (view.kind === "unknown-tab") return /* @__PURE__ */ jsx15(Fragment3, { children: notFound });
|
|
@@ -12626,6 +12592,7 @@ function AccountPage({ frame: Frame, notFound }) {
|
|
|
12626
12592
|
onSignedIn: () => {
|
|
12627
12593
|
setPrefill(spendSignInPrefill());
|
|
12628
12594
|
setSession({ status: "signed-in" });
|
|
12595
|
+
announceCustomerSessionChange();
|
|
12629
12596
|
}
|
|
12630
12597
|
}
|
|
12631
12598
|
) : null,
|