@sevenfold/setto-client 0.3.4 → 0.5.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,5 +1,5 @@
1
1
  import { jsx, jsxs, Fragment } from "react/jsx-runtime";
2
- import { useEffect, useSyncExternalStore, useState, useRef, useLayoutEffect, createContext, useContext, useCallback, useMemo, useId, forwardRef } from "react";
2
+ import { useState, useEffect, useSyncExternalStore, useRef, useLayoutEffect, createContext, useContext, useCallback, useMemo, useId, forwardRef } from "react";
3
3
  import { useTranslation } from "react-i18next";
4
4
  import { createPortal } from "react-dom";
5
5
  function __rest(s, e) {
@@ -21129,6 +21129,15 @@ class ThemeStore {
21129
21129
  this.drafts.clear();
21130
21130
  this.bump();
21131
21131
  }
21132
+ /** Resets every draft back to its original value. */
21133
+ revertAll() {
21134
+ for (const draft of this.drafts.values()) {
21135
+ if (!this.data[draft.sectionId]) this.data[draft.sectionId] = {};
21136
+ this.data[draft.sectionId][draft.field] = draft.original;
21137
+ }
21138
+ this.drafts.clear();
21139
+ this.bump();
21140
+ }
21132
21141
  commit() {
21133
21142
  this.baseline = structuredClone(this.data);
21134
21143
  this.drafts.clear();
@@ -21146,41 +21155,77 @@ class ThemeStore {
21146
21155
  for (const fn of this.listeners) fn(this.cachedSnapshot);
21147
21156
  }
21148
21157
  }
21149
- const TOOLBAR_HEIGHT = 44;
21150
- const STYLE_ID = "setto-edit-layout";
21158
+ const EMPTY = {
21159
+ sites: null,
21160
+ currentSite: null,
21161
+ loading: true,
21162
+ error: null
21163
+ };
21164
+ function useSiteAccess(supabase, session, siteId) {
21165
+ const [state, setState] = useState(EMPTY);
21166
+ useEffect(() => {
21167
+ if (!session) {
21168
+ setState({ sites: null, currentSite: null, loading: false, error: null });
21169
+ return;
21170
+ }
21171
+ let cancelled = false;
21172
+ setState((prev) => ({ ...prev, loading: true, error: null }));
21173
+ supabase.from("sites").select("*").then(({ data, error }) => {
21174
+ if (cancelled) return;
21175
+ if (error) {
21176
+ setState({ sites: [], currentSite: null, loading: false, error: error.message });
21177
+ return;
21178
+ }
21179
+ const rows = data ?? [];
21180
+ const current = rows.find((s) => s.id === siteId) ?? null;
21181
+ setState({ sites: rows, currentSite: current, loading: false, error: null });
21182
+ });
21183
+ return () => {
21184
+ cancelled = true;
21185
+ };
21186
+ }, [supabase, session, siteId]);
21187
+ return state;
21188
+ }
21189
+ const SETTO_BASE = "/setto";
21190
+ function siteHomeUrl(pathname = "/") {
21191
+ const u = new URL(window.location.href);
21192
+ u.pathname = pathname;
21193
+ u.search = "";
21194
+ return u.toString();
21195
+ }
21196
+ function isAdminRoute() {
21197
+ return window.location.pathname.startsWith(SETTO_BASE);
21198
+ }
21199
+ function isAdminDeploymentView() {
21200
+ return isAdminRoute() && new URLSearchParams(window.location.search).has("deployment");
21201
+ }
21202
+ function adminRedirectUrl() {
21203
+ return `${window.location.origin}${SETTO_BASE}`;
21204
+ }
21205
+ const FAB_SIZE = 52;
21206
+ const FAB_INSET = 16;
21207
+ const STYLE_ID$1 = "setto-edit-layout";
21151
21208
  const HTML_CLASS = "setto-edit-mode";
21152
21209
  function useSettoDocumentLayout(active) {
21153
21210
  useEffect(() => {
21154
21211
  const html = document.documentElement;
21155
21212
  html.classList.add(HTML_CLASS);
21156
- html.style.setProperty("--setto-toolbar-height", `${TOOLBAR_HEIGHT}px`);
21157
- let styleEl = document.getElementById(STYLE_ID);
21213
+ html.style.setProperty("--setto-fab-size", `${FAB_SIZE}px`);
21214
+ html.style.setProperty("--setto-fab-inset", `${FAB_INSET}px`);
21215
+ let styleEl = document.getElementById(STYLE_ID$1);
21158
21216
  if (!styleEl) {
21159
21217
  styleEl = document.createElement("style");
21160
- styleEl.id = STYLE_ID;
21218
+ styleEl.id = STYLE_ID$1;
21161
21219
  styleEl.textContent = `
21162
21220
  html.setto-edit-mode {
21163
21221
  width: 100%;
21164
21222
  overflow-x: clip;
21165
21223
  }
21166
21224
 
21167
- html.setto-edit-mode body {
21168
- padding-top: var(--setto-toolbar-height);
21169
- }
21170
-
21171
- html.setto-edit-mode #root {
21172
- width: 100%;
21173
- min-height: calc(100dvh - var(--setto-toolbar-height));
21174
- /* Containing block for position:fixed descendants so they sit below the toolbar. */
21175
- transform: translateZ(0);
21176
- }
21177
-
21178
21225
  html.setto-edit-mode #setto-toolbar-root {
21179
21226
  position: fixed;
21180
- top: 0;
21181
- left: 0;
21182
- right: 0;
21183
- height: var(--setto-toolbar-height);
21227
+ left: var(--setto-fab-inset);
21228
+ bottom: var(--setto-fab-inset);
21184
21229
  z-index: 2147483647;
21185
21230
  }
21186
21231
 
@@ -21207,19 +21252,13 @@ function useSettoDocumentLayout(active) {
21207
21252
  }
21208
21253
 
21209
21254
  html.setto-edit-mode [data-setto-key]:hover {
21210
- outline: 1px dashed rgba(100, 10, 255, 0.45);
21255
+ outline: 1px dashed rgba(196, 80, 42, 0.55);
21211
21256
  outline-offset: 2px;
21212
21257
  border-radius: 2px;
21213
21258
  }
21214
21259
 
21215
21260
  html.setto-edit-mode [data-setto-section]:hover {
21216
- box-shadow: inset 0 0 0 1px rgba(100, 10, 255, 0.25);
21217
- }
21218
-
21219
- @media (max-width: 480px) {
21220
- html.setto-edit-mode [data-setto-draft-count] {
21221
- display: none;
21222
- }
21261
+ box-shadow: inset 0 0 0 1px rgba(196, 80, 42, 0.3);
21223
21262
  }
21224
21263
  `;
21225
21264
  document.head.appendChild(styleEl);
@@ -21227,7 +21266,8 @@ function useSettoDocumentLayout(active) {
21227
21266
  notifyLayoutChange();
21228
21267
  return () => {
21229
21268
  html.classList.remove(HTML_CLASS);
21230
- html.style.removeProperty("--setto-toolbar-height");
21269
+ html.style.removeProperty("--setto-fab-size");
21270
+ html.style.removeProperty("--setto-fab-inset");
21231
21271
  styleEl?.remove();
21232
21272
  notifyLayoutChange();
21233
21273
  };
@@ -21395,8 +21435,7 @@ function computePosition(targetEl, toolbarW, toolbarH) {
21395
21435
  const rect = targetEl.getBoundingClientRect();
21396
21436
  let left = rect.left + (rect.width - toolbarW) / 2;
21397
21437
  left = Math.max(MARGIN$1, Math.min(left, window.innerWidth - toolbarW - MARGIN$1));
21398
- const minTop = TOOLBAR_HEIGHT + GAP;
21399
- const top = Math.max(minTop, rect.top - toolbarH - GAP);
21438
+ const top = Math.max(MARGIN$1, rect.top - toolbarH - GAP);
21400
21439
  return { top, left };
21401
21440
  }
21402
21441
  function BrandColorPicker({
@@ -21432,7 +21471,7 @@ function BrandColorPicker({
21432
21471
  left = window.innerWidth - popoverW - 8;
21433
21472
  }
21434
21473
  if (left < 8) left = 8;
21435
- if (top < TOOLBAR_HEIGHT + 4) top = rect.bottom + 6;
21474
+ if (top < 8) top = rect.bottom + 6;
21436
21475
  if (top + popoverH > window.innerHeight - 8) {
21437
21476
  top = rect.top - popoverH - 6;
21438
21477
  }
@@ -22150,6 +22189,43 @@ function useEditBaseline() {
22150
22189
  };
22151
22190
  }, [session, store, config.siteId]);
22152
22191
  }
22192
+ function SettoMark({
22193
+ size = 24,
22194
+ letterColor = "#F2EBDF",
22195
+ dotColor = "#C4502A",
22196
+ style
22197
+ }) {
22198
+ return /* @__PURE__ */ jsxs(
22199
+ "svg",
22200
+ {
22201
+ xmlns: "http://www.w3.org/2000/svg",
22202
+ viewBox: "0 0 64 64",
22203
+ width: size,
22204
+ height: size,
22205
+ "aria-hidden": true,
22206
+ style: { display: "block", ...style },
22207
+ children: [
22208
+ /* @__PURE__ */ jsx(
22209
+ "text",
22210
+ {
22211
+ x: "32",
22212
+ y: "46",
22213
+ textAnchor: "middle",
22214
+ fontFamily: "Georgia, serif",
22215
+ fontStyle: "italic",
22216
+ fontSize: "42",
22217
+ fill: letterColor,
22218
+ children: "S"
22219
+ }
22220
+ ),
22221
+ /* @__PURE__ */ jsx("circle", { cx: "49", cy: "17", r: "5", fill: dotColor })
22222
+ ]
22223
+ }
22224
+ );
22225
+ }
22226
+ const BLEKK = "#211711";
22227
+ const CREMA = "#F2EBDF";
22228
+ const TERRACOTTA = "#C4502A";
22153
22229
  function EditToolbar() {
22154
22230
  const { store, themeStore, assetStore, api, config, supabase } = useSetto();
22155
22231
  const { i18n } = useTranslation();
@@ -22178,6 +22254,7 @@ function EditToolbar() {
22178
22254
  const [error, setError] = useState(null);
22179
22255
  const [publishedNotice, setPublishedNotice] = useState(false);
22180
22256
  const [menuOpen, setMenuOpen] = useState(false);
22257
+ const [expanded, setExpanded] = useState(false);
22181
22258
  const menuRef = useRef(null);
22182
22259
  const publishedNoticeTimer = useRef(null);
22183
22260
  const { row: deploymentRow, status: deploymentStatus } = useDeploymentStatus(
@@ -22208,6 +22285,18 @@ function EditToolbar() {
22208
22285
  const hasDrafts = draftCount > 0;
22209
22286
  const showToolbarProgress = activeDeployment !== null && !publishDialogOpen && deploymentRow !== null && isDeploymentInProgress(deploymentRow.status) && !isDeploymentStale(deploymentRow);
22210
22287
  const showPublishedNotice = publishedNotice && !publishDialogOpen;
22288
+ useEffect(() => {
22289
+ if (error || showToolbarProgress || showPublishedNotice) {
22290
+ setExpanded(true);
22291
+ }
22292
+ }, [error, showToolbarProgress, showPublishedNotice]);
22293
+ const toggleExpanded = () => {
22294
+ setExpanded((prev) => {
22295
+ const next = !prev;
22296
+ if (!next) setMenuOpen(false);
22297
+ return next;
22298
+ });
22299
+ };
22211
22300
  useEffect(() => {
22212
22301
  return () => {
22213
22302
  if (publishedNoticeTimer.current) clearTimeout(publishedNoticeTimer.current);
@@ -22358,76 +22447,109 @@ function EditToolbar() {
22358
22447
  setPublishedNotice(false);
22359
22448
  if (publishedNoticeTimer.current) clearTimeout(publishedNoticeTimer.current);
22360
22449
  };
22450
+ const cancelDrafts = () => {
22451
+ setMenuOpen(false);
22452
+ store?.revertAll();
22453
+ themeStore?.revertAll();
22454
+ assetStore?.revertAll();
22455
+ setError(null);
22456
+ setExpanded(false);
22457
+ };
22361
22458
  const signOut = () => {
22362
22459
  setMenuOpen(false);
22363
22460
  void supabase.auth.signOut();
22364
22461
  };
22365
22462
  const goToHistory = () => {
22366
22463
  setMenuOpen(false);
22367
- window.location.href = "/admin";
22464
+ window.location.href = SETTO_BASE;
22368
22465
  };
22369
22466
  return /* @__PURE__ */ jsxs(Fragment, { children: [
22370
- /* @__PURE__ */ jsxs("header", { style: barStyle, role: "toolbar", "aria-label": "Setto editor", children: [
22371
- /* @__PURE__ */ jsx("div", { style: leftStyle, children: /* @__PURE__ */ jsx("strong", { style: { fontSize: 13 }, children: "Setto" }) }),
22372
- /* @__PURE__ */ jsxs("div", { style: rightStyle, children: [
22373
- error ? /* @__PURE__ */ jsx("span", { style: errorStyle, children: error }) : null,
22374
- hasDrafts && !publishing && activeDeployment === null ? /* @__PURE__ */ jsxs(Fragment, { children: [
22375
- /* @__PURE__ */ jsxs("span", { style: draftStyle, "data-setto-draft-count": true, children: [
22376
- draftCount,
22377
- " ",
22378
- draftCount === 1 ? "endring" : "endringer"
22379
- ] }),
22380
- /* @__PURE__ */ jsx("button", { type: "button", onClick: publish, style: publishBtnStyle, children: "Publiser" })
22381
- ] }) : null,
22382
- showPublishedNotice ? /* @__PURE__ */ jsxs(
22383
- "button",
22384
- {
22385
- type: "button",
22386
- onClick: dismissPublishedNotice,
22387
- style: publishedNoticeStyle,
22388
- title: "Lukk",
22389
- children: [
22390
- /* @__PURE__ */ jsx(PublishedCheckIcon, {}),
22391
- "Publisert"
22392
- ]
22393
- }
22394
- ) : null,
22395
- showToolbarProgress && deploymentRow ? /* @__PURE__ */ jsx(
22396
- PublishProgressBar,
22397
- {
22398
- status: deploymentRow.status,
22399
- compact: true,
22400
- href: `/admin?deployment=${encodeURIComponent(activeDeployment)}`
22401
- }
22402
- ) : null,
22403
- /* @__PURE__ */ jsxs("div", { ref: menuRef, style: { position: "relative" }, children: [
22467
+ /* @__PURE__ */ jsxs(
22468
+ "div",
22469
+ {
22470
+ role: "toolbar",
22471
+ "aria-label": "Setto editor",
22472
+ style: {
22473
+ ...containerStyleFor(hasDrafts),
22474
+ ...expanded ? expandedContainerStyle : {}
22475
+ },
22476
+ children: [
22404
22477
  /* @__PURE__ */ jsx(
22405
22478
  "button",
22406
22479
  {
22407
22480
  type: "button",
22408
- onClick: () => setMenuOpen((o) => !o),
22409
- "aria-label": "Meny",
22410
- "aria-expanded": menuOpen,
22411
- style: {
22412
- ...menuBtnStyle,
22413
- background: menuOpen ? "#f0f0f0" : void 0
22414
- },
22415
- onMouseEnter: (e) => {
22416
- e.currentTarget.style.background = "#f0f0f0";
22417
- },
22418
- onMouseLeave: (e) => {
22419
- e.currentTarget.style.background = menuOpen ? "#f0f0f0" : "transparent";
22420
- },
22421
- children: "⋮"
22481
+ "aria-label": expanded ? "Lukk Setto-meny" : "Åpne Setto-meny",
22482
+ "aria-expanded": expanded,
22483
+ title: "Setto",
22484
+ style: markButtonStyle,
22485
+ onClick: toggleExpanded,
22486
+ children: /* @__PURE__ */ jsx(SettoMark, { size: 28 })
22422
22487
  }
22423
22488
  ),
22424
- menuOpen ? /* @__PURE__ */ jsxs("div", { style: dropdownStyle, role: "menu", children: [
22425
- /* @__PURE__ */ jsx(MenuItem, { onClick: goToHistory, icon: /* @__PURE__ */ jsx(HistoryIcon, {}), children: "Historikk" }),
22426
- /* @__PURE__ */ jsx(MenuItem, { onClick: signOut, icon: /* @__PURE__ */ jsx(LogOutIcon, {}), children: "Logg ut" })
22489
+ expanded ? /* @__PURE__ */ jsxs("div", { style: contentStyle, children: [
22490
+ error ? /* @__PURE__ */ jsx("span", { style: errorStyle, children: error }) : null,
22491
+ hasDrafts && !publishing && activeDeployment === null ? /* @__PURE__ */ jsx("button", { type: "button", onClick: publish, style: publishBtnStyle, children: "Publiser" }) : null,
22492
+ showPublishedNotice ? /* @__PURE__ */ jsxs(
22493
+ "button",
22494
+ {
22495
+ type: "button",
22496
+ onClick: dismissPublishedNotice,
22497
+ style: publishedNoticeStyle,
22498
+ title: "Lukk",
22499
+ children: [
22500
+ /* @__PURE__ */ jsx(PublishedCheckIcon, {}),
22501
+ "Publisert"
22502
+ ]
22503
+ }
22504
+ ) : null,
22505
+ showToolbarProgress && deploymentRow ? /* @__PURE__ */ jsx(
22506
+ PublishProgressBar,
22507
+ {
22508
+ status: deploymentRow.status,
22509
+ compact: true,
22510
+ href: `${SETTO_BASE}?deployment=${encodeURIComponent(activeDeployment)}`
22511
+ }
22512
+ ) : null,
22513
+ !hasDrafts && !error && !showPublishedNotice && !showToolbarProgress && !publishing ? /* @__PURE__ */ jsx("span", { style: emptyStateStyle, children: "Ingen endringer" }) : null,
22514
+ /* @__PURE__ */ jsxs("div", { ref: menuRef, style: { position: "relative" }, children: [
22515
+ /* @__PURE__ */ jsx(
22516
+ "button",
22517
+ {
22518
+ type: "button",
22519
+ onClick: () => setMenuOpen((o) => !o),
22520
+ "aria-label": "Meny",
22521
+ "aria-expanded": menuOpen,
22522
+ style: {
22523
+ ...menuBtnStyle,
22524
+ background: menuOpen ? "rgba(242, 235, 223, 0.16)" : "transparent"
22525
+ },
22526
+ onMouseEnter: (e) => {
22527
+ e.currentTarget.style.background = "rgba(242, 235, 223, 0.16)";
22528
+ },
22529
+ onMouseLeave: (e) => {
22530
+ e.currentTarget.style.background = menuOpen ? "rgba(242, 235, 223, 0.16)" : "transparent";
22531
+ },
22532
+ children: "⋮"
22533
+ }
22534
+ ),
22535
+ menuOpen ? /* @__PURE__ */ jsxs("div", { style: dropdownStyle, role: "menu", children: [
22536
+ /* @__PURE__ */ jsx(
22537
+ MenuItem,
22538
+ {
22539
+ onClick: cancelDrafts,
22540
+ icon: /* @__PURE__ */ jsx(UndoIcon, {}),
22541
+ disabled: !hasDrafts,
22542
+ children: "Avbryt"
22543
+ }
22544
+ ),
22545
+ /* @__PURE__ */ jsx(MenuItem, { onClick: goToHistory, icon: /* @__PURE__ */ jsx(HistoryIcon, {}), children: "Historikk" }),
22546
+ /* @__PURE__ */ jsx(MenuItem, { onClick: signOut, icon: /* @__PURE__ */ jsx(LogOutIcon, {}), children: "Logg ut" })
22547
+ ] }) : null
22548
+ ] })
22427
22549
  ] }) : null
22428
- ] })
22429
- ] })
22430
- ] }),
22550
+ ]
22551
+ }
22552
+ ),
22431
22553
  /* @__PURE__ */ jsx(EditOnboardingDialog, {}),
22432
22554
  publishDialogOpen && (publishing || activeDeployment !== null) ? /* @__PURE__ */ jsx(
22433
22555
  PublishDialog,
@@ -22442,82 +22564,112 @@ function EditToolbar() {
22442
22564
  ) : null
22443
22565
  ] });
22444
22566
  }
22445
- const barStyle = {
22446
- height: "100%",
22447
- display: "flex",
22448
- alignItems: "center",
22449
- justifyContent: "space-between",
22450
- gap: 12,
22451
- padding: "0 12px",
22452
- background: "#ffffff",
22453
- color: "#1a1a1a",
22454
- fontFamily: 'system-ui, -apple-system, "Segoe UI", sans-serif',
22455
- fontSize: 13,
22456
- borderBottom: "1px solid #e8e8e8",
22457
- boxShadow: "0 1px 8px rgba(0, 0, 0, 0.06)"
22567
+ const EASE_OUT_EXPO = "cubic-bezier(0.16, 1, 0.3, 1)";
22568
+ const FAB_SHADOW = "0 8px 24px rgba(33, 23, 17, 0.35), 0 2px 6px rgba(33, 23, 17, 0.2)";
22569
+ function containerStyleFor(hasDrafts) {
22570
+ return {
22571
+ display: "inline-flex",
22572
+ alignItems: "center",
22573
+ height: FAB_SIZE,
22574
+ width: FAB_SIZE,
22575
+ padding: 0,
22576
+ background: BLEKK,
22577
+ color: CREMA,
22578
+ borderRadius: FAB_SIZE / 2,
22579
+ boxShadow: hasDrafts ? `0 0 0 2px ${TERRACOTTA}, ${FAB_SHADOW}` : FAB_SHADOW,
22580
+ fontFamily: 'system-ui, -apple-system, "Segoe UI", sans-serif',
22581
+ fontSize: 13,
22582
+ overflow: "visible",
22583
+ transition: `width 500ms ${EASE_OUT_EXPO}, padding 500ms ${EASE_OUT_EXPO}, box-shadow 300ms ${EASE_OUT_EXPO}`
22584
+ };
22585
+ }
22586
+ const expandedContainerStyle = {
22587
+ width: "auto",
22588
+ paddingRight: 10
22458
22589
  };
22459
- const leftStyle = {
22460
- display: "flex",
22590
+ const markButtonStyle = {
22591
+ display: "inline-flex",
22461
22592
  alignItems: "center",
22462
- minWidth: 0
22593
+ justifyContent: "center",
22594
+ flexShrink: 0,
22595
+ width: FAB_SIZE,
22596
+ height: FAB_SIZE,
22597
+ background: "transparent",
22598
+ border: "none",
22599
+ padding: 0,
22600
+ cursor: "pointer",
22601
+ color: "inherit"
22463
22602
  };
22464
- const rightStyle = {
22603
+ const contentStyle = {
22465
22604
  display: "flex",
22466
22605
  alignItems: "center",
22467
- gap: 8,
22468
- flexShrink: 0
22606
+ gap: 6,
22607
+ paddingLeft: 4,
22608
+ whiteSpace: "nowrap"
22469
22609
  };
22470
- const draftStyle = {
22471
- color: "#666",
22610
+ const errorStyle = {
22611
+ color: "#ffb4a8",
22472
22612
  fontSize: 12,
22473
- whiteSpace: "nowrap"
22613
+ maxWidth: 240,
22614
+ overflow: "hidden",
22615
+ textOverflow: "ellipsis"
22474
22616
  };
22475
- const errorStyle = { color: "#dc2626", fontSize: 12 };
22476
22617
  const publishedNoticeStyle = {
22477
22618
  display: "inline-flex",
22478
22619
  alignItems: "center",
22479
22620
  gap: 5,
22480
22621
  padding: "4px 10px",
22481
- borderRadius: 6,
22482
- border: "1px solid #bbf7d0",
22483
- background: "#f0fdf4",
22484
- color: "#15803d",
22622
+ borderRadius: 999,
22623
+ border: "1px solid rgba(242, 235, 223, 0.25)",
22624
+ background: "rgba(242, 235, 223, 0.08)",
22625
+ color: CREMA,
22485
22626
  fontSize: 12,
22486
22627
  fontWeight: 500,
22487
22628
  cursor: "pointer",
22488
22629
  whiteSpace: "nowrap"
22489
22630
  };
22490
22631
  const publishBtnStyle = {
22491
- background: "#640AFF",
22492
- color: "#fff",
22632
+ background: TERRACOTTA,
22633
+ color: CREMA,
22493
22634
  border: "none",
22494
- borderRadius: 6,
22635
+ borderRadius: 999,
22495
22636
  padding: "6px 14px",
22496
22637
  fontSize: 13,
22497
- fontWeight: 500,
22638
+ fontWeight: 600,
22498
22639
  cursor: "pointer",
22499
22640
  whiteSpace: "nowrap"
22500
22641
  };
22501
22642
  const menuBtnStyle = {
22502
22643
  background: "transparent",
22503
- color: "#666",
22644
+ color: CREMA,
22504
22645
  border: "none",
22505
- borderRadius: 6,
22506
- padding: "4px 8px",
22507
- fontSize: 20,
22646
+ borderRadius: 999,
22647
+ width: 32,
22648
+ height: 32,
22649
+ display: "inline-flex",
22650
+ alignItems: "center",
22651
+ justifyContent: "center",
22652
+ fontSize: 22,
22508
22653
  lineHeight: 1,
22509
- cursor: "pointer"
22654
+ cursor: "pointer",
22655
+ fontWeight: 700
22656
+ };
22657
+ const emptyStateStyle = {
22658
+ color: "rgba(242, 235, 223, 0.55)",
22659
+ fontSize: 13,
22660
+ whiteSpace: "nowrap",
22661
+ padding: "0 4px"
22510
22662
  };
22511
22663
  const dropdownStyle = {
22512
22664
  position: "absolute",
22513
- top: "100%",
22514
- right: 0,
22515
- marginTop: 4,
22665
+ bottom: "100%",
22666
+ left: 0,
22667
+ marginBottom: 8,
22516
22668
  background: "#fff",
22517
22669
  border: "1px solid #e8e8e8",
22518
- borderRadius: 8,
22519
- boxShadow: "0 4px 16px rgba(0, 0, 0, 0.12)",
22520
- minWidth: 140,
22670
+ borderRadius: 10,
22671
+ boxShadow: "0 12px 32px rgba(33, 23, 17, 0.18)",
22672
+ minWidth: 160,
22521
22673
  overflow: "hidden",
22522
22674
  zIndex: 1
22523
22675
  };
@@ -22537,17 +22689,19 @@ const menuItemStyle = {
22537
22689
  function MenuItem({
22538
22690
  onClick,
22539
22691
  icon,
22540
- children
22692
+ children,
22693
+ disabled = false
22541
22694
  }) {
22542
22695
  return /* @__PURE__ */ jsxs(
22543
22696
  "button",
22544
22697
  {
22545
22698
  type: "button",
22546
22699
  onClick,
22547
- style: menuItemStyle,
22700
+ disabled,
22701
+ style: { ...menuItemStyle, opacity: disabled ? 0.4 : 1, cursor: disabled ? "not-allowed" : "pointer" },
22548
22702
  role: "menuitem",
22549
22703
  onMouseEnter: (e) => {
22550
- e.currentTarget.style.background = "#f5f5f5";
22704
+ if (!disabled) e.currentTarget.style.background = "#f5f5f5";
22551
22705
  },
22552
22706
  onMouseLeave: (e) => {
22553
22707
  e.currentTarget.style.background = "transparent";
@@ -22596,6 +22750,12 @@ function LogOutIcon() {
22596
22750
  /* @__PURE__ */ jsx("line", { x1: "21", y1: "12", x2: "9", y2: "12" })
22597
22751
  ] });
22598
22752
  }
22753
+ function UndoIcon() {
22754
+ return /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
22755
+ /* @__PURE__ */ jsx("path", { d: "M3 7v6h6" }),
22756
+ /* @__PURE__ */ jsx("path", { d: "M21 17a9 9 0 0 0-15-6.7L3 13" })
22757
+ ] });
22758
+ }
22599
22759
  function EditModeShell({ children, themeStore }) {
22600
22760
  useSettoDocumentLayout(true);
22601
22761
  const [toolbarRoot, setToolbarRoot] = useState(null);
@@ -22635,7 +22795,9 @@ function SettoProvider({ config, children }) {
22635
22795
  const api = useMemo(() => createApi({ apiUrl: config.apiUrl, supabase }), [config.apiUrl, supabase]);
22636
22796
  const [session, setSession] = useState(null);
22637
22797
  const [authLoading, setAuthLoading] = useState(true);
22638
- const [editParam, setEditParam] = useState(false);
22798
+ const [onAdminRoute, setOnAdminRoute] = useState(
22799
+ () => typeof window === "undefined" ? false : isAdminRoute()
22800
+ );
22639
22801
  useEffect(() => {
22640
22802
  let cancelled = false;
22641
22803
  supabase.auth.getSession().then(({ data }) => {
@@ -22652,18 +22814,12 @@ function SettoProvider({ config, children }) {
22652
22814
  };
22653
22815
  }, [supabase]);
22654
22816
  useEffect(() => {
22655
- const compute = () => {
22656
- try {
22657
- const params = new URLSearchParams(window.location.search);
22658
- setEditParam(params.get("setto") === "edit");
22659
- } catch {
22660
- setEditParam(false);
22661
- }
22662
- };
22817
+ const compute = () => setOnAdminRoute(isAdminRoute());
22663
22818
  compute();
22664
22819
  window.addEventListener("popstate", compute);
22665
22820
  return () => window.removeEventListener("popstate", compute);
22666
22821
  }, []);
22822
+ const access = useSiteAccess(supabase, session, config.siteId);
22667
22823
  const { i18n } = useTranslation();
22668
22824
  const storeRef = useRef(null);
22669
22825
  const themeStoreRef = useRef(null);
@@ -22676,7 +22832,7 @@ function SettoProvider({ config, children }) {
22676
22832
  assetStoreRef.current = new AssetStore();
22677
22833
  setStoreReady(true);
22678
22834
  }, [i18n, config.theme]);
22679
- const editMode = !!session && editParam;
22835
+ const editMode = !!session && !!access.currentSite && !onAdminRoute && !authLoading && !access.loading;
22680
22836
  const value = {
22681
22837
  config,
22682
22838
  supabase,
@@ -22684,6 +22840,8 @@ function SettoProvider({ config, children }) {
22684
22840
  session,
22685
22841
  authLoading,
22686
22842
  editMode,
22843
+ currentSite: access.currentSite,
22844
+ sites: access.sites,
22687
22845
  store: storeReady ? storeRef.current : null,
22688
22846
  themeStore: storeReady ? themeStoreRef.current : null,
22689
22847
  assetStore: storeReady ? assetStoreRef.current : null
@@ -22699,7 +22857,8 @@ function useSetto() {
22699
22857
  }
22700
22858
  const GuestEditContext = createContext(null);
22701
22859
  function GuestEditProvider({ children }) {
22702
- const { editMode } = useSetto();
22860
+ const { editMode, themeStore } = useSetto();
22861
+ const { selectSection } = useSectionEdit();
22703
22862
  const [active, setActive] = useState(false);
22704
22863
  useEffect(() => {
22705
22864
  if (editMode && active) setActive(false);
@@ -22709,8 +22868,14 @@ function GuestEditProvider({ children }) {
22709
22868
  return () => document.documentElement.classList.remove("setto-guest-editing");
22710
22869
  }, [active]);
22711
22870
  const start = useCallback(() => setActive(true), []);
22712
- const stop = useCallback(() => setActive(false), []);
22713
- return /* @__PURE__ */ jsx(GuestEditContext.Provider, { value: { active, start, stop }, children });
22871
+ const stop = useCallback(() => {
22872
+ setActive(false);
22873
+ selectSection(null);
22874
+ }, [selectSection]);
22875
+ return /* @__PURE__ */ jsxs(GuestEditContext.Provider, { value: { active, start, stop }, children: [
22876
+ children,
22877
+ active && themeStore ? createPortal(/* @__PURE__ */ jsx(SectionToolbar, {}), document.body) : null
22878
+ ] });
22714
22879
  }
22715
22880
  function useGuestEdit() {
22716
22881
  const ctx = useContext(GuestEditContext);
@@ -22852,9 +23017,9 @@ function T({ k }) {
22852
23017
  const value = store ? store.get(k, i18n.language) : t(k);
22853
23018
  useEffect(() => {
22854
23019
  const el = ref.current;
22855
- if (!el || focused || !editMode) return;
23020
+ if (!el || focused || !editable) return;
22856
23021
  if (el.textContent !== value) el.textContent = value;
22857
- }, [value, focused, editMode]);
23022
+ }, [value, focused, editable]);
22858
23023
  if (!editable) return /* @__PURE__ */ jsx(Fragment, { children: value });
22859
23024
  const handleMouseDown = (e) => {
22860
23025
  e.stopPropagation();
@@ -22901,7 +23066,6 @@ function T({ k }) {
22901
23066
  commit(e.currentTarget);
22902
23067
  };
22903
23068
  const handleInput = (e) => {
22904
- if (!editMode) return;
22905
23069
  store?.set(k, i18n.language, e.currentTarget.textContent ?? "");
22906
23070
  };
22907
23071
  const handlePaste = (e) => {
@@ -22924,7 +23088,7 @@ function T({ k }) {
22924
23088
  contentEditable: true,
22925
23089
  suppressContentEditableWarning: true,
22926
23090
  lang,
22927
- spellCheck: true,
23091
+ spellCheck: false,
22928
23092
  role: "textbox",
22929
23093
  tabIndex: 0,
22930
23094
  onFocus: (e) => {
@@ -22977,9 +23141,9 @@ function isNestedThemeTarget(target, container) {
22977
23141
  const hit = target.closest("[data-setto-section]");
22978
23142
  return !!(hit && hit !== container);
22979
23143
  }
22980
- function handleThemeTargetClick(e, editMode, themeId, selected, selectSection, onClick) {
23144
+ function handleThemeTargetClick(e, editable, themeId, selected, selectSection, onClick) {
22981
23145
  onClick?.(e);
22982
- if (!editMode || e.defaultPrevented) return;
23146
+ if (!editable || e.defaultPrevented) return;
22983
23147
  const target = e.target;
22984
23148
  if (isTextEditClick(target)) return;
22985
23149
  if (target.closest("a[href], button, input, textarea, select")) return;
@@ -22987,11 +23151,11 @@ function handleThemeTargetClick(e, editMode, themeId, selected, selectSection, o
22987
23151
  e.stopPropagation();
22988
23152
  selectSection(selected ? null : themeId);
22989
23153
  }
22990
- function themeTargetEditStyle(editMode, selected) {
22991
- if (!editMode) return {};
23154
+ function themeTargetEditStyle(editable, selected, accent = "#640AFF") {
23155
+ if (!editable) return {};
22992
23156
  return {
22993
23157
  cursor: "pointer",
22994
- outline: selected ? "2px solid #640AFF" : void 0,
23158
+ outline: selected ? `2px solid ${accent}` : void 0,
22995
23159
  outlineOffset: selected ? -2 : void 0
22996
23160
  };
22997
23161
  }
@@ -23009,23 +23173,26 @@ function useSectionTheme(sectionId) {
23009
23173
  const SettoSection = forwardRef(
23010
23174
  function SettoSection2({ sectionId, className, style, children, onClick, ...rest }, ref) {
23011
23175
  const { editMode } = useSetto();
23176
+ const guest = useGuestEdit();
23177
+ const editable = editMode || guest.active;
23178
+ const accent = editMode ? "#640AFF" : "#C4502A";
23012
23179
  const { selectedId, selectSection } = useSectionEdit();
23013
23180
  const colors = useSectionTheme(sectionId);
23014
23181
  const selected = selectedId === sectionId;
23015
23182
  const handleClick = useCallback(
23016
23183
  (e) => {
23017
- if (editMode) {
23184
+ if (editable) {
23018
23185
  const target = e.target;
23019
23186
  if (isNestedThemeTarget(target, e.currentTarget)) return;
23020
23187
  }
23021
- handleThemeTargetClick(e, editMode, sectionId, selected, selectSection, onClick);
23188
+ handleThemeTargetClick(e, editable, sectionId, selected, selectSection, onClick);
23022
23189
  },
23023
- [editMode, onClick, selectSection, selected, sectionId]
23190
+ [editable, onClick, selectSection, selected, sectionId]
23024
23191
  );
23025
23192
  const mergedStyle = {
23026
23193
  ...style,
23027
23194
  ...colors.background ? { backgroundColor: colors.background } : {},
23028
- ...themeTargetEditStyle(editMode, selected)
23195
+ ...themeTargetEditStyle(editable, selected, accent)
23029
23196
  };
23030
23197
  return /* @__PURE__ */ jsx(
23031
23198
  "section",
@@ -23044,6 +23211,9 @@ const SettoSection = forwardRef(
23044
23211
  const SettoBlock = forwardRef(
23045
23212
  function SettoBlock2({ blockId, className, style, children, onClick, ...rest }, ref) {
23046
23213
  const { editMode } = useSetto();
23214
+ const guest = useGuestEdit();
23215
+ const editable = editMode || guest.active;
23216
+ const accent = editMode ? "#640AFF" : "#C4502A";
23047
23217
  const { selectedId, selectSection } = useSectionEdit();
23048
23218
  const colors = useSectionTheme(blockId);
23049
23219
  const selected = selectedId === blockId;
@@ -23051,19 +23221,19 @@ const SettoBlock = forwardRef(
23051
23221
  (e) => {
23052
23222
  handleThemeTargetClick(
23053
23223
  e,
23054
- editMode,
23224
+ editable,
23055
23225
  blockId,
23056
23226
  selected,
23057
23227
  selectSection,
23058
23228
  onClick
23059
23229
  );
23060
23230
  },
23061
- [editMode, blockId, selected, selectSection, onClick]
23231
+ [editable, blockId, selected, selectSection, onClick]
23062
23232
  );
23063
23233
  const mergedStyle = {
23064
23234
  ...style,
23065
23235
  ...colors.background ? { backgroundColor: colors.background } : {},
23066
- ...themeTargetEditStyle(editMode, selected)
23236
+ ...themeTargetEditStyle(editable, selected, accent)
23067
23237
  };
23068
23238
  return /* @__PURE__ */ jsx(
23069
23239
  "div",
@@ -23111,7 +23281,7 @@ function FloatingPopover({
23111
23281
  if (top + popoverH > window.innerHeight - 8) {
23112
23282
  top = rect.top - popoverH - 6;
23113
23283
  }
23114
- if (top < TOOLBAR_HEIGHT + 4) top = rect.bottom + 6;
23284
+ if (top < 8) top = rect.bottom + 6;
23115
23285
  setPos({ top, left });
23116
23286
  };
23117
23287
  update();
@@ -23175,6 +23345,9 @@ function FloatingPopover({
23175
23345
  function SettoIcon({ k, icons, size = 24, className, style }) {
23176
23346
  const { t, i18n } = useTranslation();
23177
23347
  const { editMode, store } = useSetto();
23348
+ const guest = useGuestEdit();
23349
+ const editable = editMode || guest.active;
23350
+ const accent = editMode ? "#640AFF" : "#C4502A";
23178
23351
  const anchorRef = useRef(null);
23179
23352
  const [open, setOpen] = useState(false);
23180
23353
  const [search, setSearch] = useState("");
@@ -23186,7 +23359,7 @@ function SettoIcon({ k, icons, size = 24, className, style }) {
23186
23359
  const iconName = store ? store.get(k, i18n.language) : t(k);
23187
23360
  const Icon = icons[iconName] ?? icons[Object.keys(icons)[0] ?? ""] ?? null;
23188
23361
  if (!Icon) return null;
23189
- if (!editMode) {
23362
+ if (!editable) {
23190
23363
  return /* @__PURE__ */ jsx(Icon, { size, className, style });
23191
23364
  }
23192
23365
  const handleClick = (e) => {
@@ -23217,7 +23390,7 @@ function SettoIcon({ k, icons, size = 24, className, style }) {
23217
23390
  style: {
23218
23391
  display: "inline-flex",
23219
23392
  cursor: "pointer",
23220
- outline: open ? "2px solid #640AFF" : void 0,
23393
+ outline: open ? `2px solid ${accent}` : void 0,
23221
23394
  outlineOffset: 2,
23222
23395
  borderRadius: 4
23223
23396
  },
@@ -23484,6 +23657,28 @@ function SettoImage({ srcKey, alt, className, style, ...rest }) {
23484
23657
  )
23485
23658
  ] });
23486
23659
  }
23660
+ const STYLE_ID = "setto-repeater-styles";
23661
+ function useRepeaterStyles() {
23662
+ useEffect(() => {
23663
+ if (document.getElementById(STYLE_ID)) return;
23664
+ const el = document.createElement("style");
23665
+ el.id = STYLE_ID;
23666
+ el.textContent = `
23667
+ .setto-repeater-remove {
23668
+ opacity: 0;
23669
+ transition: opacity 120ms ease;
23670
+ }
23671
+ .setto-repeater-item:hover .setto-repeater-remove,
23672
+ .setto-repeater-remove:focus-visible {
23673
+ opacity: 1;
23674
+ }
23675
+ @media (hover: none) {
23676
+ .setto-repeater-remove { opacity: 1; }
23677
+ }
23678
+ `;
23679
+ document.head.appendChild(el);
23680
+ }, []);
23681
+ }
23487
23682
  function SettoRepeater({
23488
23683
  itemsKey,
23489
23684
  defaultItem,
@@ -23493,7 +23688,10 @@ function SettoRepeater({
23493
23688
  }) {
23494
23689
  const { i18n } = useTranslation();
23495
23690
  const { editMode, store } = useSetto();
23691
+ const guest = useGuestEdit();
23692
+ const editable = editMode || guest.active;
23496
23693
  const lng = i18n.language;
23694
+ useRepeaterStyles();
23497
23695
  const [, force] = useState(0);
23498
23696
  useEffect(() => {
23499
23697
  if (!store) return;
@@ -23507,16 +23705,35 @@ function SettoRepeater({
23507
23705
  if (keys.length <= 1) return;
23508
23706
  store?.removeListItem(itemsKey, itemKey, lng);
23509
23707
  };
23708
+ const canRemove = keys.length > 1;
23510
23709
  return /* @__PURE__ */ jsxs("div", { className, "data-setto-repeater": true, children: [
23511
- editMode ? /* @__PURE__ */ jsx(
23710
+ keys.map((itemKey, index) => /* @__PURE__ */ jsxs("div", { className: "setto-repeater-item", style: { position: "relative" }, children: [
23711
+ editable && canRemove ? /* @__PURE__ */ jsx(
23712
+ "button",
23713
+ {
23714
+ type: "button",
23715
+ "data-setto-ui": true,
23716
+ className: "setto-repeater-remove",
23717
+ "aria-label": `Fjern ${itemLabel}`,
23718
+ title: `Fjern ${itemLabel}`,
23719
+ onClick: (e) => {
23720
+ e.stopPropagation();
23721
+ handleRemove(itemKey);
23722
+ },
23723
+ style: removeBtnStyle,
23724
+ children: "×"
23725
+ }
23726
+ ) : null,
23727
+ children(itemKey, index)
23728
+ ] }, itemKey)),
23729
+ editable ? /* @__PURE__ */ jsx(
23512
23730
  "div",
23513
23731
  {
23514
23732
  "data-setto-ui": true,
23515
23733
  style: {
23516
23734
  display: "flex",
23517
23735
  justifyContent: "flex-end",
23518
- marginBottom: 8,
23519
- gap: 8
23736
+ marginTop: 8
23520
23737
  },
23521
23738
  children: /* @__PURE__ */ jsxs(
23522
23739
  "button",
@@ -23527,7 +23744,7 @@ function SettoRepeater({
23527
23744
  e.stopPropagation();
23528
23745
  handleAdd();
23529
23746
  },
23530
- style: controlBtnStyle,
23747
+ style: addBtnStyle,
23531
23748
  children: [
23532
23749
  "+ Legg til ",
23533
23750
  itemLabel
@@ -23535,37 +23752,10 @@ function SettoRepeater({
23535
23752
  }
23536
23753
  )
23537
23754
  }
23538
- ) : null,
23539
- keys.map((itemKey, index) => /* @__PURE__ */ jsxs("div", { className: "setto-repeater-item", style: { position: "relative" }, children: [
23540
- editMode ? /* @__PURE__ */ jsx(
23541
- "button",
23542
- {
23543
- type: "button",
23544
- "data-setto-ui": true,
23545
- "aria-label": `Fjern ${itemLabel}`,
23546
- disabled: keys.length <= 1,
23547
- onClick: (e) => {
23548
- e.stopPropagation();
23549
- handleRemove(itemKey);
23550
- },
23551
- style: {
23552
- ...controlBtnStyle,
23553
- position: "absolute",
23554
- top: 0,
23555
- right: 0,
23556
- zIndex: 2,
23557
- padding: "4px 8px",
23558
- fontSize: 11,
23559
- opacity: keys.length <= 1 ? 0.4 : 1
23560
- },
23561
- children: "× Fjern"
23562
- }
23563
- ) : null,
23564
- children(itemKey, index)
23565
- ] }, itemKey))
23755
+ ) : null
23566
23756
  ] });
23567
23757
  }
23568
- const controlBtnStyle = {
23758
+ const addBtnStyle = {
23569
23759
  padding: "6px 10px",
23570
23760
  background: "#f7f7f7",
23571
23761
  border: "1px solid #e0e0e0",
@@ -23575,22 +23765,27 @@ const controlBtnStyle = {
23575
23765
  cursor: "pointer",
23576
23766
  fontFamily: 'system-ui, -apple-system, "Segoe UI", sans-serif'
23577
23767
  };
23578
- function editModeUrl(pathname = "/") {
23579
- const u = new URL(window.location.href);
23580
- u.pathname = pathname;
23581
- u.search = "";
23582
- u.searchParams.set("setto", "edit");
23583
- return u.toString();
23584
- }
23585
- function isAdminRoute() {
23586
- return window.location.pathname.startsWith("/admin");
23587
- }
23588
- function isAdminDeploymentView() {
23589
- return isAdminRoute() && new URLSearchParams(window.location.search).has("deployment");
23590
- }
23591
- function adminRedirectUrl() {
23592
- return `${window.location.origin}/admin`;
23593
- }
23768
+ const removeBtnStyle = {
23769
+ position: "absolute",
23770
+ top: 4,
23771
+ right: 4,
23772
+ zIndex: 2,
23773
+ display: "flex",
23774
+ alignItems: "center",
23775
+ justifyContent: "center",
23776
+ width: 24,
23777
+ height: 24,
23778
+ padding: 0,
23779
+ background: "#ffffff",
23780
+ border: "1px solid #e0e0e0",
23781
+ borderRadius: "50%",
23782
+ fontSize: 16,
23783
+ lineHeight: 1,
23784
+ color: "#444",
23785
+ cursor: "pointer",
23786
+ boxShadow: "0 1px 4px rgba(0,0,0,0.18)",
23787
+ fontFamily: 'system-ui, -apple-system, "Segoe UI", sans-serif'
23788
+ };
23594
23789
  function authCallbackType() {
23595
23790
  const hash = window.location.hash.replace(/^#/, "");
23596
23791
  if (!hash) return null;
@@ -23693,7 +23888,7 @@ function AuthGate({ children }) {
23693
23888
  clearAuthHashFromUrl();
23694
23889
  const { data: sessionData } = await supabase.auth.getSession();
23695
23890
  if (sessionData.session) {
23696
- window.location.replace(editModeUrl());
23891
+ window.location.replace(siteHomeUrl());
23697
23892
  return;
23698
23893
  }
23699
23894
  setMode("signin");
@@ -23871,7 +24066,7 @@ const loadingStyle = {
23871
24066
  };
23872
24067
  function deploymentAdminUrl(deploymentId) {
23873
24068
  const u = new URL(window.location.href);
23874
- u.pathname = "/admin";
24069
+ u.pathname = SETTO_BASE;
23875
24070
  u.search = "";
23876
24071
  u.searchParams.set("deployment", deploymentId);
23877
24072
  return u.pathname + u.search;
@@ -23883,8 +24078,7 @@ function SettoAdminApp() {
23883
24078
  return /* @__PURE__ */ jsx(AuthGate, { children: /* @__PURE__ */ jsx(SiteList, {}) });
23884
24079
  }
23885
24080
  function SiteList() {
23886
- const { supabase, session, config } = useSetto();
23887
- const [sites, setSites] = useState(null);
24081
+ const { supabase, session, sites, currentSite, config } = useSetto();
23888
24082
  const [error, setError] = useState(null);
23889
24083
  const redirectAfterSignIn = useRef(false);
23890
24084
  useEffect(() => {
@@ -23895,33 +24089,13 @@ function SiteList() {
23895
24089
  });
23896
24090
  return () => sub.subscription.unsubscribe();
23897
24091
  }, [supabase]);
23898
- useEffect(() => {
23899
- if (!session) return;
23900
- let cancelled = false;
23901
- supabase.from("sites").select("*").then(({ data, error: err }) => {
23902
- if (cancelled) return;
23903
- if (err) {
23904
- setError(err.message);
23905
- setSites([]);
23906
- return;
23907
- }
23908
- setSites(data ?? []);
23909
- });
23910
- return () => {
23911
- cancelled = true;
23912
- };
23913
- }, [supabase, session]);
23914
- const currentSite = useMemo(
23915
- () => sites?.find((s) => s.id === config.siteId) ?? null,
23916
- [sites, config.siteId]
23917
- );
23918
24092
  useEffect(() => {
23919
24093
  if (!redirectAfterSignIn.current || !session || sites === null || isAdminDeploymentView()) {
23920
24094
  return;
23921
24095
  }
23922
24096
  if (currentSite) {
23923
24097
  redirectAfterSignIn.current = false;
23924
- window.location.replace(editModeUrl());
24098
+ window.location.replace(siteHomeUrl());
23925
24099
  return;
23926
24100
  }
23927
24101
  redirectAfterSignIn.current = false;
@@ -23946,8 +24120,8 @@ function SiteList() {
23946
24120
  " · branch ",
23947
24121
  currentSite.branch
23948
24122
  ] }),
23949
- /* @__PURE__ */ jsx("div", { style: { display: "flex", gap: 8 }, children: /* @__PURE__ */ jsx("a", { href: editUrl(), style: primaryBtnLinkStyle, children: "Begynn å redigere" }) }),
23950
- /* @__PURE__ */ jsx(DeploymentList, { siteId: currentSite.id })
24123
+ /* @__PURE__ */ jsx("div", { style: { display: "flex", gap: 8 }, children: /* @__PURE__ */ jsx("a", { href: siteHomeUrl(), style: primaryBtnLinkStyle, children: "Begynn å redigere" }) }),
24124
+ /* @__PURE__ */ jsx(DeploymentList, { siteId: currentSite.id, onError: setError })
23951
24125
  ] }) : sites && sites.length === 0 ? /* @__PURE__ */ jsx("p", { style: mutedStyle, children: "Du er ikke medlem av noen sider ennå. Be administratoren legge deg til." }) : null,
23952
24126
  sites && !currentSite && sites.length > 0 ? /* @__PURE__ */ jsxs("section", { style: cardStyle, children: [
23953
24127
  /* @__PURE__ */ jsx("h2", { style: { margin: 0, fontSize: 16 }, children: "Andre sider du har tilgang til" }),
@@ -23965,7 +24139,10 @@ function SiteList() {
23965
24139
  ] })
23966
24140
  ] });
23967
24141
  }
23968
- function DeploymentList({ siteId }) {
24142
+ function DeploymentList({
24143
+ siteId,
24144
+ onError
24145
+ }) {
23969
24146
  const { supabase, api, config } = useSetto();
23970
24147
  const [rows, setRows] = useState(null);
23971
24148
  const [focusId, setFocusId] = useState(focusedDeploymentId);
@@ -23984,7 +24161,7 @@ function DeploymentList({ siteId }) {
23984
24161
  try {
23985
24162
  await api.cancelDeployment(config.siteId, focusId);
23986
24163
  setFocusId(null);
23987
- window.history.replaceState(null, "", "/admin");
24164
+ window.history.replaceState(null, "", SETTO_BASE);
23988
24165
  } catch (err) {
23989
24166
  setCancelError(err instanceof Error ? err.message : "Kunne ikke avbryte");
23990
24167
  } finally {
@@ -23997,8 +24174,10 @@ function DeploymentList({ siteId }) {
23997
24174
  }, [api, config.siteId]);
23998
24175
  useEffect(() => {
23999
24176
  let cancelled = false;
24000
- supabase.from("deployments").select("*").eq("site_id", siteId).order("started_at", { ascending: false }).limit(10).then(({ data }) => {
24001
- if (!cancelled) setRows(data ?? []);
24177
+ supabase.from("deployments").select("*").eq("site_id", siteId).order("started_at", { ascending: false }).limit(10).then(({ data, error }) => {
24178
+ if (cancelled) return;
24179
+ if (error) onError?.(error.message);
24180
+ setRows(data ?? []);
24002
24181
  });
24003
24182
  const channel = supabase.channel(`setto-site-deps-${siteId}`).on(
24004
24183
  "postgres_changes",
@@ -24017,7 +24196,7 @@ function DeploymentList({ siteId }) {
24017
24196
  cancelled = true;
24018
24197
  supabase.removeChannel(channel);
24019
24198
  };
24020
- }, [supabase, siteId]);
24199
+ }, [supabase, siteId, onError]);
24021
24200
  const showFocusPanel = focusId !== null && (focusRow === null || isDeploymentInProgress(focusRow.status));
24022
24201
  if (!rows) return /* @__PURE__ */ jsx("p", { style: mutedStyle, children: "Laster deploys…" });
24023
24202
  return /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 12 }, children: [
@@ -24102,9 +24281,6 @@ function SignOutButton() {
24102
24281
  const { supabase } = useSetto();
24103
24282
  return /* @__PURE__ */ jsx("button", { onClick: () => supabase.auth.signOut(), style: signOutBtnStyle, children: "Logg ut" });
24104
24283
  }
24105
- function editUrl() {
24106
- return editModeUrl("/");
24107
- }
24108
24284
  const loadingRedirectStyle = {
24109
24285
  minHeight: "100dvh",
24110
24286
  display: "flex",
@@ -24213,6 +24389,7 @@ function depDotStyle(status) {
24213
24389
  export {
24214
24390
  AuthGate,
24215
24391
  GuestEditProvider,
24392
+ SETTO_BASE,
24216
24393
  SettoAdminApp,
24217
24394
  SettoBlock,
24218
24395
  SettoIcon,