@sevenfold/setto-client 0.9.0 → 0.10.0
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/chrome.d.ts +36 -0
- package/dist/index.d.ts +3 -1
- package/dist/setto-client.js +271 -202
- package/dist/setto-client.js.map +1 -1
- package/package.json +1 -1
package/dist/chrome.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import * as ReactDOM from 'react-dom';
|
|
3
|
+
/** What the chrome is handed. Additive changes only; nothing may be removed. */
|
|
4
|
+
export interface SettoChromeHandshake {
|
|
5
|
+
contract: number;
|
|
6
|
+
/** The host's React, so the chrome bundle ships no framework of its own. */
|
|
7
|
+
react: typeof React;
|
|
8
|
+
reactDom: typeof ReactDOM;
|
|
9
|
+
config: {
|
|
10
|
+
siteId: string;
|
|
11
|
+
apiUrl: string;
|
|
12
|
+
displayName?: string;
|
|
13
|
+
};
|
|
14
|
+
/** Read the current state. Live: call it, never cache what it returned. */
|
|
15
|
+
getState: () => {
|
|
16
|
+
hasSession: boolean;
|
|
17
|
+
accessToken: string | null;
|
|
18
|
+
currentSite: {
|
|
19
|
+
id: string;
|
|
20
|
+
display_name?: string;
|
|
21
|
+
status?: string;
|
|
22
|
+
} | null;
|
|
23
|
+
sitesLoaded: boolean;
|
|
24
|
+
editMode: boolean;
|
|
25
|
+
authLoading: boolean;
|
|
26
|
+
};
|
|
27
|
+
/** Told whenever that state changes. Returns an unsubscribe. */
|
|
28
|
+
subscribe: (listener: () => void) => () => void;
|
|
29
|
+
}
|
|
30
|
+
declare global {
|
|
31
|
+
interface Window {
|
|
32
|
+
__setto__?: SettoChromeHandshake;
|
|
33
|
+
__settoChromeLoaded__?: boolean;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export declare function SettoChrome(): null;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export { SettoProvider, useSetto } from './provider';
|
|
2
2
|
export { GuestEditProvider, useGuestEdit } from './guest-edit';
|
|
3
|
-
export { SettoPreviewBar, isPreviewHost } from './preview-bar';
|
|
3
|
+
export { SettoPreviewBar, isPreviewHost, previewBarState } from './preview-bar';
|
|
4
|
+
export { SettoChrome } from './chrome';
|
|
5
|
+
export type { SettoChromeHandshake } from './chrome';
|
|
4
6
|
export { T } from './T';
|
|
5
7
|
export { SettoSection } from './SettoSection';
|
|
6
8
|
export { SettoBlock } from './SettoBlock';
|
package/dist/setto-client.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { jsx, jsxs, Fragment } from "react/jsx-runtime";
|
|
2
|
+
import * as React from "react";
|
|
2
3
|
import { useState, useRef, useEffect, useSyncExternalStore, useLayoutEffect, createContext, useContext, useCallback, useMemo, useId, useInsertionEffect, forwardRef, Fragment as Fragment$1 } from "react";
|
|
3
4
|
import { useTranslation } from "react-i18next";
|
|
5
|
+
import * as ReactDOM from "react-dom";
|
|
4
6
|
import { createPortal } from "react-dom";
|
|
5
7
|
function __rest(s, e) {
|
|
6
8
|
var t = {};
|
|
@@ -21358,6 +21360,71 @@ function useSiteAccess(supabase, session, siteId) {
|
|
|
21358
21360
|
}, [supabase, session, siteId]);
|
|
21359
21361
|
return state;
|
|
21360
21362
|
}
|
|
21363
|
+
const CHROME_CONTRACT = 1;
|
|
21364
|
+
function chromeUrl(apiUrl) {
|
|
21365
|
+
return `${apiUrl.replace(/\/$/, "")}/chrome/v${CHROME_CONTRACT}.js`;
|
|
21366
|
+
}
|
|
21367
|
+
function SettoChrome() {
|
|
21368
|
+
const setto = useSetto();
|
|
21369
|
+
const listeners = useRef(/* @__PURE__ */ new Set());
|
|
21370
|
+
const stateRef = useRef(() => ({
|
|
21371
|
+
hasSession: false,
|
|
21372
|
+
accessToken: null,
|
|
21373
|
+
currentSite: null,
|
|
21374
|
+
sitesLoaded: false,
|
|
21375
|
+
editMode: false,
|
|
21376
|
+
authLoading: true
|
|
21377
|
+
}));
|
|
21378
|
+
stateRef.current = () => ({
|
|
21379
|
+
hasSession: !!setto.session,
|
|
21380
|
+
accessToken: setto.session?.access_token ?? null,
|
|
21381
|
+
currentSite: setto.currentSite ? {
|
|
21382
|
+
id: setto.currentSite.id,
|
|
21383
|
+
display_name: setto.currentSite.display_name,
|
|
21384
|
+
status: setto.currentSite.status
|
|
21385
|
+
} : null,
|
|
21386
|
+
sitesLoaded: setto.sites !== null,
|
|
21387
|
+
editMode: setto.editMode,
|
|
21388
|
+
authLoading: setto.authLoading
|
|
21389
|
+
});
|
|
21390
|
+
useEffect(() => {
|
|
21391
|
+
for (const listener of listeners.current) {
|
|
21392
|
+
try {
|
|
21393
|
+
listener();
|
|
21394
|
+
} catch {
|
|
21395
|
+
}
|
|
21396
|
+
}
|
|
21397
|
+
});
|
|
21398
|
+
useEffect(() => {
|
|
21399
|
+
if (typeof window === "undefined" || typeof document === "undefined") return;
|
|
21400
|
+
if (window.__settoChromeLoaded__) return;
|
|
21401
|
+
try {
|
|
21402
|
+
window.__setto__ = {
|
|
21403
|
+
contract: CHROME_CONTRACT,
|
|
21404
|
+
react: React,
|
|
21405
|
+
reactDom: ReactDOM,
|
|
21406
|
+
config: {
|
|
21407
|
+
siteId: setto.config.siteId,
|
|
21408
|
+
apiUrl: setto.config.apiUrl,
|
|
21409
|
+
displayName: setto.config.displayName
|
|
21410
|
+
},
|
|
21411
|
+
getState: () => stateRef.current(),
|
|
21412
|
+
subscribe: (listener) => {
|
|
21413
|
+
listeners.current.add(listener);
|
|
21414
|
+
return () => listeners.current.delete(listener);
|
|
21415
|
+
}
|
|
21416
|
+
};
|
|
21417
|
+
const script = document.createElement("script");
|
|
21418
|
+
script.src = chromeUrl(setto.config.apiUrl);
|
|
21419
|
+
script.async = true;
|
|
21420
|
+
script.onerror = () => void 0;
|
|
21421
|
+
document.head.appendChild(script);
|
|
21422
|
+
window.__settoChromeLoaded__ = true;
|
|
21423
|
+
} catch {
|
|
21424
|
+
}
|
|
21425
|
+
}, []);
|
|
21426
|
+
return null;
|
|
21427
|
+
}
|
|
21361
21428
|
const SETTO_BASE = "/setto";
|
|
21362
21429
|
function siteHomeUrl(pathname = "/") {
|
|
21363
21430
|
const u = new URL(window.location.href);
|
|
@@ -21392,202 +21459,6 @@ function withoutCheckoutIntent() {
|
|
|
21392
21459
|
u.searchParams.delete(CHECKOUT_INTENT);
|
|
21393
21460
|
return u.pathname + (u.search || "") + u.hash;
|
|
21394
21461
|
}
|
|
21395
|
-
const PREVIEW_HOST = /(^|\.)site\.setto\.no$/i;
|
|
21396
|
-
function isPreviewHost(hostname) {
|
|
21397
|
-
return PREVIEW_HOST.test(hostname);
|
|
21398
|
-
}
|
|
21399
|
-
function previewBarState(input2) {
|
|
21400
|
-
if (!input2.onPreview || input2.authLoading) return "hidden";
|
|
21401
|
-
if (input2.hasSession && !input2.sitesLoaded) return "hidden";
|
|
21402
|
-
if (!input2.hasSession && input2.soldPublic === null) return "hidden";
|
|
21403
|
-
const bought = input2.paidMarker || input2.currentSiteIsLive || input2.soldPublic === true;
|
|
21404
|
-
if (bought) return "hidden";
|
|
21405
|
-
return input2.hasSession && input2.hasCurrentSite ? "owner" : "visitor";
|
|
21406
|
-
}
|
|
21407
|
-
function SettoPreviewBar() {
|
|
21408
|
-
const { supabase, api, config, session, authLoading, currentSite, sites, editMode } = useSetto();
|
|
21409
|
-
const [onPreview, setOnPreview] = useState(false);
|
|
21410
|
-
const [busy, setBusy] = useState(false);
|
|
21411
|
-
const [error, setError] = useState(null);
|
|
21412
|
-
const [paid, setPaid] = useState(false);
|
|
21413
|
-
const [soldPublic, setSoldPublic] = useState(null);
|
|
21414
|
-
useEffect(() => {
|
|
21415
|
-
setOnPreview(isPreviewHost(window.location.hostname));
|
|
21416
|
-
setPaid(new URLSearchParams(window.location.search).get("betaling") === "ok");
|
|
21417
|
-
}, []);
|
|
21418
|
-
useEffect(() => {
|
|
21419
|
-
if (!onPreview || session) return;
|
|
21420
|
-
let cancelled = false;
|
|
21421
|
-
fetch(`${config.apiUrl}/onboarding/sites/${config.siteId}/sold`, {
|
|
21422
|
-
headers: { Accept: "application/json" }
|
|
21423
|
-
}).then((r) => r.ok ? r.json() : null).then((d) => {
|
|
21424
|
-
if (!cancelled && d) setSoldPublic(d.sold === true);
|
|
21425
|
-
}).catch(() => {
|
|
21426
|
-
});
|
|
21427
|
-
return () => {
|
|
21428
|
-
cancelled = true;
|
|
21429
|
-
};
|
|
21430
|
-
}, [onPreview, session, config.apiUrl, config.siteId]);
|
|
21431
|
-
const signIn = () => {
|
|
21432
|
-
window.location.href = checkoutIntentUrl();
|
|
21433
|
-
};
|
|
21434
|
-
const next = () => {
|
|
21435
|
-
if (window.parent !== window) {
|
|
21436
|
-
window.parent.postMessage({ type: "setto:next", siteId: config.siteId }, "*");
|
|
21437
|
-
return;
|
|
21438
|
-
}
|
|
21439
|
-
signIn();
|
|
21440
|
-
};
|
|
21441
|
-
const publish = async () => {
|
|
21442
|
-
setBusy(true);
|
|
21443
|
-
setError(null);
|
|
21444
|
-
try {
|
|
21445
|
-
const token = (await supabase.auth.getSession()).data.session?.access_token;
|
|
21446
|
-
if (!token) {
|
|
21447
|
-
signIn();
|
|
21448
|
-
return;
|
|
21449
|
-
}
|
|
21450
|
-
const data = await api.request(
|
|
21451
|
-
`/onboarding/sites/${config.siteId}/checkout`,
|
|
21452
|
-
{
|
|
21453
|
-
method: "POST",
|
|
21454
|
-
// Back to the page they were looking at, not to a chat window.
|
|
21455
|
-
body: { returnTo: window.location.origin + window.location.pathname }
|
|
21456
|
-
},
|
|
21457
|
-
"checkout"
|
|
21458
|
-
);
|
|
21459
|
-
if (!data.url) throw new Error("checkout_failed");
|
|
21460
|
-
window.location.href = data.url;
|
|
21461
|
-
} catch (err) {
|
|
21462
|
-
const detail = err instanceof Error ? err.message : "";
|
|
21463
|
-
setError(
|
|
21464
|
-
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."
|
|
21465
|
-
);
|
|
21466
|
-
setBusy(false);
|
|
21467
|
-
}
|
|
21468
|
-
};
|
|
21469
|
-
const resumed = useRef(false);
|
|
21470
|
-
useEffect(() => {
|
|
21471
|
-
if (resumed.current || !onPreview || !session || !currentSite) return;
|
|
21472
|
-
if (!wantsCheckout()) return;
|
|
21473
|
-
if (paid || currentSite.status === "live") return;
|
|
21474
|
-
resumed.current = true;
|
|
21475
|
-
window.history.replaceState(null, "", withoutCheckoutIntent());
|
|
21476
|
-
void publish();
|
|
21477
|
-
}, [onPreview, session, currentSite, paid]);
|
|
21478
|
-
const state = previewBarState({
|
|
21479
|
-
onPreview,
|
|
21480
|
-
authLoading,
|
|
21481
|
-
hasSession: !!session,
|
|
21482
|
-
sitesLoaded: sites !== null,
|
|
21483
|
-
hasCurrentSite: !!currentSite,
|
|
21484
|
-
currentSiteIsLive: currentSite?.status === "live",
|
|
21485
|
-
soldPublic,
|
|
21486
|
-
paidMarker: paid
|
|
21487
|
-
});
|
|
21488
|
-
if (state === "hidden") return null;
|
|
21489
|
-
const owner = state === "owner";
|
|
21490
|
-
const name = currentSite?.display_name?.trim() || config.displayName?.trim() || config.siteId;
|
|
21491
|
-
return createPortal(
|
|
21492
|
-
/*
|
|
21493
|
-
* Lifted above the editor's floating button when edit mode is on, which
|
|
21494
|
-
* sits in the same corner band. Sideways would collide again the moment
|
|
21495
|
-
* its toolbar opens, since that expands rightward.
|
|
21496
|
-
*/
|
|
21497
|
-
/* @__PURE__ */ jsx(
|
|
21498
|
-
"div",
|
|
21499
|
-
{
|
|
21500
|
-
style: editMode ? { ...barStyle, bottom: "calc(max(16px, env(safe-area-inset-bottom, 0px)) + 68px)" } : barStyle,
|
|
21501
|
-
"data-setto-preview-bar": "",
|
|
21502
|
-
children: owner ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
21503
|
-
/* @__PURE__ */ jsx("span", { style: dotStyle, "aria-hidden": true }),
|
|
21504
|
-
/* @__PURE__ */ jsxs("span", { style: labelStyle, children: [
|
|
21505
|
-
"Du forhåndsviser nå ",
|
|
21506
|
-
/* @__PURE__ */ jsx("strong", { style: { fontWeight: 600 }, children: name })
|
|
21507
|
-
] }),
|
|
21508
|
-
error && /* @__PURE__ */ jsx("span", { style: errorStyle$2, children: error }),
|
|
21509
|
-
/* @__PURE__ */ jsx("button", { type: "button", style: primaryStyle, onClick: publish, disabled: busy, children: busy ? "Åpner betaling …" : "Publiser" })
|
|
21510
|
-
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
21511
|
-
/* @__PURE__ */ jsx("span", { style: dotStyle, "aria-hidden": true }),
|
|
21512
|
-
/* @__PURE__ */ jsx("span", { style: labelStyle, children: "Forhåndsvisning" }),
|
|
21513
|
-
/* @__PURE__ */ jsx("button", { type: "button", style: primaryStyle, onClick: next, children: "Neste" })
|
|
21514
|
-
] })
|
|
21515
|
-
}
|
|
21516
|
-
),
|
|
21517
|
-
document.body
|
|
21518
|
-
);
|
|
21519
|
-
}
|
|
21520
|
-
const barStyle = {
|
|
21521
|
-
position: "fixed",
|
|
21522
|
-
left: "50%",
|
|
21523
|
-
bottom: "max(16px, env(safe-area-inset-bottom, 0px))",
|
|
21524
|
-
transform: "translateX(-50%)",
|
|
21525
|
-
zIndex: 2147483e3,
|
|
21526
|
-
display: "flex",
|
|
21527
|
-
alignItems: "center",
|
|
21528
|
-
gap: 14,
|
|
21529
|
-
maxWidth: "calc(100vw - 24px)",
|
|
21530
|
-
padding: "10px 10px 10px 18px",
|
|
21531
|
-
borderRadius: 999,
|
|
21532
|
-
background: "#211711",
|
|
21533
|
-
color: "#fffdf8",
|
|
21534
|
-
fontFamily: 'system-ui, -apple-system, "Segoe UI", sans-serif',
|
|
21535
|
-
fontSize: 14,
|
|
21536
|
-
lineHeight: 1.3,
|
|
21537
|
-
boxShadow: "0 18px 40px -18px rgba(0, 0, 0, 0.65)",
|
|
21538
|
-
/*
|
|
21539
|
-
* A frosted rim, so the pill reads as an object on any page.
|
|
21540
|
-
*
|
|
21541
|
-
* The bar sits on sites Setto did not choose the colours for: on a dark hero
|
|
21542
|
-
* a dark pill disappears, and on a pale section its shadow is all that holds
|
|
21543
|
-
* it. A translucent white border with `padding-box` clipping lets the page
|
|
21544
|
-
* show through the rim itself, which separates the pill from either ground
|
|
21545
|
-
* without picking a side.
|
|
21546
|
-
*/
|
|
21547
|
-
border: "5px solid rgba(255, 255, 255, 0.22)",
|
|
21548
|
-
backgroundClip: "padding-box",
|
|
21549
|
-
backdropFilter: "blur(10px)",
|
|
21550
|
-
WebkitBackdropFilter: "blur(10px)"
|
|
21551
|
-
};
|
|
21552
|
-
const dotStyle = {
|
|
21553
|
-
width: 7,
|
|
21554
|
-
height: 7,
|
|
21555
|
-
borderRadius: "50%",
|
|
21556
|
-
background: "#c4502a",
|
|
21557
|
-
flex: "none"
|
|
21558
|
-
};
|
|
21559
|
-
const labelStyle = {
|
|
21560
|
-
whiteSpace: "nowrap",
|
|
21561
|
-
overflow: "hidden",
|
|
21562
|
-
textOverflow: "ellipsis"
|
|
21563
|
-
};
|
|
21564
|
-
const primaryStyle = {
|
|
21565
|
-
flex: "none",
|
|
21566
|
-
padding: "9px 18px",
|
|
21567
|
-
border: 0,
|
|
21568
|
-
borderRadius: 999,
|
|
21569
|
-
background: "#c4502a",
|
|
21570
|
-
color: "#fffdf8",
|
|
21571
|
-
font: "inherit",
|
|
21572
|
-
fontWeight: 600,
|
|
21573
|
-
cursor: "pointer"
|
|
21574
|
-
};
|
|
21575
|
-
const errorStyle$2 = {
|
|
21576
|
-
color: "#ffb4a2",
|
|
21577
|
-
fontSize: 13,
|
|
21578
|
-
/*
|
|
21579
|
-
* Shrinkable, unlike the label beside it.
|
|
21580
|
-
*
|
|
21581
|
-
* `nowrap` on a flex child whose `min-width` is `auto` cannot shrink below
|
|
21582
|
-
* its own text, so on a 320px phone the error shouldered «Publiser» 19px
|
|
21583
|
-
* past the right edge of the screen and the bar clipped it — the button
|
|
21584
|
-
* that takes the money, unreachable, exactly when something had gone wrong.
|
|
21585
|
-
*/
|
|
21586
|
-
minWidth: 0,
|
|
21587
|
-
overflow: "hidden",
|
|
21588
|
-
textOverflow: "ellipsis",
|
|
21589
|
-
whiteSpace: "nowrap"
|
|
21590
|
-
};
|
|
21591
21462
|
const FAB_SIZE = 52;
|
|
21592
21463
|
const FAB_INSET = 16;
|
|
21593
21464
|
const STYLE_ID$3 = "setto-edit-layout";
|
|
@@ -22708,7 +22579,7 @@ function DomainView({ siteId }) {
|
|
|
22708
22579
|
),
|
|
22709
22580
|
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => void attach(), disabled: busy || !host.trim(), style: primary, children: busy ? "Legger til …" : "Legg til domene" })
|
|
22710
22581
|
] }),
|
|
22711
|
-
error && /* @__PURE__ */ jsx("p", { style: errorStyle$
|
|
22582
|
+
error && /* @__PURE__ */ jsx("p", { style: errorStyle$2, children: error })
|
|
22712
22583
|
] });
|
|
22713
22584
|
}
|
|
22714
22585
|
return /* @__PURE__ */ jsxs("div", { style: wrap, children: [
|
|
@@ -22728,7 +22599,7 @@ function DomainView({ siteId }) {
|
|
|
22728
22599
|
/* @__PURE__ */ jsx("p", { style: { ...muted, margin: "0.35rem 0 0" }, children: rec.why })
|
|
22729
22600
|
] }, `${rec.type}-${rec.name}`)) })
|
|
22730
22601
|
] }),
|
|
22731
|
-
error && /* @__PURE__ */ jsx("p", { style: errorStyle$
|
|
22602
|
+
error && /* @__PURE__ */ jsx("p", { style: errorStyle$2, children: error }),
|
|
22732
22603
|
/* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: "0.6rem", flexWrap: "wrap" }, children: [
|
|
22733
22604
|
!state.verified && /* @__PURE__ */ jsx("button", { type: "button", onClick: () => void recheck(), disabled: busy, style: primary, children: busy ? "Sjekker …" : "Jeg har lagt inn radene" }),
|
|
22734
22605
|
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => void detach(), disabled: busy, style: quiet, children: "Fjern domenet" })
|
|
@@ -22776,7 +22647,7 @@ const quiet = {
|
|
|
22776
22647
|
font: "inherit",
|
|
22777
22648
|
cursor: "pointer"
|
|
22778
22649
|
};
|
|
22779
|
-
const errorStyle$
|
|
22650
|
+
const errorStyle$2 = { margin: 0, color: "#b42318" };
|
|
22780
22651
|
const liveBadge = {
|
|
22781
22652
|
fontSize: "0.75rem",
|
|
22782
22653
|
padding: "0.15rem 0.5rem",
|
|
@@ -24930,7 +24801,7 @@ function EditToolbar() {
|
|
|
24930
24801
|
}
|
|
24931
24802
|
),
|
|
24932
24803
|
expanded ? /* @__PURE__ */ jsxs("div", { style: contentStyle, children: [
|
|
24933
|
-
error ? /* @__PURE__ */ jsx("span", { style: errorStyle, children: error }) : null,
|
|
24804
|
+
error ? /* @__PURE__ */ jsx("span", { style: errorStyle$1, children: error }) : null,
|
|
24934
24805
|
hasDrafts && !publishing && activeDeployment === null ? /* @__PURE__ */ jsx("button", { type: "button", onClick: publish, style: publishBtnStyle, children: "Publiser" }) : null,
|
|
24935
24806
|
showPublishedNotice ? /* @__PURE__ */ jsxs(
|
|
24936
24807
|
"button",
|
|
@@ -25051,7 +24922,7 @@ const contentStyle = {
|
|
|
25051
24922
|
paddingLeft: 4,
|
|
25052
24923
|
whiteSpace: "nowrap"
|
|
25053
24924
|
};
|
|
25054
|
-
const errorStyle = {
|
|
24925
|
+
const errorStyle$1 = {
|
|
25055
24926
|
color: "#ffb4a8",
|
|
25056
24927
|
fontSize: 12,
|
|
25057
24928
|
maxWidth: 240,
|
|
@@ -25305,7 +25176,7 @@ function SettoProvider({ config, children }) {
|
|
|
25305
25176
|
const editShellActive = editMode && !!value.store;
|
|
25306
25177
|
return /* @__PURE__ */ jsx(SettoContext.Provider, { value, children: /* @__PURE__ */ jsxs(SectionEditProvider, { children: [
|
|
25307
25178
|
/* @__PURE__ */ jsx(EditModeShell, { active: editShellActive, themeStore: value.themeStore, children }),
|
|
25308
|
-
!onAdminRoute && /* @__PURE__ */ jsx(
|
|
25179
|
+
!onAdminRoute && /* @__PURE__ */ jsx(SettoChrome, {})
|
|
25309
25180
|
] }) });
|
|
25310
25181
|
}
|
|
25311
25182
|
function useSetto() {
|
|
@@ -25315,6 +25186,202 @@ function useSetto() {
|
|
|
25315
25186
|
}
|
|
25316
25187
|
return ctx;
|
|
25317
25188
|
}
|
|
25189
|
+
const PREVIEW_HOST = /(^|\.)site\.setto\.no$/i;
|
|
25190
|
+
function isPreviewHost(hostname) {
|
|
25191
|
+
return PREVIEW_HOST.test(hostname);
|
|
25192
|
+
}
|
|
25193
|
+
function previewBarState(input2) {
|
|
25194
|
+
if (!input2.onPreview || input2.authLoading) return "hidden";
|
|
25195
|
+
if (input2.hasSession && !input2.sitesLoaded) return "hidden";
|
|
25196
|
+
if (!input2.hasSession && input2.soldPublic === null) return "hidden";
|
|
25197
|
+
const bought = input2.paidMarker || input2.currentSiteIsLive || input2.soldPublic === true;
|
|
25198
|
+
if (bought) return "hidden";
|
|
25199
|
+
return input2.hasSession && input2.hasCurrentSite ? "owner" : "visitor";
|
|
25200
|
+
}
|
|
25201
|
+
function SettoPreviewBar() {
|
|
25202
|
+
const { supabase, api, config, session, authLoading, currentSite, sites, editMode } = useSetto();
|
|
25203
|
+
const [onPreview, setOnPreview] = useState(false);
|
|
25204
|
+
const [busy, setBusy] = useState(false);
|
|
25205
|
+
const [error, setError] = useState(null);
|
|
25206
|
+
const [paid, setPaid] = useState(false);
|
|
25207
|
+
const [soldPublic, setSoldPublic] = useState(null);
|
|
25208
|
+
useEffect(() => {
|
|
25209
|
+
setOnPreview(isPreviewHost(window.location.hostname));
|
|
25210
|
+
setPaid(new URLSearchParams(window.location.search).get("betaling") === "ok");
|
|
25211
|
+
}, []);
|
|
25212
|
+
useEffect(() => {
|
|
25213
|
+
if (!onPreview || session) return;
|
|
25214
|
+
let cancelled = false;
|
|
25215
|
+
fetch(`${config.apiUrl}/onboarding/sites/${config.siteId}/sold`, {
|
|
25216
|
+
headers: { Accept: "application/json" }
|
|
25217
|
+
}).then((r) => r.ok ? r.json() : null).then((d) => {
|
|
25218
|
+
if (!cancelled && d) setSoldPublic(d.sold === true);
|
|
25219
|
+
}).catch(() => {
|
|
25220
|
+
});
|
|
25221
|
+
return () => {
|
|
25222
|
+
cancelled = true;
|
|
25223
|
+
};
|
|
25224
|
+
}, [onPreview, session, config.apiUrl, config.siteId]);
|
|
25225
|
+
const signIn = () => {
|
|
25226
|
+
window.location.href = checkoutIntentUrl();
|
|
25227
|
+
};
|
|
25228
|
+
const next = () => {
|
|
25229
|
+
if (window.parent !== window) {
|
|
25230
|
+
window.parent.postMessage({ type: "setto:next", siteId: config.siteId }, "*");
|
|
25231
|
+
return;
|
|
25232
|
+
}
|
|
25233
|
+
signIn();
|
|
25234
|
+
};
|
|
25235
|
+
const publish = async () => {
|
|
25236
|
+
setBusy(true);
|
|
25237
|
+
setError(null);
|
|
25238
|
+
try {
|
|
25239
|
+
const token = (await supabase.auth.getSession()).data.session?.access_token;
|
|
25240
|
+
if (!token) {
|
|
25241
|
+
signIn();
|
|
25242
|
+
return;
|
|
25243
|
+
}
|
|
25244
|
+
const data = await api.request(
|
|
25245
|
+
`/onboarding/sites/${config.siteId}/checkout`,
|
|
25246
|
+
{
|
|
25247
|
+
method: "POST",
|
|
25248
|
+
// Back to the page they were looking at, not to a chat window.
|
|
25249
|
+
body: { returnTo: window.location.origin + window.location.pathname }
|
|
25250
|
+
},
|
|
25251
|
+
"checkout"
|
|
25252
|
+
);
|
|
25253
|
+
if (!data.url) throw new Error("checkout_failed");
|
|
25254
|
+
window.location.href = data.url;
|
|
25255
|
+
} catch (err) {
|
|
25256
|
+
const detail = err instanceof Error ? err.message : "";
|
|
25257
|
+
setError(
|
|
25258
|
+
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."
|
|
25259
|
+
);
|
|
25260
|
+
setBusy(false);
|
|
25261
|
+
}
|
|
25262
|
+
};
|
|
25263
|
+
const resumed = useRef(false);
|
|
25264
|
+
useEffect(() => {
|
|
25265
|
+
if (resumed.current || !onPreview || !session || !currentSite) return;
|
|
25266
|
+
if (!wantsCheckout()) return;
|
|
25267
|
+
if (paid || currentSite.status === "live") return;
|
|
25268
|
+
resumed.current = true;
|
|
25269
|
+
window.history.replaceState(null, "", withoutCheckoutIntent());
|
|
25270
|
+
void publish();
|
|
25271
|
+
}, [onPreview, session, currentSite, paid]);
|
|
25272
|
+
const state = previewBarState({
|
|
25273
|
+
onPreview,
|
|
25274
|
+
authLoading,
|
|
25275
|
+
hasSession: !!session,
|
|
25276
|
+
sitesLoaded: sites !== null,
|
|
25277
|
+
hasCurrentSite: !!currentSite,
|
|
25278
|
+
currentSiteIsLive: currentSite?.status === "live",
|
|
25279
|
+
soldPublic,
|
|
25280
|
+
paidMarker: paid
|
|
25281
|
+
});
|
|
25282
|
+
if (state === "hidden") return null;
|
|
25283
|
+
const owner = state === "owner";
|
|
25284
|
+
const name = currentSite?.display_name?.trim() || config.displayName?.trim() || config.siteId;
|
|
25285
|
+
return createPortal(
|
|
25286
|
+
/*
|
|
25287
|
+
* Lifted above the editor's floating button when edit mode is on, which
|
|
25288
|
+
* sits in the same corner band. Sideways would collide again the moment
|
|
25289
|
+
* its toolbar opens, since that expands rightward.
|
|
25290
|
+
*/
|
|
25291
|
+
/* @__PURE__ */ jsx(
|
|
25292
|
+
"div",
|
|
25293
|
+
{
|
|
25294
|
+
style: editMode ? { ...barStyle, bottom: "calc(max(16px, env(safe-area-inset-bottom, 0px)) + 68px)" } : barStyle,
|
|
25295
|
+
"data-setto-preview-bar": "",
|
|
25296
|
+
children: owner ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
25297
|
+
/* @__PURE__ */ jsx("span", { style: dotStyle, "aria-hidden": true }),
|
|
25298
|
+
/* @__PURE__ */ jsxs("span", { style: labelStyle, children: [
|
|
25299
|
+
"Du forhåndsviser nå ",
|
|
25300
|
+
/* @__PURE__ */ jsx("strong", { style: { fontWeight: 600 }, children: name })
|
|
25301
|
+
] }),
|
|
25302
|
+
error && /* @__PURE__ */ jsx("span", { style: errorStyle, children: error }),
|
|
25303
|
+
/* @__PURE__ */ jsx("button", { type: "button", style: primaryStyle, onClick: publish, disabled: busy, children: busy ? "Åpner betaling …" : "Publiser" })
|
|
25304
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
25305
|
+
/* @__PURE__ */ jsx("span", { style: dotStyle, "aria-hidden": true }),
|
|
25306
|
+
/* @__PURE__ */ jsx("span", { style: labelStyle, children: "Forhåndsvisning" }),
|
|
25307
|
+
/* @__PURE__ */ jsx("button", { type: "button", style: primaryStyle, onClick: next, children: "Neste" })
|
|
25308
|
+
] })
|
|
25309
|
+
}
|
|
25310
|
+
),
|
|
25311
|
+
document.body
|
|
25312
|
+
);
|
|
25313
|
+
}
|
|
25314
|
+
const barStyle = {
|
|
25315
|
+
position: "fixed",
|
|
25316
|
+
left: "50%",
|
|
25317
|
+
bottom: "max(16px, env(safe-area-inset-bottom, 0px))",
|
|
25318
|
+
transform: "translateX(-50%)",
|
|
25319
|
+
zIndex: 2147483e3,
|
|
25320
|
+
display: "flex",
|
|
25321
|
+
alignItems: "center",
|
|
25322
|
+
gap: 14,
|
|
25323
|
+
maxWidth: "calc(100vw - 24px)",
|
|
25324
|
+
padding: "10px 10px 10px 18px",
|
|
25325
|
+
borderRadius: 999,
|
|
25326
|
+
background: "#211711",
|
|
25327
|
+
color: "#fffdf8",
|
|
25328
|
+
fontFamily: 'system-ui, -apple-system, "Segoe UI", sans-serif',
|
|
25329
|
+
fontSize: 14,
|
|
25330
|
+
lineHeight: 1.3,
|
|
25331
|
+
boxShadow: "0 18px 40px -18px rgba(0, 0, 0, 0.65)",
|
|
25332
|
+
/*
|
|
25333
|
+
* A frosted rim, so the pill reads as an object on any page.
|
|
25334
|
+
*
|
|
25335
|
+
* The bar sits on sites Setto did not choose the colours for: on a dark hero
|
|
25336
|
+
* a dark pill disappears, and on a pale section its shadow is all that holds
|
|
25337
|
+
* it. A translucent white border with `padding-box` clipping lets the page
|
|
25338
|
+
* show through the rim itself, which separates the pill from either ground
|
|
25339
|
+
* without picking a side.
|
|
25340
|
+
*/
|
|
25341
|
+
border: "5px solid rgba(255, 255, 255, 0.22)",
|
|
25342
|
+
backgroundClip: "padding-box",
|
|
25343
|
+
backdropFilter: "blur(10px)",
|
|
25344
|
+
WebkitBackdropFilter: "blur(10px)"
|
|
25345
|
+
};
|
|
25346
|
+
const dotStyle = {
|
|
25347
|
+
width: 7,
|
|
25348
|
+
height: 7,
|
|
25349
|
+
borderRadius: "50%",
|
|
25350
|
+
background: "#c4502a",
|
|
25351
|
+
flex: "none"
|
|
25352
|
+
};
|
|
25353
|
+
const labelStyle = {
|
|
25354
|
+
whiteSpace: "nowrap",
|
|
25355
|
+
overflow: "hidden",
|
|
25356
|
+
textOverflow: "ellipsis"
|
|
25357
|
+
};
|
|
25358
|
+
const primaryStyle = {
|
|
25359
|
+
flex: "none",
|
|
25360
|
+
padding: "9px 18px",
|
|
25361
|
+
border: 0,
|
|
25362
|
+
borderRadius: 999,
|
|
25363
|
+
background: "#c4502a",
|
|
25364
|
+
color: "#fffdf8",
|
|
25365
|
+
font: "inherit",
|
|
25366
|
+
fontWeight: 600,
|
|
25367
|
+
cursor: "pointer"
|
|
25368
|
+
};
|
|
25369
|
+
const errorStyle = {
|
|
25370
|
+
color: "#ffb4a2",
|
|
25371
|
+
fontSize: 13,
|
|
25372
|
+
/*
|
|
25373
|
+
* Shrinkable, unlike the label beside it.
|
|
25374
|
+
*
|
|
25375
|
+
* `nowrap` on a flex child whose `min-width` is `auto` cannot shrink below
|
|
25376
|
+
* its own text, so on a 320px phone the error shouldered «Publiser» 19px
|
|
25377
|
+
* past the right edge of the screen and the bar clipped it — the button
|
|
25378
|
+
* that takes the money, unreachable, exactly when something had gone wrong.
|
|
25379
|
+
*/
|
|
25380
|
+
minWidth: 0,
|
|
25381
|
+
overflow: "hidden",
|
|
25382
|
+
textOverflow: "ellipsis",
|
|
25383
|
+
whiteSpace: "nowrap"
|
|
25384
|
+
};
|
|
25318
25385
|
function isTextEditClick(target) {
|
|
25319
25386
|
if (target.closest("[data-setto-key]")) return true;
|
|
25320
25387
|
if (target.closest("[data-setto-ui]")) return true;
|
|
@@ -27076,6 +27143,7 @@ export {
|
|
|
27076
27143
|
SettoAnimation,
|
|
27077
27144
|
SettoBlock,
|
|
27078
27145
|
SettoBlockTree,
|
|
27146
|
+
SettoChrome,
|
|
27079
27147
|
SettoForm,
|
|
27080
27148
|
SettoIcon,
|
|
27081
27149
|
SettoImage,
|
|
@@ -27092,6 +27160,7 @@ export {
|
|
|
27092
27160
|
isPreviewHost,
|
|
27093
27161
|
nodeChildren,
|
|
27094
27162
|
parseBlockTree,
|
|
27163
|
+
previewBarState,
|
|
27095
27164
|
readLocaleFields,
|
|
27096
27165
|
settoSubmissionUrl,
|
|
27097
27166
|
useGuestEdit,
|