@sevenfold/setto-client 0.8.5 → 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.
@@ -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 = {};
@@ -5039,26 +5041,26 @@ var PostgrestClient = class PostgrestClient2 {
5039
5041
  this.schemaName = schema;
5040
5042
  this.urlLengthLimit = urlLengthLimit;
5041
5043
  const originalFetch = fetch$1 !== null && fetch$1 !== void 0 ? fetch$1 : globalThis.fetch;
5042
- if (timeout !== void 0 && timeout > 0) this.fetch = (input, init) => {
5044
+ if (timeout !== void 0 && timeout > 0) this.fetch = (input2, init) => {
5043
5045
  const controller = new AbortController();
5044
5046
  const timeoutId = setTimeout(() => controller.abort(), timeout);
5045
5047
  const existingSignal = init === null || init === void 0 ? void 0 : init.signal;
5046
5048
  if (existingSignal) {
5047
5049
  if (existingSignal.aborted) {
5048
5050
  clearTimeout(timeoutId);
5049
- return originalFetch(input, init);
5051
+ return originalFetch(input2, init);
5050
5052
  }
5051
5053
  const abortHandler = () => {
5052
5054
  clearTimeout(timeoutId);
5053
5055
  controller.abort();
5054
5056
  };
5055
5057
  existingSignal.addEventListener("abort", abortHandler, { once: true });
5056
- return originalFetch(input, _objectSpread2$2(_objectSpread2$2({}, init), {}, { signal: controller.signal })).finally(() => {
5058
+ return originalFetch(input2, _objectSpread2$2(_objectSpread2$2({}, init), {}, { signal: controller.signal })).finally(() => {
5057
5059
  clearTimeout(timeoutId);
5058
5060
  existingSignal.removeEventListener("abort", abortHandler);
5059
5061
  });
5060
5062
  }
5061
- return originalFetch(input, _objectSpread2$2(_objectSpread2$2({}, init), {}, { signal: controller.signal })).finally(() => clearTimeout(timeoutId));
5063
+ return originalFetch(input2, _objectSpread2$2(_objectSpread2$2({}, init), {}, { signal: controller.signal })).finally(() => clearTimeout(timeoutId));
5062
5064
  };
5063
5065
  else this.fetch = originalFetch;
5064
5066
  this.retry = retry;
@@ -20182,25 +20184,25 @@ const fetchWithAuth = (supabaseKey, supabaseUrl, getAccessToken, customFetch, tr
20182
20184
  const traceEnabled = (tracePropagationOptions === null || tracePropagationOptions === void 0 ? void 0 : tracePropagationOptions.enabled) === true;
20183
20185
  const respectSampling = (tracePropagationOptions === null || tracePropagationOptions === void 0 ? void 0 : tracePropagationOptions.respectSamplingDecision) !== false;
20184
20186
  const traceTargets = traceEnabled ? getDefaultPropagationTargets(supabaseUrl) : null;
20185
- return async (input, init) => {
20187
+ return async (input2, init) => {
20186
20188
  var _await$getAccessToken;
20187
20189
  const accessToken = (_await$getAccessToken = await getAccessToken()) !== null && _await$getAccessToken !== void 0 ? _await$getAccessToken : supabaseKey;
20188
20190
  let headers = new HeadersConstructor(init === null || init === void 0 ? void 0 : init.headers);
20189
20191
  if (!headers.has("apikey")) headers.set("apikey", supabaseKey);
20190
20192
  if (!headers.has("Authorization")) headers.set("Authorization", `Bearer ${accessToken}`);
20191
20193
  if (traceTargets) {
20192
- const traceHeaders = await getTraceHeaders(input, traceTargets, respectSampling);
20194
+ const traceHeaders = await getTraceHeaders(input2, traceTargets, respectSampling);
20193
20195
  if (traceHeaders) {
20194
20196
  if (traceHeaders.traceparent && !headers.has("traceparent")) headers.set("traceparent", traceHeaders.traceparent);
20195
20197
  if (traceHeaders.tracestate && !headers.has("tracestate")) headers.set("tracestate", traceHeaders.tracestate);
20196
20198
  if (traceHeaders.baggage && !headers.has("baggage")) headers.set("baggage", traceHeaders.baggage);
20197
20199
  }
20198
20200
  }
20199
- return fetch$1(input, _objectSpread2(_objectSpread2({}, init), {}, { headers }));
20201
+ return fetch$1(input2, _objectSpread2(_objectSpread2({}, init), {}, { headers }));
20200
20202
  };
20201
20203
  };
20202
- async function getTraceHeaders(input, targets, respectSampling) {
20203
- if (!shouldPropagateToTarget(typeof input === "string" ? input : input instanceof URL ? input : input.url, targets)) return null;
20204
+ async function getTraceHeaders(input2, targets, respectSampling) {
20205
+ if (!shouldPropagateToTarget(typeof input2 === "string" ? input2 : input2 instanceof URL ? input2 : input2.url, targets)) return null;
20204
20206
  const traceContext = await extractTraceContext();
20205
20207
  if (!traceContext || !traceContext.traceparent) return null;
20206
20208
  if (respectSampling) {
@@ -21310,14 +21312,14 @@ class ThemeStore {
21310
21312
  for (const fn of this.listeners) fn(this.cachedSnapshot);
21311
21313
  }
21312
21314
  }
21313
- const EMPTY = {
21315
+ const EMPTY$1 = {
21314
21316
  sites: null,
21315
21317
  currentSite: null,
21316
21318
  loading: true,
21317
21319
  error: null
21318
21320
  };
21319
21321
  function useSiteAccess(supabase, session, siteId) {
21320
- const [state, setState] = useState(EMPTY);
21322
+ const [state, setState] = useState(EMPTY$1);
21321
21323
  const cachedUserIdRef = useRef(null);
21322
21324
  useEffect(() => {
21323
21325
  if (!session) {
@@ -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(input) {
21400
- if (!input.onPreview || input.authLoading) return "hidden";
21401
- if (input.hasSession && !input.sitesLoaded) return "hidden";
21402
- if (!input.hasSession && input.soldPublic === null) return "hidden";
21403
- const bought = input.paidMarker || input.currentSiteIsLive || input.soldPublic === true;
21404
- if (bought) return "hidden";
21405
- return input.hasSession && input.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$1, 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$1 = {
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";
@@ -22268,13 +22139,13 @@ function isDeploymentInProgress(status) {
22268
22139
  }
22269
22140
  const STALE_PENDING_MS = 20 * 60 * 1e3;
22270
22141
  const STALE_BUILDING_MS = 45 * 60 * 1e3;
22271
- function isDeploymentStale(row) {
22272
- if (row.status !== "pending" && row.status !== "building") return false;
22142
+ function isDeploymentStale(row2) {
22143
+ if (row2.status !== "pending" && row2.status !== "building") return false;
22273
22144
  const now = Date.now();
22274
- if (row.status === "pending") {
22275
- return now - new Date(row.started_at).getTime() > STALE_PENDING_MS;
22145
+ if (row2.status === "pending") {
22146
+ return now - new Date(row2.started_at).getTime() > STALE_PENDING_MS;
22276
22147
  }
22277
- return now - new Date(row.updated_at).getTime() > STALE_BUILDING_MS;
22148
+ return now - new Date(row2.updated_at).getTime() > STALE_BUILDING_MS;
22278
22149
  }
22279
22150
  async function fetchInProgressDeploymentId(supabase, siteId) {
22280
22151
  const { data } = await supabase.from("deployments").select("id, status, started_at, updated_at").eq("site_id", siteId).in("status", [...IN_PROGRESS_STATUSES]).order("started_at", { ascending: false }).limit(5);
@@ -22292,7 +22163,7 @@ async function waitForDeploymentByCommit(supabase, siteId, commitSha, timeoutMs
22292
22163
  return null;
22293
22164
  }
22294
22165
  function useDeploymentStatus(supabase, deploymentId) {
22295
- const [row, setRow] = useState(null);
22166
+ const [row2, setRow] = useState(null);
22296
22167
  useEffect(() => {
22297
22168
  if (!deploymentId) {
22298
22169
  setRow(null);
@@ -22322,7 +22193,7 @@ function useDeploymentStatus(supabase, deploymentId) {
22322
22193
  if (channel) supabase.removeChannel(channel);
22323
22194
  };
22324
22195
  }, [supabase, deploymentId]);
22325
- return { row, status: row?.status ?? null };
22196
+ return { row: row2, status: row2?.status ?? null };
22326
22197
  }
22327
22198
  function deploymentStepIndex(status) {
22328
22199
  switch (status) {
@@ -22461,14 +22332,14 @@ function PublishProgressBar({ status, compact, href }) {
22461
22332
  return /* @__PURE__ */ jsx("div", { style: wrapStyle, children: inner });
22462
22333
  }
22463
22334
  function PublishDialog({
22464
- row,
22335
+ row: row2,
22465
22336
  status,
22466
22337
  onClose,
22467
22338
  onComplete
22468
22339
  }) {
22469
22340
  const buildingStartedAt = useRef(null);
22470
22341
  const [elapsed, setElapsed] = useState(0);
22471
- const progressStatus = row?.status ?? status ?? "pending";
22342
+ const progressStatus = row2?.status ?? status ?? "pending";
22472
22343
  const errored = progressStatus === "error" || progressStatus === "canceled";
22473
22344
  const isReady = progressStatus === "ready";
22474
22345
  const isBuilding = progressStatus === "building";
@@ -22495,7 +22366,7 @@ function PublishDialog({
22495
22366
  /* @__PURE__ */ jsx("h2", { style: { margin: "0 0 12px", fontSize: 18, fontWeight: 600, color: "#dc2626" }, children: "Publisering feilet" }),
22496
22367
  /* @__PURE__ */ jsxs("p", { style: { margin: "0 0 20px", fontSize: 14, lineHeight: 1.5, color: "#444" }, children: [
22497
22368
  progressStatus === "error" ? "Bygg feilet." : "Avbrutt.",
22498
- row?.error_message ? ` ${row.error_message}` : ""
22369
+ row2?.error_message ? ` ${row2.error_message}` : ""
22499
22370
  ] }),
22500
22371
  /* @__PURE__ */ jsx("button", { type: "button", onClick: onComplete, style: secondaryBtnStyle$1, children: "OK" })
22501
22372
  ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -22576,6 +22447,226 @@ function useEditBaseline() {
22576
22447
  };
22577
22448
  }, [session, store, config.siteId]);
22578
22449
  }
22450
+ const EMPTY = {
22451
+ host: null,
22452
+ verified: false,
22453
+ status: null,
22454
+ instructions: [],
22455
+ statusLine: null
22456
+ };
22457
+ function Copyable({ value }) {
22458
+ const [copied, setCopied] = useState(false);
22459
+ return /* @__PURE__ */ jsx(
22460
+ "button",
22461
+ {
22462
+ type: "button",
22463
+ onClick: () => {
22464
+ void navigator.clipboard?.writeText(value).then(
22465
+ () => {
22466
+ setCopied(true);
22467
+ window.setTimeout(() => setCopied(false), 1400);
22468
+ },
22469
+ () => void 0
22470
+ );
22471
+ },
22472
+ title: "Kopier",
22473
+ style: {
22474
+ font: "inherit",
22475
+ fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
22476
+ background: "none",
22477
+ border: 0,
22478
+ padding: 0,
22479
+ cursor: "pointer",
22480
+ color: "inherit",
22481
+ textAlign: "left",
22482
+ wordBreak: "break-all"
22483
+ },
22484
+ children: copied ? "Kopiert" : value
22485
+ }
22486
+ );
22487
+ }
22488
+ function DomainView({ siteId }) {
22489
+ const { api } = useSetto();
22490
+ const [state, setState] = useState(EMPTY);
22491
+ const [host, setHost] = useState("");
22492
+ const [loading, setLoading] = useState(true);
22493
+ const [busy, setBusy] = useState(false);
22494
+ const [error, setError] = useState(null);
22495
+ const load = async () => {
22496
+ try {
22497
+ setState(await api.request(`/onboarding/sites/${siteId}/domain`, {}, "domain"));
22498
+ } catch {
22499
+ setState(EMPTY);
22500
+ } finally {
22501
+ setLoading(false);
22502
+ }
22503
+ };
22504
+ useEffect(() => {
22505
+ void load();
22506
+ }, [siteId]);
22507
+ async function attach() {
22508
+ setBusy(true);
22509
+ setError(null);
22510
+ try {
22511
+ setState(
22512
+ await api.request(
22513
+ `/onboarding/sites/${siteId}/domain`,
22514
+ { method: "POST", body: { host } },
22515
+ "attachDomain"
22516
+ )
22517
+ );
22518
+ setHost("");
22519
+ } catch (err) {
22520
+ const detail = err instanceof Error ? err.message : "";
22521
+ setError(
22522
+ detail.includes("invalid_domain") ? "Det ser ikke ut som et domenenavn. Skriv det som dittfirma.no." : detail.includes("domain_taken") ? "Domenet er allerede i bruk på en annen Setto-side." : detail.includes("site_not_deployed") ? "Nettsiden er ikke publisert ennå." : "Klarte ikke legge til domenet. Prøv igjen."
22523
+ );
22524
+ } finally {
22525
+ setBusy(false);
22526
+ }
22527
+ }
22528
+ async function recheck() {
22529
+ setBusy(true);
22530
+ setError(null);
22531
+ try {
22532
+ setState(
22533
+ await api.request(
22534
+ `/onboarding/sites/${siteId}/domain/verify`,
22535
+ { method: "POST" },
22536
+ "verifyDomain"
22537
+ )
22538
+ );
22539
+ } catch {
22540
+ setError("Klarte ikke sjekke akkurat nå. Prøv igjen om litt.");
22541
+ } finally {
22542
+ setBusy(false);
22543
+ }
22544
+ }
22545
+ async function detach() {
22546
+ setBusy(true);
22547
+ setError(null);
22548
+ try {
22549
+ setState(
22550
+ await api.request(
22551
+ `/onboarding/sites/${siteId}/domain`,
22552
+ { method: "DELETE" },
22553
+ "detachDomain"
22554
+ )
22555
+ );
22556
+ } catch {
22557
+ setError("Klarte ikke fjerne domenet.");
22558
+ } finally {
22559
+ setBusy(false);
22560
+ }
22561
+ }
22562
+ if (loading) return /* @__PURE__ */ jsx("p", { style: muted, children: "Henter …" });
22563
+ if (!state.host) {
22564
+ return /* @__PURE__ */ jsxs("div", { style: wrap, children: [
22565
+ /* @__PURE__ */ jsx("p", { style: lead, children: "Har du et domene fra før — dittfirma.no — kan du peke det hit. Nettsiden beholder adressen den har nå i tillegg, så ingenting slutter å virke underveis." }),
22566
+ /* @__PURE__ */ jsxs("div", { style: row, children: [
22567
+ /* @__PURE__ */ jsx(
22568
+ "input",
22569
+ {
22570
+ value: host,
22571
+ onChange: (e) => setHost(e.target.value),
22572
+ placeholder: "dittfirma.no",
22573
+ "aria-label": "Domenet ditt",
22574
+ autoCapitalize: "off",
22575
+ autoCorrect: "off",
22576
+ spellCheck: false,
22577
+ style: input
22578
+ }
22579
+ ),
22580
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: () => void attach(), disabled: busy || !host.trim(), style: primary, children: busy ? "Legger til …" : "Legg til domene" })
22581
+ ] }),
22582
+ error && /* @__PURE__ */ jsx("p", { style: errorStyle$2, children: error })
22583
+ ] });
22584
+ }
22585
+ return /* @__PURE__ */ jsxs("div", { style: wrap, children: [
22586
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "baseline", gap: "0.6rem", flexWrap: "wrap" }, children: [
22587
+ /* @__PURE__ */ jsx("strong", { style: { fontSize: "1.05rem" }, children: state.host }),
22588
+ /* @__PURE__ */ jsx("span", { style: state.verified ? liveBadge : pendingBadge, children: state.verified ? "Live" : "Venter på DNS" })
22589
+ ] }),
22590
+ state.statusLine && /* @__PURE__ */ jsx("p", { style: lead, children: state.statusLine }),
22591
+ state.instructions.length > 0 && !state.verified && /* @__PURE__ */ jsxs(Fragment, { children: [
22592
+ /* @__PURE__ */ jsx("p", { style: muted, children: "Logg inn der du kjøpte domenet, finn DNS-innstillingene, og legg inn dette:" }),
22593
+ /* @__PURE__ */ jsx("div", { style: { display: "grid", gap: "0.6rem" }, children: state.instructions.map((rec) => /* @__PURE__ */ jsxs("div", { style: card, children: [
22594
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: "1.2rem", flexWrap: "wrap" }, children: [
22595
+ /* @__PURE__ */ jsx(Field, { label: "Type", value: rec.type }),
22596
+ /* @__PURE__ */ jsx(Field, { label: "Navn", value: rec.name }),
22597
+ /* @__PURE__ */ jsx(Field, { label: "Peker til", value: rec.value, wide: true })
22598
+ ] }),
22599
+ /* @__PURE__ */ jsx("p", { style: { ...muted, margin: "0.35rem 0 0" }, children: rec.why })
22600
+ ] }, `${rec.type}-${rec.name}`)) })
22601
+ ] }),
22602
+ error && /* @__PURE__ */ jsx("p", { style: errorStyle$2, children: error }),
22603
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: "0.6rem", flexWrap: "wrap" }, children: [
22604
+ !state.verified && /* @__PURE__ */ jsx("button", { type: "button", onClick: () => void recheck(), disabled: busy, style: primary, children: busy ? "Sjekker …" : "Jeg har lagt inn radene" }),
22605
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: () => void detach(), disabled: busy, style: quiet, children: "Fjern domenet" })
22606
+ ] })
22607
+ ] });
22608
+ }
22609
+ function Field({ label, value, wide }) {
22610
+ return /* @__PURE__ */ jsxs("div", { style: { minWidth: wide ? "14rem" : "5rem", flex: wide ? "1 1 14rem" : "0 0 auto" }, children: [
22611
+ /* @__PURE__ */ jsx("div", { style: { ...muted, fontSize: "0.72rem", textTransform: "uppercase", letterSpacing: "0.04em" }, children: label }),
22612
+ /* @__PURE__ */ jsx(Copyable, { value })
22613
+ ] });
22614
+ }
22615
+ const wrap = { display: "grid", gap: "0.9rem", maxWidth: "42rem" };
22616
+ const lead = { margin: 0, lineHeight: 1.5 };
22617
+ const muted = { margin: 0, opacity: 0.65, lineHeight: 1.5 };
22618
+ const row = { display: "flex", gap: "0.6rem", flexWrap: "wrap" };
22619
+ const input = {
22620
+ flex: "1 1 14rem",
22621
+ padding: "0.6rem 0.75rem",
22622
+ border: "1px solid rgba(0,0,0,0.18)",
22623
+ borderRadius: "0.5rem",
22624
+ font: "inherit"
22625
+ };
22626
+ const card = {
22627
+ padding: "0.7rem 0.85rem",
22628
+ border: "1px solid rgba(0,0,0,0.12)",
22629
+ borderRadius: "0.6rem",
22630
+ background: "rgba(0,0,0,0.02)"
22631
+ };
22632
+ const primary = {
22633
+ padding: "0.6rem 1.1rem",
22634
+ borderRadius: "999px",
22635
+ border: 0,
22636
+ background: "#c4502a",
22637
+ color: "#fff",
22638
+ font: "inherit",
22639
+ fontWeight: 600,
22640
+ cursor: "pointer"
22641
+ };
22642
+ const quiet = {
22643
+ padding: "0.6rem 0.9rem",
22644
+ borderRadius: "999px",
22645
+ border: "1px solid rgba(0,0,0,0.18)",
22646
+ background: "transparent",
22647
+ font: "inherit",
22648
+ cursor: "pointer"
22649
+ };
22650
+ const errorStyle$2 = { margin: 0, color: "#b42318" };
22651
+ const liveBadge = {
22652
+ fontSize: "0.75rem",
22653
+ padding: "0.15rem 0.5rem",
22654
+ borderRadius: "999px",
22655
+ background: "rgba(15,123,79,0.12)",
22656
+ color: "#0f7b4f"
22657
+ };
22658
+ const pendingBadge = {
22659
+ fontSize: "0.75rem",
22660
+ padding: "0.15rem 0.5rem",
22661
+ borderRadius: "999px",
22662
+ background: "rgba(196,80,42,0.12)",
22663
+ color: "#c4502a"
22664
+ };
22665
+ const domainModule = {
22666
+ id: "domain",
22667
+ label: "Domene",
22668
+ AdminView: DomainView
22669
+ };
22579
22670
  const shellStyle$1 = {
22580
22671
  minHeight: "100dvh",
22581
22672
  background: "#0a0a0d",
@@ -22846,28 +22937,28 @@ function SubmissionList({ siteId }) {
22846
22937
  return /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 12 }, children: [
22847
22938
  /* @__PURE__ */ jsx("h3", { style: subheadingStyle, children: "Henvendelser" }),
22848
22939
  error ? /* @__PURE__ */ jsx("p", { style: errStyle, children: error }) : null,
22849
- rows.length === 0 ? /* @__PURE__ */ jsx("p", { style: mutedStyle, children: "Ingen henvendelser ennå. Når noen sender inn et skjema på siden, dukker det opp her og i e-posten." }) : /* @__PURE__ */ jsx("ul", { style: { listStyle: "none", padding: 0, margin: 0, display: "flex", flexDirection: "column", gap: 6 }, children: rows.map((row) => /* @__PURE__ */ jsx(
22940
+ rows.length === 0 ? /* @__PURE__ */ jsx("p", { style: mutedStyle, children: "Ingen henvendelser ennå. Når noen sender inn et skjema på siden, dukker det opp her og i e-posten." }) : /* @__PURE__ */ jsx("ul", { style: { listStyle: "none", padding: 0, margin: 0, display: "flex", flexDirection: "column", gap: 6 }, children: rows.map((row2) => /* @__PURE__ */ jsx(
22850
22941
  SubmissionItem,
22851
22942
  {
22852
- row,
22853
- open: openId === row.id,
22854
- onToggle: () => setOpenId(openId === row.id ? null : row.id),
22855
- onDelete: () => void remove2(row.id)
22943
+ row: row2,
22944
+ open: openId === row2.id,
22945
+ onToggle: () => setOpenId(openId === row2.id ? null : row2.id),
22946
+ onDelete: () => void remove2(row2.id)
22856
22947
  },
22857
- row.id
22948
+ row2.id
22858
22949
  )) }),
22859
22950
  hasMore ? /* @__PURE__ */ jsx("button", { type: "button", onClick: () => void loadMore(), disabled: loadingMore, style: secondaryBtnStyle, children: loadingMore ? "Laster …" : "Vis eldre" }) : null
22860
22951
  ] });
22861
22952
  }
22862
22953
  function SubmissionItem({
22863
- row,
22954
+ row: row2,
22864
22955
  open,
22865
22956
  onToggle,
22866
22957
  onDelete
22867
22958
  }) {
22868
- const fields = submissionFields(row);
22959
+ const fields = submissionFields(row2);
22869
22960
  const sender = senderOf(fields);
22870
- const title = row.meta.title ?? row.form_id;
22961
+ const title = row2.meta.title ?? row2.form_id;
22871
22962
  return /* @__PURE__ */ jsxs("li", { children: [
22872
22963
  /* @__PURE__ */ jsxs(
22873
22964
  "button",
@@ -22877,7 +22968,7 @@ function SubmissionItem({
22877
22968
  "aria-expanded": open,
22878
22969
  style: { ...rowStyle, width: "100%", textAlign: "left", color: "inherit", border: "none", cursor: "pointer", font: "inherit" },
22879
22970
  children: [
22880
- /* @__PURE__ */ jsx("span", { style: statusDotStyle(row.email_status), title: emailStatusLabel(row) }),
22971
+ /* @__PURE__ */ jsx("span", { style: statusDotStyle(row2.email_status), title: emailStatusLabel(row2) }),
22881
22972
  /* @__PURE__ */ jsxs("span", { style: { flex: 1, fontSize: 13, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: [
22882
22973
  sender ?? /* @__PURE__ */ jsx("span", { style: { color: "#aaa" }, children: "Uten navn" }),
22883
22974
  /* @__PURE__ */ jsxs("span", { style: { color: "#888" }, children: [
@@ -22885,7 +22976,7 @@ function SubmissionItem({
22885
22976
  title
22886
22977
  ] })
22887
22978
  ] }),
22888
- /* @__PURE__ */ jsx("time", { style: { fontSize: 11, color: "#888", flexShrink: 0 }, children: formatWhen(row.created_at) })
22979
+ /* @__PURE__ */ jsx("time", { style: { fontSize: 11, color: "#888", flexShrink: 0 }, children: formatWhen(row2.created_at) })
22889
22980
  ]
22890
22981
  }
22891
22982
  ),
@@ -22895,26 +22986,26 @@ function SubmissionItem({
22895
22986
  /* @__PURE__ */ jsx("dd", { style: { margin: 0, fontSize: 14, whiteSpace: "pre-wrap", wordBreak: "break-word" }, children: f.value === "" ? /* @__PURE__ */ jsx("span", { style: { color: "#666" }, children: "—" }) : f.value })
22896
22987
  ] }, f.name)) }),
22897
22988
  /* @__PURE__ */ jsxs("p", { style: mutedStyle, children: [
22898
- emailStatusLabel(row),
22899
- row.recipients.length > 0 ? ` til ${row.recipients.join(", ")}` : "",
22900
- row.email_error ? ` — ${row.email_error}` : "",
22901
- row.meta.page ? /* @__PURE__ */ jsxs(Fragment, { children: [
22989
+ emailStatusLabel(row2),
22990
+ row2.recipients.length > 0 ? ` til ${row2.recipients.join(", ")}` : "",
22991
+ row2.email_error ? ` — ${row2.email_error}` : "",
22992
+ row2.meta.page ? /* @__PURE__ */ jsxs(Fragment, { children: [
22902
22993
  " · ",
22903
- /* @__PURE__ */ jsx("a", { href: row.meta.page, style: { color: "#bda6ff" }, target: "_blank", rel: "noreferrer", children: "Sendt fra siden" })
22994
+ /* @__PURE__ */ jsx("a", { href: row2.meta.page, style: { color: "#bda6ff" }, target: "_blank", rel: "noreferrer", children: "Sendt fra siden" })
22904
22995
  ] }) : null
22905
22996
  ] }),
22906
22997
  /* @__PURE__ */ jsx("button", { type: "button", onClick: onDelete, style: dangerBtnStyle, children: "Slett henvendelse" })
22907
22998
  ] }) : null
22908
22999
  ] });
22909
23000
  }
22910
- function submissionFields(row) {
23001
+ function submissionFields(row2) {
22911
23002
  const flat = [];
22912
- flatten(row.data, "", flat);
22913
- const rank = new Map((row.field_order ?? []).map((n, i) => [n, i]));
23003
+ flatten(row2.data, "", flat);
23004
+ const rank = new Map((row2.field_order ?? []).map((n, i) => [n, i]));
22914
23005
  const rankOf = (name) => rank.get(name) ?? rank.get(name.split(".")[0]) ?? Number.MAX_SAFE_INTEGER;
22915
23006
  return flat.map((f, i) => ({ f, i })).sort((a, b) => rankOf(a.f.name) - rankOf(b.f.name) || a.i - b.i).map(({ f }) => ({
22916
23007
  name: f.name,
22917
- label: row.labels?.[f.name] ?? row.labels?.[f.name.split(".")[0]] ?? prettify(f.name),
23008
+ label: row2.labels?.[f.name] ?? row2.labels?.[f.name.split(".")[0]] ?? prettify(f.name),
22918
23009
  value: f.value
22919
23010
  }));
22920
23011
  }
@@ -22953,8 +23044,8 @@ function senderOf(fields) {
22953
23044
  const first = fields.find((f) => f.value !== "");
22954
23045
  return first ? first.value.slice(0, 80) : null;
22955
23046
  }
22956
- function emailStatusLabel(row) {
22957
- switch (row.email_status) {
23047
+ function emailStatusLabel(row2) {
23048
+ switch (row2.email_status) {
22958
23049
  case "sent":
22959
23050
  return "E-post sendt";
22960
23051
  case "failed":
@@ -24404,7 +24495,7 @@ const formsModule = {
24404
24495
  label: "Skjemaer",
24405
24496
  AdminView: FormsView
24406
24497
  };
24407
- const SETTO_MODULES = [formsModule];
24498
+ const SETTO_MODULES = [formsModule, domainModule];
24408
24499
  function findModule(id) {
24409
24500
  return id ? SETTO_MODULES.find((m) => m.id === id) : void 0;
24410
24501
  }
@@ -24710,7 +24801,7 @@ function EditToolbar() {
24710
24801
  }
24711
24802
  ),
24712
24803
  expanded ? /* @__PURE__ */ jsxs("div", { style: contentStyle, children: [
24713
- error ? /* @__PURE__ */ jsx("span", { style: errorStyle, children: error }) : null,
24804
+ error ? /* @__PURE__ */ jsx("span", { style: errorStyle$1, children: error }) : null,
24714
24805
  hasDrafts && !publishing && activeDeployment === null ? /* @__PURE__ */ jsx("button", { type: "button", onClick: publish, style: publishBtnStyle, children: "Publiser" }) : null,
24715
24806
  showPublishedNotice ? /* @__PURE__ */ jsxs(
24716
24807
  "button",
@@ -24831,7 +24922,7 @@ const contentStyle = {
24831
24922
  paddingLeft: 4,
24832
24923
  whiteSpace: "nowrap"
24833
24924
  };
24834
- const errorStyle = {
24925
+ const errorStyle$1 = {
24835
24926
  color: "#ffb4a8",
24836
24927
  fontSize: 12,
24837
24928
  maxWidth: 240,
@@ -25085,7 +25176,7 @@ function SettoProvider({ config, children }) {
25085
25176
  const editShellActive = editMode && !!value.store;
25086
25177
  return /* @__PURE__ */ jsx(SettoContext.Provider, { value, children: /* @__PURE__ */ jsxs(SectionEditProvider, { children: [
25087
25178
  /* @__PURE__ */ jsx(EditModeShell, { active: editShellActive, themeStore: value.themeStore, children }),
25088
- !onAdminRoute && /* @__PURE__ */ jsx(SettoPreviewBar, {})
25179
+ !onAdminRoute && /* @__PURE__ */ jsx(SettoChrome, {})
25089
25180
  ] }) });
25090
25181
  }
25091
25182
  function useSetto() {
@@ -25095,6 +25186,202 @@ function useSetto() {
25095
25186
  }
25096
25187
  return ctx;
25097
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
+ };
25098
25385
  function isTextEditClick(target) {
25099
25386
  if (target.closest("[data-setto-key]")) return true;
25100
25387
  if (target.closest("[data-setto-ui]")) return true;
@@ -26230,8 +26517,8 @@ function DeploymentsView({
26230
26517
  ] })
26231
26518
  ] });
26232
26519
  }
26233
- function statusLabel(row) {
26234
- switch (row.status) {
26520
+ function statusLabel(row2) {
26521
+ switch (row2.status) {
26235
26522
  case "pending":
26236
26523
  return "Venter";
26237
26524
  case "building":
@@ -26239,7 +26526,7 @@ function statusLabel(row) {
26239
26526
  case "ready":
26240
26527
  return "Publisert";
26241
26528
  case "error":
26242
- return row.error_message?.includes("webhook") ? "Utløpt" : "Feilet";
26529
+ return row2.error_message?.includes("webhook") ? "Utløpt" : "Feilet";
26243
26530
  case "canceled":
26244
26531
  return "Avbrutt";
26245
26532
  }
@@ -26400,20 +26687,20 @@ function nodeChildren(node) {
26400
26687
  }
26401
26688
  }
26402
26689
  const ANIMATION_PRESETS = ["fadeIn", "slideUp", "stagger", "scrollReveal"];
26403
- function validateBlockTree(input) {
26690
+ function validateBlockTree(input2) {
26404
26691
  const issues = [];
26405
- if (!isRecord(input)) {
26692
+ if (!isRecord(input2)) {
26406
26693
  return fail("$", "Block tree must be an object");
26407
26694
  }
26408
- if (input.version !== "1") {
26695
+ if (input2.version !== "1") {
26409
26696
  issues.push({ path: "$.version", message: 'Expected version "1"' });
26410
26697
  }
26411
- if (!Array.isArray(input.root)) {
26698
+ if (!Array.isArray(input2.root)) {
26412
26699
  return fail("$.root", "root must be an array of block nodes");
26413
26700
  }
26414
26701
  const seenIds = /* @__PURE__ */ new Set();
26415
- for (let i = 0; i < input.root.length; i++) {
26416
- validateNode(input.root[i], `root[${i}]`, seenIds, issues);
26702
+ for (let i = 0; i < input2.root.length; i++) {
26703
+ validateNode(input2.root[i], `root[${i}]`, seenIds, issues);
26417
26704
  }
26418
26705
  return issues.length ? { ok: false, issues } : { ok: true };
26419
26706
  }
@@ -26563,14 +26850,14 @@ function isRecord(value) {
26563
26850
  function fail(path, message) {
26564
26851
  return { ok: false, issues: [{ path, message }] };
26565
26852
  }
26566
- function parseBlockTree(input) {
26567
- const result = validateBlockTree(input);
26853
+ function parseBlockTree(input2) {
26854
+ const result = validateBlockTree(input2);
26568
26855
  if (!result.ok) {
26569
26856
  const first = result.issues[0];
26570
26857
  const detail = first ? `${first.path}: ${first.message}` : "unknown";
26571
26858
  throw new Error(`Invalid block tree at ${detail}`);
26572
26859
  }
26573
- return input;
26860
+ return input2;
26574
26861
  }
26575
26862
  const IconRegistryContext = createContext(null);
26576
26863
  const RepeaterScopeContext = createContext(null);
@@ -26856,6 +27143,7 @@ export {
26856
27143
  SettoAnimation,
26857
27144
  SettoBlock,
26858
27145
  SettoBlockTree,
27146
+ SettoChrome,
26859
27147
  SettoForm,
26860
27148
  SettoIcon,
26861
27149
  SettoImage,
@@ -26872,6 +27160,7 @@ export {
26872
27160
  isPreviewHost,
26873
27161
  nodeChildren,
26874
27162
  parseBlockTree,
27163
+ previewBarState,
26875
27164
  readLocaleFields,
26876
27165
  settoSubmissionUrl,
26877
27166
  useGuestEdit,