@sevenfold/setto-client 0.9.0 → 0.10.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/chrome.d.ts +51 -0
- package/dist/index.d.ts +3 -1
- package/dist/setto-client.js +288 -202
- package/dist/setto-client.js.map +1 -1
- package/package.json +1 -1
package/dist/chrome.d.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
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
|
+
/**
|
|
10
|
+
* Hand back a component and the host renders it.
|
|
11
|
+
*
|
|
12
|
+
* Inverted deliberately. The first cut had the chrome create its own React
|
|
13
|
+
* root, which cannot work: `createRoot` lives in `react-dom/client`, not
|
|
14
|
+
* `react-dom`, and React 19 removed `render` altogether — so the bundle
|
|
15
|
+
* loaded, found neither, and mounted nothing at all.
|
|
16
|
+
*
|
|
17
|
+
* Rendering it from here is better than passing `createRoot` across would
|
|
18
|
+
* have been. The chrome ends up inside the host's own tree, so its hooks are
|
|
19
|
+
* the host's hooks with no second root to keep in step, and the error
|
|
20
|
+
* boundary that keeps a mistake off the customer's page lives on this side
|
|
21
|
+
* where it cannot be forgotten.
|
|
22
|
+
*/
|
|
23
|
+
mount: (Component: React.ComponentType) => void;
|
|
24
|
+
config: {
|
|
25
|
+
siteId: string;
|
|
26
|
+
apiUrl: string;
|
|
27
|
+
displayName?: string;
|
|
28
|
+
};
|
|
29
|
+
/** Read the current state. Live: call it, never cache what it returned. */
|
|
30
|
+
getState: () => {
|
|
31
|
+
hasSession: boolean;
|
|
32
|
+
accessToken: string | null;
|
|
33
|
+
currentSite: {
|
|
34
|
+
id: string;
|
|
35
|
+
display_name?: string;
|
|
36
|
+
status?: string;
|
|
37
|
+
} | null;
|
|
38
|
+
sitesLoaded: boolean;
|
|
39
|
+
editMode: boolean;
|
|
40
|
+
authLoading: boolean;
|
|
41
|
+
};
|
|
42
|
+
/** Told whenever that state changes. Returns an unsubscribe. */
|
|
43
|
+
subscribe: (listener: () => void) => () => void;
|
|
44
|
+
}
|
|
45
|
+
declare global {
|
|
46
|
+
interface Window {
|
|
47
|
+
__setto__?: SettoChromeHandshake;
|
|
48
|
+
__settoChromeLoaded__?: boolean;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
export declare function SettoChrome(): import("react/jsx-runtime").JSX.Element | 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,88 @@ 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
|
+
class ChromeBoundary extends React.Component {
|
|
21368
|
+
state = { dead: false };
|
|
21369
|
+
static getDerivedStateFromError() {
|
|
21370
|
+
return { dead: true };
|
|
21371
|
+
}
|
|
21372
|
+
componentDidCatch(error) {
|
|
21373
|
+
console.warn("[setto] chrome failed, continuing without it", error);
|
|
21374
|
+
}
|
|
21375
|
+
render() {
|
|
21376
|
+
return this.state.dead ? null : this.props.children;
|
|
21377
|
+
}
|
|
21378
|
+
}
|
|
21379
|
+
function SettoChrome() {
|
|
21380
|
+
const setto = useSetto();
|
|
21381
|
+
const [Chrome, setChrome] = useState(null);
|
|
21382
|
+
const listeners = useRef(/* @__PURE__ */ new Set());
|
|
21383
|
+
const stateRef = useRef(() => ({
|
|
21384
|
+
hasSession: false,
|
|
21385
|
+
accessToken: null,
|
|
21386
|
+
currentSite: null,
|
|
21387
|
+
sitesLoaded: false,
|
|
21388
|
+
editMode: false,
|
|
21389
|
+
authLoading: true
|
|
21390
|
+
}));
|
|
21391
|
+
stateRef.current = () => ({
|
|
21392
|
+
hasSession: !!setto.session,
|
|
21393
|
+
accessToken: setto.session?.access_token ?? null,
|
|
21394
|
+
currentSite: setto.currentSite ? {
|
|
21395
|
+
id: setto.currentSite.id,
|
|
21396
|
+
display_name: setto.currentSite.display_name,
|
|
21397
|
+
status: setto.currentSite.status
|
|
21398
|
+
} : null,
|
|
21399
|
+
sitesLoaded: setto.sites !== null,
|
|
21400
|
+
editMode: setto.editMode,
|
|
21401
|
+
authLoading: setto.authLoading
|
|
21402
|
+
});
|
|
21403
|
+
useEffect(() => {
|
|
21404
|
+
for (const listener of listeners.current) {
|
|
21405
|
+
try {
|
|
21406
|
+
listener();
|
|
21407
|
+
} catch {
|
|
21408
|
+
}
|
|
21409
|
+
}
|
|
21410
|
+
});
|
|
21411
|
+
useEffect(() => {
|
|
21412
|
+
if (typeof window === "undefined" || typeof document === "undefined") return;
|
|
21413
|
+
if (window.__settoChromeLoaded__) return;
|
|
21414
|
+
try {
|
|
21415
|
+
window.__setto__ = {
|
|
21416
|
+
contract: CHROME_CONTRACT,
|
|
21417
|
+
react: React,
|
|
21418
|
+
reactDom: ReactDOM,
|
|
21419
|
+
config: {
|
|
21420
|
+
siteId: setto.config.siteId,
|
|
21421
|
+
apiUrl: setto.config.apiUrl,
|
|
21422
|
+
displayName: setto.config.displayName
|
|
21423
|
+
},
|
|
21424
|
+
getState: () => stateRef.current(),
|
|
21425
|
+
subscribe: (listener) => {
|
|
21426
|
+
listeners.current.add(listener);
|
|
21427
|
+
return () => listeners.current.delete(listener);
|
|
21428
|
+
},
|
|
21429
|
+
// `setChrome(() => C)`: a component is a function, and the updater form
|
|
21430
|
+
// is the only way to put one into state without React calling it.
|
|
21431
|
+
mount: (Component) => setChrome(() => Component)
|
|
21432
|
+
};
|
|
21433
|
+
const script = document.createElement("script");
|
|
21434
|
+
script.src = chromeUrl(setto.config.apiUrl);
|
|
21435
|
+
script.async = true;
|
|
21436
|
+
script.onerror = () => void 0;
|
|
21437
|
+
document.head.appendChild(script);
|
|
21438
|
+
window.__settoChromeLoaded__ = true;
|
|
21439
|
+
} catch {
|
|
21440
|
+
}
|
|
21441
|
+
}, []);
|
|
21442
|
+
if (!Chrome) return null;
|
|
21443
|
+
return /* @__PURE__ */ jsx(ChromeBoundary, { children: /* @__PURE__ */ jsx(Chrome, {}) });
|
|
21444
|
+
}
|
|
21361
21445
|
const SETTO_BASE = "/setto";
|
|
21362
21446
|
function siteHomeUrl(pathname = "/") {
|
|
21363
21447
|
const u = new URL(window.location.href);
|
|
@@ -21392,202 +21476,6 @@ function withoutCheckoutIntent() {
|
|
|
21392
21476
|
u.searchParams.delete(CHECKOUT_INTENT);
|
|
21393
21477
|
return u.pathname + (u.search || "") + u.hash;
|
|
21394
21478
|
}
|
|
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
21479
|
const FAB_SIZE = 52;
|
|
21592
21480
|
const FAB_INSET = 16;
|
|
21593
21481
|
const STYLE_ID$3 = "setto-edit-layout";
|
|
@@ -22708,7 +22596,7 @@ function DomainView({ siteId }) {
|
|
|
22708
22596
|
),
|
|
22709
22597
|
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => void attach(), disabled: busy || !host.trim(), style: primary, children: busy ? "Legger til …" : "Legg til domene" })
|
|
22710
22598
|
] }),
|
|
22711
|
-
error && /* @__PURE__ */ jsx("p", { style: errorStyle$
|
|
22599
|
+
error && /* @__PURE__ */ jsx("p", { style: errorStyle$2, children: error })
|
|
22712
22600
|
] });
|
|
22713
22601
|
}
|
|
22714
22602
|
return /* @__PURE__ */ jsxs("div", { style: wrap, children: [
|
|
@@ -22728,7 +22616,7 @@ function DomainView({ siteId }) {
|
|
|
22728
22616
|
/* @__PURE__ */ jsx("p", { style: { ...muted, margin: "0.35rem 0 0" }, children: rec.why })
|
|
22729
22617
|
] }, `${rec.type}-${rec.name}`)) })
|
|
22730
22618
|
] }),
|
|
22731
|
-
error && /* @__PURE__ */ jsx("p", { style: errorStyle$
|
|
22619
|
+
error && /* @__PURE__ */ jsx("p", { style: errorStyle$2, children: error }),
|
|
22732
22620
|
/* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: "0.6rem", flexWrap: "wrap" }, children: [
|
|
22733
22621
|
!state.verified && /* @__PURE__ */ jsx("button", { type: "button", onClick: () => void recheck(), disabled: busy, style: primary, children: busy ? "Sjekker …" : "Jeg har lagt inn radene" }),
|
|
22734
22622
|
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => void detach(), disabled: busy, style: quiet, children: "Fjern domenet" })
|
|
@@ -22776,7 +22664,7 @@ const quiet = {
|
|
|
22776
22664
|
font: "inherit",
|
|
22777
22665
|
cursor: "pointer"
|
|
22778
22666
|
};
|
|
22779
|
-
const errorStyle$
|
|
22667
|
+
const errorStyle$2 = { margin: 0, color: "#b42318" };
|
|
22780
22668
|
const liveBadge = {
|
|
22781
22669
|
fontSize: "0.75rem",
|
|
22782
22670
|
padding: "0.15rem 0.5rem",
|
|
@@ -24930,7 +24818,7 @@ function EditToolbar() {
|
|
|
24930
24818
|
}
|
|
24931
24819
|
),
|
|
24932
24820
|
expanded ? /* @__PURE__ */ jsxs("div", { style: contentStyle, children: [
|
|
24933
|
-
error ? /* @__PURE__ */ jsx("span", { style: errorStyle, children: error }) : null,
|
|
24821
|
+
error ? /* @__PURE__ */ jsx("span", { style: errorStyle$1, children: error }) : null,
|
|
24934
24822
|
hasDrafts && !publishing && activeDeployment === null ? /* @__PURE__ */ jsx("button", { type: "button", onClick: publish, style: publishBtnStyle, children: "Publiser" }) : null,
|
|
24935
24823
|
showPublishedNotice ? /* @__PURE__ */ jsxs(
|
|
24936
24824
|
"button",
|
|
@@ -25051,7 +24939,7 @@ const contentStyle = {
|
|
|
25051
24939
|
paddingLeft: 4,
|
|
25052
24940
|
whiteSpace: "nowrap"
|
|
25053
24941
|
};
|
|
25054
|
-
const errorStyle = {
|
|
24942
|
+
const errorStyle$1 = {
|
|
25055
24943
|
color: "#ffb4a8",
|
|
25056
24944
|
fontSize: 12,
|
|
25057
24945
|
maxWidth: 240,
|
|
@@ -25305,7 +25193,7 @@ function SettoProvider({ config, children }) {
|
|
|
25305
25193
|
const editShellActive = editMode && !!value.store;
|
|
25306
25194
|
return /* @__PURE__ */ jsx(SettoContext.Provider, { value, children: /* @__PURE__ */ jsxs(SectionEditProvider, { children: [
|
|
25307
25195
|
/* @__PURE__ */ jsx(EditModeShell, { active: editShellActive, themeStore: value.themeStore, children }),
|
|
25308
|
-
!onAdminRoute && /* @__PURE__ */ jsx(
|
|
25196
|
+
!onAdminRoute && /* @__PURE__ */ jsx(SettoChrome, {})
|
|
25309
25197
|
] }) });
|
|
25310
25198
|
}
|
|
25311
25199
|
function useSetto() {
|
|
@@ -25315,6 +25203,202 @@ function useSetto() {
|
|
|
25315
25203
|
}
|
|
25316
25204
|
return ctx;
|
|
25317
25205
|
}
|
|
25206
|
+
const PREVIEW_HOST = /(^|\.)site\.setto\.no$/i;
|
|
25207
|
+
function isPreviewHost(hostname) {
|
|
25208
|
+
return PREVIEW_HOST.test(hostname);
|
|
25209
|
+
}
|
|
25210
|
+
function previewBarState(input2) {
|
|
25211
|
+
if (!input2.onPreview || input2.authLoading) return "hidden";
|
|
25212
|
+
if (input2.hasSession && !input2.sitesLoaded) return "hidden";
|
|
25213
|
+
if (!input2.hasSession && input2.soldPublic === null) return "hidden";
|
|
25214
|
+
const bought = input2.paidMarker || input2.currentSiteIsLive || input2.soldPublic === true;
|
|
25215
|
+
if (bought) return "hidden";
|
|
25216
|
+
return input2.hasSession && input2.hasCurrentSite ? "owner" : "visitor";
|
|
25217
|
+
}
|
|
25218
|
+
function SettoPreviewBar() {
|
|
25219
|
+
const { supabase, api, config, session, authLoading, currentSite, sites, editMode } = useSetto();
|
|
25220
|
+
const [onPreview, setOnPreview] = useState(false);
|
|
25221
|
+
const [busy, setBusy] = useState(false);
|
|
25222
|
+
const [error, setError] = useState(null);
|
|
25223
|
+
const [paid, setPaid] = useState(false);
|
|
25224
|
+
const [soldPublic, setSoldPublic] = useState(null);
|
|
25225
|
+
useEffect(() => {
|
|
25226
|
+
setOnPreview(isPreviewHost(window.location.hostname));
|
|
25227
|
+
setPaid(new URLSearchParams(window.location.search).get("betaling") === "ok");
|
|
25228
|
+
}, []);
|
|
25229
|
+
useEffect(() => {
|
|
25230
|
+
if (!onPreview || session) return;
|
|
25231
|
+
let cancelled = false;
|
|
25232
|
+
fetch(`${config.apiUrl}/onboarding/sites/${config.siteId}/sold`, {
|
|
25233
|
+
headers: { Accept: "application/json" }
|
|
25234
|
+
}).then((r) => r.ok ? r.json() : null).then((d) => {
|
|
25235
|
+
if (!cancelled && d) setSoldPublic(d.sold === true);
|
|
25236
|
+
}).catch(() => {
|
|
25237
|
+
});
|
|
25238
|
+
return () => {
|
|
25239
|
+
cancelled = true;
|
|
25240
|
+
};
|
|
25241
|
+
}, [onPreview, session, config.apiUrl, config.siteId]);
|
|
25242
|
+
const signIn = () => {
|
|
25243
|
+
window.location.href = checkoutIntentUrl();
|
|
25244
|
+
};
|
|
25245
|
+
const next = () => {
|
|
25246
|
+
if (window.parent !== window) {
|
|
25247
|
+
window.parent.postMessage({ type: "setto:next", siteId: config.siteId }, "*");
|
|
25248
|
+
return;
|
|
25249
|
+
}
|
|
25250
|
+
signIn();
|
|
25251
|
+
};
|
|
25252
|
+
const publish = async () => {
|
|
25253
|
+
setBusy(true);
|
|
25254
|
+
setError(null);
|
|
25255
|
+
try {
|
|
25256
|
+
const token = (await supabase.auth.getSession()).data.session?.access_token;
|
|
25257
|
+
if (!token) {
|
|
25258
|
+
signIn();
|
|
25259
|
+
return;
|
|
25260
|
+
}
|
|
25261
|
+
const data = await api.request(
|
|
25262
|
+
`/onboarding/sites/${config.siteId}/checkout`,
|
|
25263
|
+
{
|
|
25264
|
+
method: "POST",
|
|
25265
|
+
// Back to the page they were looking at, not to a chat window.
|
|
25266
|
+
body: { returnTo: window.location.origin + window.location.pathname }
|
|
25267
|
+
},
|
|
25268
|
+
"checkout"
|
|
25269
|
+
);
|
|
25270
|
+
if (!data.url) throw new Error("checkout_failed");
|
|
25271
|
+
window.location.href = data.url;
|
|
25272
|
+
} catch (err) {
|
|
25273
|
+
const detail = err instanceof Error ? err.message : "";
|
|
25274
|
+
setError(
|
|
25275
|
+
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."
|
|
25276
|
+
);
|
|
25277
|
+
setBusy(false);
|
|
25278
|
+
}
|
|
25279
|
+
};
|
|
25280
|
+
const resumed = useRef(false);
|
|
25281
|
+
useEffect(() => {
|
|
25282
|
+
if (resumed.current || !onPreview || !session || !currentSite) return;
|
|
25283
|
+
if (!wantsCheckout()) return;
|
|
25284
|
+
if (paid || currentSite.status === "live") return;
|
|
25285
|
+
resumed.current = true;
|
|
25286
|
+
window.history.replaceState(null, "", withoutCheckoutIntent());
|
|
25287
|
+
void publish();
|
|
25288
|
+
}, [onPreview, session, currentSite, paid]);
|
|
25289
|
+
const state = previewBarState({
|
|
25290
|
+
onPreview,
|
|
25291
|
+
authLoading,
|
|
25292
|
+
hasSession: !!session,
|
|
25293
|
+
sitesLoaded: sites !== null,
|
|
25294
|
+
hasCurrentSite: !!currentSite,
|
|
25295
|
+
currentSiteIsLive: currentSite?.status === "live",
|
|
25296
|
+
soldPublic,
|
|
25297
|
+
paidMarker: paid
|
|
25298
|
+
});
|
|
25299
|
+
if (state === "hidden") return null;
|
|
25300
|
+
const owner = state === "owner";
|
|
25301
|
+
const name = currentSite?.display_name?.trim() || config.displayName?.trim() || config.siteId;
|
|
25302
|
+
return createPortal(
|
|
25303
|
+
/*
|
|
25304
|
+
* Lifted above the editor's floating button when edit mode is on, which
|
|
25305
|
+
* sits in the same corner band. Sideways would collide again the moment
|
|
25306
|
+
* its toolbar opens, since that expands rightward.
|
|
25307
|
+
*/
|
|
25308
|
+
/* @__PURE__ */ jsx(
|
|
25309
|
+
"div",
|
|
25310
|
+
{
|
|
25311
|
+
style: editMode ? { ...barStyle, bottom: "calc(max(16px, env(safe-area-inset-bottom, 0px)) + 68px)" } : barStyle,
|
|
25312
|
+
"data-setto-preview-bar": "",
|
|
25313
|
+
children: owner ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
25314
|
+
/* @__PURE__ */ jsx("span", { style: dotStyle, "aria-hidden": true }),
|
|
25315
|
+
/* @__PURE__ */ jsxs("span", { style: labelStyle, children: [
|
|
25316
|
+
"Du forhåndsviser nå ",
|
|
25317
|
+
/* @__PURE__ */ jsx("strong", { style: { fontWeight: 600 }, children: name })
|
|
25318
|
+
] }),
|
|
25319
|
+
error && /* @__PURE__ */ jsx("span", { style: errorStyle, children: error }),
|
|
25320
|
+
/* @__PURE__ */ jsx("button", { type: "button", style: primaryStyle, onClick: publish, disabled: busy, children: busy ? "Åpner betaling …" : "Publiser" })
|
|
25321
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
25322
|
+
/* @__PURE__ */ jsx("span", { style: dotStyle, "aria-hidden": true }),
|
|
25323
|
+
/* @__PURE__ */ jsx("span", { style: labelStyle, children: "Forhåndsvisning" }),
|
|
25324
|
+
/* @__PURE__ */ jsx("button", { type: "button", style: primaryStyle, onClick: next, children: "Neste" })
|
|
25325
|
+
] })
|
|
25326
|
+
}
|
|
25327
|
+
),
|
|
25328
|
+
document.body
|
|
25329
|
+
);
|
|
25330
|
+
}
|
|
25331
|
+
const barStyle = {
|
|
25332
|
+
position: "fixed",
|
|
25333
|
+
left: "50%",
|
|
25334
|
+
bottom: "max(16px, env(safe-area-inset-bottom, 0px))",
|
|
25335
|
+
transform: "translateX(-50%)",
|
|
25336
|
+
zIndex: 2147483e3,
|
|
25337
|
+
display: "flex",
|
|
25338
|
+
alignItems: "center",
|
|
25339
|
+
gap: 14,
|
|
25340
|
+
maxWidth: "calc(100vw - 24px)",
|
|
25341
|
+
padding: "10px 10px 10px 18px",
|
|
25342
|
+
borderRadius: 999,
|
|
25343
|
+
background: "#211711",
|
|
25344
|
+
color: "#fffdf8",
|
|
25345
|
+
fontFamily: 'system-ui, -apple-system, "Segoe UI", sans-serif',
|
|
25346
|
+
fontSize: 14,
|
|
25347
|
+
lineHeight: 1.3,
|
|
25348
|
+
boxShadow: "0 18px 40px -18px rgba(0, 0, 0, 0.65)",
|
|
25349
|
+
/*
|
|
25350
|
+
* A frosted rim, so the pill reads as an object on any page.
|
|
25351
|
+
*
|
|
25352
|
+
* The bar sits on sites Setto did not choose the colours for: on a dark hero
|
|
25353
|
+
* a dark pill disappears, and on a pale section its shadow is all that holds
|
|
25354
|
+
* it. A translucent white border with `padding-box` clipping lets the page
|
|
25355
|
+
* show through the rim itself, which separates the pill from either ground
|
|
25356
|
+
* without picking a side.
|
|
25357
|
+
*/
|
|
25358
|
+
border: "5px solid rgba(255, 255, 255, 0.22)",
|
|
25359
|
+
backgroundClip: "padding-box",
|
|
25360
|
+
backdropFilter: "blur(10px)",
|
|
25361
|
+
WebkitBackdropFilter: "blur(10px)"
|
|
25362
|
+
};
|
|
25363
|
+
const dotStyle = {
|
|
25364
|
+
width: 7,
|
|
25365
|
+
height: 7,
|
|
25366
|
+
borderRadius: "50%",
|
|
25367
|
+
background: "#c4502a",
|
|
25368
|
+
flex: "none"
|
|
25369
|
+
};
|
|
25370
|
+
const labelStyle = {
|
|
25371
|
+
whiteSpace: "nowrap",
|
|
25372
|
+
overflow: "hidden",
|
|
25373
|
+
textOverflow: "ellipsis"
|
|
25374
|
+
};
|
|
25375
|
+
const primaryStyle = {
|
|
25376
|
+
flex: "none",
|
|
25377
|
+
padding: "9px 18px",
|
|
25378
|
+
border: 0,
|
|
25379
|
+
borderRadius: 999,
|
|
25380
|
+
background: "#c4502a",
|
|
25381
|
+
color: "#fffdf8",
|
|
25382
|
+
font: "inherit",
|
|
25383
|
+
fontWeight: 600,
|
|
25384
|
+
cursor: "pointer"
|
|
25385
|
+
};
|
|
25386
|
+
const errorStyle = {
|
|
25387
|
+
color: "#ffb4a2",
|
|
25388
|
+
fontSize: 13,
|
|
25389
|
+
/*
|
|
25390
|
+
* Shrinkable, unlike the label beside it.
|
|
25391
|
+
*
|
|
25392
|
+
* `nowrap` on a flex child whose `min-width` is `auto` cannot shrink below
|
|
25393
|
+
* its own text, so on a 320px phone the error shouldered «Publiser» 19px
|
|
25394
|
+
* past the right edge of the screen and the bar clipped it — the button
|
|
25395
|
+
* that takes the money, unreachable, exactly when something had gone wrong.
|
|
25396
|
+
*/
|
|
25397
|
+
minWidth: 0,
|
|
25398
|
+
overflow: "hidden",
|
|
25399
|
+
textOverflow: "ellipsis",
|
|
25400
|
+
whiteSpace: "nowrap"
|
|
25401
|
+
};
|
|
25318
25402
|
function isTextEditClick(target) {
|
|
25319
25403
|
if (target.closest("[data-setto-key]")) return true;
|
|
25320
25404
|
if (target.closest("[data-setto-ui]")) return true;
|
|
@@ -27076,6 +27160,7 @@ export {
|
|
|
27076
27160
|
SettoAnimation,
|
|
27077
27161
|
SettoBlock,
|
|
27078
27162
|
SettoBlockTree,
|
|
27163
|
+
SettoChrome,
|
|
27079
27164
|
SettoForm,
|
|
27080
27165
|
SettoIcon,
|
|
27081
27166
|
SettoImage,
|
|
@@ -27092,6 +27177,7 @@ export {
|
|
|
27092
27177
|
isPreviewHost,
|
|
27093
27178
|
nodeChildren,
|
|
27094
27179
|
parseBlockTree,
|
|
27180
|
+
previewBarState,
|
|
27095
27181
|
readLocaleFields,
|
|
27096
27182
|
settoSubmissionUrl,
|
|
27097
27183
|
useGuestEdit,
|