@sevenfold/setto-client 0.7.0 → 0.8.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/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export { SettoProvider, useSetto } from './provider';
2
2
  export { GuestEditProvider, useGuestEdit } from './guest-edit';
3
+ export { SettoPreviewBar, isPreviewHost } from './preview-bar';
3
4
  export { T } from './T';
4
5
  export { SettoSection } from './SettoSection';
5
6
  export { SettoBlock } from './SettoBlock';
@@ -0,0 +1,29 @@
1
+ /** True when this page is served from a Setto preview address. */
2
+ export declare function isPreviewHost(hostname: string): boolean;
3
+ export type PreviewBarState = 'hidden' | 'owner' | 'visitor';
4
+ /**
5
+ * Whether the bar shows at all, and as whom.
6
+ *
7
+ * Pulled out of the component because the interesting failures are all in this
8
+ * decision and none of them are visual: showing «Publiser» to somebody who has
9
+ * already paid, showing «Forhåndsvisning» on a site that has been bought, or
10
+ * flashing either one during the round trip that would have said otherwise.
11
+ *
12
+ * Unknown is a state, not a default. Both `sitesLoaded` and `soldPublic` start
13
+ * out unresolved, and every unresolved input means `hidden` — the bar arrives
14
+ * a moment late rather than asserting something it has to take back.
15
+ */
16
+ export declare function previewBarState(input: {
17
+ onPreview: boolean;
18
+ authLoading: boolean;
19
+ hasSession: boolean;
20
+ /** `sites !== null` — whether this account's site list has come back. */
21
+ sitesLoaded: boolean;
22
+ hasCurrentSite: boolean;
23
+ currentSiteIsLive: boolean;
24
+ /** The public answer, null until it arrives or if it never does. */
25
+ soldPublic: boolean | null;
26
+ /** Stripe's own `?betaling=ok`, which lands before the webhook does. */
27
+ paidMarker: boolean;
28
+ }): PreviewBarState;
29
+ export declare function SettoPreviewBar(): import("react").ReactPortal | null;
@@ -21358,6 +21358,175 @@ function useSiteAccess(supabase, session, siteId) {
21358
21358
  }, [supabase, session, siteId]);
21359
21359
  return state;
21360
21360
  }
21361
+ const PREVIEW_HOST = /(^|\.)site\.setto\.no$/i;
21362
+ function isPreviewHost(hostname) {
21363
+ return PREVIEW_HOST.test(hostname);
21364
+ }
21365
+ function previewBarState(input) {
21366
+ if (!input.onPreview || input.authLoading) return "hidden";
21367
+ if (input.hasSession && !input.sitesLoaded) return "hidden";
21368
+ if (!input.hasSession && input.soldPublic === null) return "hidden";
21369
+ const bought = input.paidMarker || input.currentSiteIsLive || input.soldPublic === true;
21370
+ if (bought) return "hidden";
21371
+ return input.hasSession && input.hasCurrentSite ? "owner" : "visitor";
21372
+ }
21373
+ function SettoPreviewBar() {
21374
+ const { supabase, api, config, session, authLoading, currentSite, sites, editMode } = useSetto();
21375
+ const [onPreview, setOnPreview] = useState(false);
21376
+ const [busy, setBusy] = useState(false);
21377
+ const [error, setError] = useState(null);
21378
+ const [paid, setPaid] = useState(false);
21379
+ const [soldPublic, setSoldPublic] = useState(null);
21380
+ useEffect(() => {
21381
+ setOnPreview(isPreviewHost(window.location.hostname));
21382
+ setPaid(new URLSearchParams(window.location.search).get("betaling") === "ok");
21383
+ }, []);
21384
+ useEffect(() => {
21385
+ if (!onPreview || session) return;
21386
+ let cancelled = false;
21387
+ api.request(`/onboarding/sites/${config.siteId}/sold`, {}, "siteSold").then((d) => {
21388
+ if (!cancelled) setSoldPublic(d.sold === true);
21389
+ }).catch(() => {
21390
+ if (!cancelled) setSoldPublic(null);
21391
+ });
21392
+ return () => {
21393
+ cancelled = true;
21394
+ };
21395
+ }, [onPreview, session, api, config.siteId]);
21396
+ const state = previewBarState({
21397
+ onPreview,
21398
+ authLoading,
21399
+ hasSession: !!session,
21400
+ sitesLoaded: sites !== null,
21401
+ hasCurrentSite: !!currentSite,
21402
+ currentSiteIsLive: currentSite?.status === "live",
21403
+ soldPublic,
21404
+ paidMarker: paid
21405
+ });
21406
+ if (state === "hidden") return null;
21407
+ const owner = state === "owner";
21408
+ const name = currentSite?.display_name?.trim() || config.displayName?.trim() || config.siteId;
21409
+ const signIn = () => {
21410
+ window.location.href = "/setto";
21411
+ };
21412
+ const publish = async () => {
21413
+ setBusy(true);
21414
+ setError(null);
21415
+ try {
21416
+ const token = (await supabase.auth.getSession()).data.session?.access_token;
21417
+ if (!token) {
21418
+ signIn();
21419
+ return;
21420
+ }
21421
+ const data = await api.request(
21422
+ `/onboarding/sites/${config.siteId}/checkout`,
21423
+ {
21424
+ method: "POST",
21425
+ // Back to the page they were looking at, not to a chat window.
21426
+ body: { returnTo: window.location.origin + window.location.pathname }
21427
+ },
21428
+ "checkout"
21429
+ );
21430
+ if (!data.url) throw new Error("checkout_failed");
21431
+ window.location.href = data.url;
21432
+ } catch (err) {
21433
+ const detail = err instanceof Error ? err.message : "";
21434
+ setError(
21435
+ detail.includes("already_paid") ? "Denne siden er allerede betalt." : detail.includes("not_found") ? "Fant ingen bestilling for denne siden på kontoen din." : detail.includes("stripe_not_configured") ? "Betaling er ikke satt opp ennå." : "Klarte ikke starte betalingen. Prøv igjen."
21436
+ );
21437
+ setBusy(false);
21438
+ }
21439
+ };
21440
+ return createPortal(
21441
+ /*
21442
+ * Lifted above the editor's floating button when edit mode is on, which
21443
+ * sits in the same corner band. Sideways would collide again the moment
21444
+ * its toolbar opens, since that expands rightward.
21445
+ */
21446
+ /* @__PURE__ */ jsx(
21447
+ "div",
21448
+ {
21449
+ style: editMode ? { ...barStyle, bottom: "calc(max(16px, env(safe-area-inset-bottom, 0px)) + 68px)" } : barStyle,
21450
+ "data-setto-preview-bar": "",
21451
+ children: owner ? /* @__PURE__ */ jsxs(Fragment, { children: [
21452
+ /* @__PURE__ */ jsx("span", { style: dotStyle, "aria-hidden": true }),
21453
+ /* @__PURE__ */ jsxs("span", { style: labelStyle, children: [
21454
+ "Du forhåndsviser nå ",
21455
+ /* @__PURE__ */ jsx("strong", { style: { fontWeight: 600 }, children: name })
21456
+ ] }),
21457
+ error && /* @__PURE__ */ jsx("span", { style: errorStyle$1, children: error }),
21458
+ /* @__PURE__ */ jsx("button", { type: "button", style: primaryStyle, onClick: publish, disabled: busy, children: busy ? "Åpner betaling …" : "Publiser" })
21459
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
21460
+ /* @__PURE__ */ jsx("span", { style: dotStyle, "aria-hidden": true }),
21461
+ /* @__PURE__ */ jsxs("span", { style: labelStyle, children: [
21462
+ "Forhåndsvisning av ",
21463
+ name
21464
+ ] }),
21465
+ /* @__PURE__ */ jsx("button", { type: "button", style: primaryStyle, onClick: signIn, children: "Rediger" })
21466
+ ] })
21467
+ }
21468
+ ),
21469
+ document.body
21470
+ );
21471
+ }
21472
+ const barStyle = {
21473
+ position: "fixed",
21474
+ left: "50%",
21475
+ bottom: "max(16px, env(safe-area-inset-bottom, 0px))",
21476
+ transform: "translateX(-50%)",
21477
+ zIndex: 2147483e3,
21478
+ display: "flex",
21479
+ alignItems: "center",
21480
+ gap: 14,
21481
+ maxWidth: "calc(100vw - 24px)",
21482
+ padding: "10px 10px 10px 18px",
21483
+ borderRadius: 999,
21484
+ background: "#211711",
21485
+ color: "#fffdf8",
21486
+ fontFamily: 'system-ui, -apple-system, "Segoe UI", sans-serif',
21487
+ fontSize: 14,
21488
+ lineHeight: 1.3,
21489
+ boxShadow: "0 18px 40px -18px rgba(0, 0, 0, 0.65)"
21490
+ };
21491
+ const dotStyle = {
21492
+ width: 7,
21493
+ height: 7,
21494
+ borderRadius: "50%",
21495
+ background: "#c4502a",
21496
+ flex: "none"
21497
+ };
21498
+ const labelStyle = {
21499
+ whiteSpace: "nowrap",
21500
+ overflow: "hidden",
21501
+ textOverflow: "ellipsis"
21502
+ };
21503
+ const primaryStyle = {
21504
+ flex: "none",
21505
+ padding: "9px 18px",
21506
+ border: 0,
21507
+ borderRadius: 999,
21508
+ background: "#c4502a",
21509
+ color: "#fffdf8",
21510
+ font: "inherit",
21511
+ fontWeight: 600,
21512
+ cursor: "pointer"
21513
+ };
21514
+ const errorStyle$1 = {
21515
+ color: "#ffb4a2",
21516
+ fontSize: 13,
21517
+ /*
21518
+ * Shrinkable, unlike the label beside it.
21519
+ *
21520
+ * `nowrap` on a flex child whose `min-width` is `auto` cannot shrink below
21521
+ * its own text, so on a 320px phone the error shouldered «Publiser» 19px
21522
+ * past the right edge of the screen and the bar clipped it — the button
21523
+ * that takes the money, unreachable, exactly when something had gone wrong.
21524
+ */
21525
+ minWidth: 0,
21526
+ overflow: "hidden",
21527
+ textOverflow: "ellipsis",
21528
+ whiteSpace: "nowrap"
21529
+ };
21361
21530
  const SETTO_BASE = "/setto";
21362
21531
  function siteHomeUrl(pathname = "/") {
21363
21532
  const u = new URL(window.location.href);
@@ -24872,7 +25041,10 @@ function SettoProvider({ config, children }) {
24872
25041
  assetStore: storeReady ? assetStoreRef.current : null
24873
25042
  };
24874
25043
  const editShellActive = editMode && !!value.store;
24875
- return /* @__PURE__ */ jsx(SettoContext.Provider, { value, children: /* @__PURE__ */ jsx(SectionEditProvider, { children: /* @__PURE__ */ jsx(EditModeShell, { active: editShellActive, themeStore: value.themeStore, children }) }) });
25044
+ return /* @__PURE__ */ jsx(SettoContext.Provider, { value, children: /* @__PURE__ */ jsxs(SectionEditProvider, { children: [
25045
+ /* @__PURE__ */ jsx(EditModeShell, { active: editShellActive, themeStore: value.themeStore, children }),
25046
+ !onAdminRoute && /* @__PURE__ */ jsx(SettoPreviewBar, {})
25047
+ ] }) });
24876
25048
  }
24877
25049
  function useSetto() {
24878
25050
  const ctx = useContext(SettoContext);
@@ -26643,6 +26815,7 @@ export {
26643
26815
  SettoForm,
26644
26816
  SettoIcon,
26645
26817
  SettoImage,
26818
+ SettoPreviewBar,
26646
26819
  SettoProvider,
26647
26820
  SettoRepeater,
26648
26821
  SettoSection,
@@ -26652,6 +26825,7 @@ export {
26652
26825
  extractContentKeys,
26653
26826
  formFieldsPrefix,
26654
26827
  isFormFieldType,
26828
+ isPreviewHost,
26655
26829
  nodeChildren,
26656
26830
  parseBlockTree,
26657
26831
  readLocaleFields,