@sevenfold/setto-client 0.4.0 → 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,14 +21155,63 @@ class ThemeStore {
21146
21155
  for (const fn of this.listeners) fn(this.cachedSnapshot);
21147
21156
  }
21148
21157
  }
21149
- const TOOLBAR_HEIGHT = 44;
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;
21150
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`);
21213
+ html.style.setProperty("--setto-fab-size", `${FAB_SIZE}px`);
21214
+ html.style.setProperty("--setto-fab-inset", `${FAB_INSET}px`);
21157
21215
  let styleEl = document.getElementById(STYLE_ID$1);
21158
21216
  if (!styleEl) {
21159
21217
  styleEl = document.createElement("style");
@@ -21164,23 +21222,10 @@ function useSettoDocumentLayout(active) {
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,23 +22189,43 @@ function useEditBaseline() {
22150
22189
  };
22151
22190
  }, [session, store, config.siteId]);
22152
22191
  }
22153
- const SETTO_BASE = "/setto";
22154
- function editModeUrl(pathname = "/") {
22155
- const u = new URL(window.location.href);
22156
- u.pathname = pathname;
22157
- u.search = "";
22158
- u.searchParams.set("setto", "edit");
22159
- return u.toString();
22160
- }
22161
- function isAdminRoute() {
22162
- return window.location.pathname.startsWith(SETTO_BASE);
22163
- }
22164
- function isAdminDeploymentView() {
22165
- return isAdminRoute() && new URLSearchParams(window.location.search).has("deployment");
22166
- }
22167
- function adminRedirectUrl() {
22168
- return `${window.location.origin}${SETTO_BASE}`;
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
+ );
22169
22225
  }
22226
+ const BLEKK = "#211711";
22227
+ const CREMA = "#F2EBDF";
22228
+ const TERRACOTTA = "#C4502A";
22170
22229
  function EditToolbar() {
22171
22230
  const { store, themeStore, assetStore, api, config, supabase } = useSetto();
22172
22231
  const { i18n } = useTranslation();
@@ -22195,6 +22254,7 @@ function EditToolbar() {
22195
22254
  const [error, setError] = useState(null);
22196
22255
  const [publishedNotice, setPublishedNotice] = useState(false);
22197
22256
  const [menuOpen, setMenuOpen] = useState(false);
22257
+ const [expanded, setExpanded] = useState(false);
22198
22258
  const menuRef = useRef(null);
22199
22259
  const publishedNoticeTimer = useRef(null);
22200
22260
  const { row: deploymentRow, status: deploymentStatus } = useDeploymentStatus(
@@ -22225,6 +22285,18 @@ function EditToolbar() {
22225
22285
  const hasDrafts = draftCount > 0;
22226
22286
  const showToolbarProgress = activeDeployment !== null && !publishDialogOpen && deploymentRow !== null && isDeploymentInProgress(deploymentRow.status) && !isDeploymentStale(deploymentRow);
22227
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
+ };
22228
22300
  useEffect(() => {
22229
22301
  return () => {
22230
22302
  if (publishedNoticeTimer.current) clearTimeout(publishedNoticeTimer.current);
@@ -22375,6 +22447,14 @@ function EditToolbar() {
22375
22447
  setPublishedNotice(false);
22376
22448
  if (publishedNoticeTimer.current) clearTimeout(publishedNoticeTimer.current);
22377
22449
  };
22450
+ const cancelDrafts = () => {
22451
+ setMenuOpen(false);
22452
+ store?.revertAll();
22453
+ themeStore?.revertAll();
22454
+ assetStore?.revertAll();
22455
+ setError(null);
22456
+ setExpanded(false);
22457
+ };
22378
22458
  const signOut = () => {
22379
22459
  setMenuOpen(false);
22380
22460
  void supabase.auth.signOut();
@@ -22384,67 +22464,92 @@ function EditToolbar() {
22384
22464
  window.location.href = SETTO_BASE;
22385
22465
  };
22386
22466
  return /* @__PURE__ */ jsxs(Fragment, { children: [
22387
- /* @__PURE__ */ jsxs("header", { style: barStyle, role: "toolbar", "aria-label": "Setto editor", children: [
22388
- /* @__PURE__ */ jsx("div", { style: leftStyle, children: /* @__PURE__ */ jsx("strong", { style: { fontSize: 13 }, children: "Setto" }) }),
22389
- /* @__PURE__ */ jsxs("div", { style: rightStyle, children: [
22390
- error ? /* @__PURE__ */ jsx("span", { style: errorStyle, children: error }) : null,
22391
- hasDrafts && !publishing && activeDeployment === null ? /* @__PURE__ */ jsxs(Fragment, { children: [
22392
- /* @__PURE__ */ jsxs("span", { style: draftStyle, "data-setto-draft-count": true, children: [
22393
- draftCount,
22394
- " ",
22395
- draftCount === 1 ? "endring" : "endringer"
22396
- ] }),
22397
- /* @__PURE__ */ jsx("button", { type: "button", onClick: publish, style: publishBtnStyle, children: "Publiser" })
22398
- ] }) : null,
22399
- showPublishedNotice ? /* @__PURE__ */ jsxs(
22400
- "button",
22401
- {
22402
- type: "button",
22403
- onClick: dismissPublishedNotice,
22404
- style: publishedNoticeStyle,
22405
- title: "Lukk",
22406
- children: [
22407
- /* @__PURE__ */ jsx(PublishedCheckIcon, {}),
22408
- "Publisert"
22409
- ]
22410
- }
22411
- ) : null,
22412
- showToolbarProgress && deploymentRow ? /* @__PURE__ */ jsx(
22413
- PublishProgressBar,
22414
- {
22415
- status: deploymentRow.status,
22416
- compact: true,
22417
- href: `${SETTO_BASE}?deployment=${encodeURIComponent(activeDeployment)}`
22418
- }
22419
- ) : null,
22420
- /* @__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: [
22421
22477
  /* @__PURE__ */ jsx(
22422
22478
  "button",
22423
22479
  {
22424
22480
  type: "button",
22425
- onClick: () => setMenuOpen((o) => !o),
22426
- "aria-label": "Meny",
22427
- "aria-expanded": menuOpen,
22428
- style: {
22429
- ...menuBtnStyle,
22430
- background: menuOpen ? "#f0f0f0" : void 0
22431
- },
22432
- onMouseEnter: (e) => {
22433
- e.currentTarget.style.background = "#f0f0f0";
22434
- },
22435
- onMouseLeave: (e) => {
22436
- e.currentTarget.style.background = menuOpen ? "#f0f0f0" : "transparent";
22437
- },
22438
- 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 })
22439
22487
  }
22440
22488
  ),
22441
- menuOpen ? /* @__PURE__ */ jsxs("div", { style: dropdownStyle, role: "menu", children: [
22442
- /* @__PURE__ */ jsx(MenuItem, { onClick: goToHistory, icon: /* @__PURE__ */ jsx(HistoryIcon, {}), children: "Historikk" }),
22443
- /* @__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
+ ] })
22444
22549
  ] }) : null
22445
- ] })
22446
- ] })
22447
- ] }),
22550
+ ]
22551
+ }
22552
+ ),
22448
22553
  /* @__PURE__ */ jsx(EditOnboardingDialog, {}),
22449
22554
  publishDialogOpen && (publishing || activeDeployment !== null) ? /* @__PURE__ */ jsx(
22450
22555
  PublishDialog,
@@ -22459,82 +22564,112 @@ function EditToolbar() {
22459
22564
  ) : null
22460
22565
  ] });
22461
22566
  }
22462
- const barStyle = {
22463
- height: "100%",
22464
- display: "flex",
22465
- alignItems: "center",
22466
- justifyContent: "space-between",
22467
- gap: 12,
22468
- padding: "0 12px",
22469
- background: "#ffffff",
22470
- color: "#1a1a1a",
22471
- fontFamily: 'system-ui, -apple-system, "Segoe UI", sans-serif',
22472
- fontSize: 13,
22473
- borderBottom: "1px solid #e8e8e8",
22474
- 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
22475
22589
  };
22476
- const leftStyle = {
22477
- display: "flex",
22590
+ const markButtonStyle = {
22591
+ display: "inline-flex",
22478
22592
  alignItems: "center",
22479
- 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"
22480
22602
  };
22481
- const rightStyle = {
22603
+ const contentStyle = {
22482
22604
  display: "flex",
22483
22605
  alignItems: "center",
22484
- gap: 8,
22485
- flexShrink: 0
22606
+ gap: 6,
22607
+ paddingLeft: 4,
22608
+ whiteSpace: "nowrap"
22486
22609
  };
22487
- const draftStyle = {
22488
- color: "#666",
22610
+ const errorStyle = {
22611
+ color: "#ffb4a8",
22489
22612
  fontSize: 12,
22490
- whiteSpace: "nowrap"
22613
+ maxWidth: 240,
22614
+ overflow: "hidden",
22615
+ textOverflow: "ellipsis"
22491
22616
  };
22492
- const errorStyle = { color: "#dc2626", fontSize: 12 };
22493
22617
  const publishedNoticeStyle = {
22494
22618
  display: "inline-flex",
22495
22619
  alignItems: "center",
22496
22620
  gap: 5,
22497
22621
  padding: "4px 10px",
22498
- borderRadius: 6,
22499
- border: "1px solid #bbf7d0",
22500
- background: "#f0fdf4",
22501
- 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,
22502
22626
  fontSize: 12,
22503
22627
  fontWeight: 500,
22504
22628
  cursor: "pointer",
22505
22629
  whiteSpace: "nowrap"
22506
22630
  };
22507
22631
  const publishBtnStyle = {
22508
- background: "#640AFF",
22509
- color: "#fff",
22632
+ background: TERRACOTTA,
22633
+ color: CREMA,
22510
22634
  border: "none",
22511
- borderRadius: 6,
22635
+ borderRadius: 999,
22512
22636
  padding: "6px 14px",
22513
22637
  fontSize: 13,
22514
- fontWeight: 500,
22638
+ fontWeight: 600,
22515
22639
  cursor: "pointer",
22516
22640
  whiteSpace: "nowrap"
22517
22641
  };
22518
22642
  const menuBtnStyle = {
22519
22643
  background: "transparent",
22520
- color: "#666",
22644
+ color: CREMA,
22521
22645
  border: "none",
22522
- borderRadius: 6,
22523
- padding: "4px 8px",
22524
- fontSize: 20,
22646
+ borderRadius: 999,
22647
+ width: 32,
22648
+ height: 32,
22649
+ display: "inline-flex",
22650
+ alignItems: "center",
22651
+ justifyContent: "center",
22652
+ fontSize: 22,
22525
22653
  lineHeight: 1,
22526
- 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"
22527
22662
  };
22528
22663
  const dropdownStyle = {
22529
22664
  position: "absolute",
22530
- top: "100%",
22531
- right: 0,
22532
- marginTop: 4,
22665
+ bottom: "100%",
22666
+ left: 0,
22667
+ marginBottom: 8,
22533
22668
  background: "#fff",
22534
22669
  border: "1px solid #e8e8e8",
22535
- borderRadius: 8,
22536
- boxShadow: "0 4px 16px rgba(0, 0, 0, 0.12)",
22537
- minWidth: 140,
22670
+ borderRadius: 10,
22671
+ boxShadow: "0 12px 32px rgba(33, 23, 17, 0.18)",
22672
+ minWidth: 160,
22538
22673
  overflow: "hidden",
22539
22674
  zIndex: 1
22540
22675
  };
@@ -22554,17 +22689,19 @@ const menuItemStyle = {
22554
22689
  function MenuItem({
22555
22690
  onClick,
22556
22691
  icon,
22557
- children
22692
+ children,
22693
+ disabled = false
22558
22694
  }) {
22559
22695
  return /* @__PURE__ */ jsxs(
22560
22696
  "button",
22561
22697
  {
22562
22698
  type: "button",
22563
22699
  onClick,
22564
- style: menuItemStyle,
22700
+ disabled,
22701
+ style: { ...menuItemStyle, opacity: disabled ? 0.4 : 1, cursor: disabled ? "not-allowed" : "pointer" },
22565
22702
  role: "menuitem",
22566
22703
  onMouseEnter: (e) => {
22567
- e.currentTarget.style.background = "#f5f5f5";
22704
+ if (!disabled) e.currentTarget.style.background = "#f5f5f5";
22568
22705
  },
22569
22706
  onMouseLeave: (e) => {
22570
22707
  e.currentTarget.style.background = "transparent";
@@ -22613,6 +22750,12 @@ function LogOutIcon() {
22613
22750
  /* @__PURE__ */ jsx("line", { x1: "21", y1: "12", x2: "9", y2: "12" })
22614
22751
  ] });
22615
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
+ }
22616
22759
  function EditModeShell({ children, themeStore }) {
22617
22760
  useSettoDocumentLayout(true);
22618
22761
  const [toolbarRoot, setToolbarRoot] = useState(null);
@@ -22652,7 +22795,9 @@ function SettoProvider({ config, children }) {
22652
22795
  const api = useMemo(() => createApi({ apiUrl: config.apiUrl, supabase }), [config.apiUrl, supabase]);
22653
22796
  const [session, setSession] = useState(null);
22654
22797
  const [authLoading, setAuthLoading] = useState(true);
22655
- const [editParam, setEditParam] = useState(false);
22798
+ const [onAdminRoute, setOnAdminRoute] = useState(
22799
+ () => typeof window === "undefined" ? false : isAdminRoute()
22800
+ );
22656
22801
  useEffect(() => {
22657
22802
  let cancelled = false;
22658
22803
  supabase.auth.getSession().then(({ data }) => {
@@ -22669,18 +22814,12 @@ function SettoProvider({ config, children }) {
22669
22814
  };
22670
22815
  }, [supabase]);
22671
22816
  useEffect(() => {
22672
- const compute = () => {
22673
- try {
22674
- const params = new URLSearchParams(window.location.search);
22675
- setEditParam(params.get("setto") === "edit");
22676
- } catch {
22677
- setEditParam(false);
22678
- }
22679
- };
22817
+ const compute = () => setOnAdminRoute(isAdminRoute());
22680
22818
  compute();
22681
22819
  window.addEventListener("popstate", compute);
22682
22820
  return () => window.removeEventListener("popstate", compute);
22683
22821
  }, []);
22822
+ const access = useSiteAccess(supabase, session, config.siteId);
22684
22823
  const { i18n } = useTranslation();
22685
22824
  const storeRef = useRef(null);
22686
22825
  const themeStoreRef = useRef(null);
@@ -22693,7 +22832,7 @@ function SettoProvider({ config, children }) {
22693
22832
  assetStoreRef.current = new AssetStore();
22694
22833
  setStoreReady(true);
22695
22834
  }, [i18n, config.theme]);
22696
- const editMode = !!session && editParam;
22835
+ const editMode = !!session && !!access.currentSite && !onAdminRoute && !authLoading && !access.loading;
22697
22836
  const value = {
22698
22837
  config,
22699
22838
  supabase,
@@ -22701,6 +22840,8 @@ function SettoProvider({ config, children }) {
22701
22840
  session,
22702
22841
  authLoading,
22703
22842
  editMode,
22843
+ currentSite: access.currentSite,
22844
+ sites: access.sites,
22704
22845
  store: storeReady ? storeRef.current : null,
22705
22846
  themeStore: storeReady ? themeStoreRef.current : null,
22706
22847
  assetStore: storeReady ? assetStoreRef.current : null
@@ -23140,7 +23281,7 @@ function FloatingPopover({
23140
23281
  if (top + popoverH > window.innerHeight - 8) {
23141
23282
  top = rect.top - popoverH - 6;
23142
23283
  }
23143
- if (top < TOOLBAR_HEIGHT + 4) top = rect.bottom + 6;
23284
+ if (top < 8) top = rect.bottom + 6;
23144
23285
  setPos({ top, left });
23145
23286
  };
23146
23287
  update();
@@ -23747,7 +23888,7 @@ function AuthGate({ children }) {
23747
23888
  clearAuthHashFromUrl();
23748
23889
  const { data: sessionData } = await supabase.auth.getSession();
23749
23890
  if (sessionData.session) {
23750
- window.location.replace(editModeUrl());
23891
+ window.location.replace(siteHomeUrl());
23751
23892
  return;
23752
23893
  }
23753
23894
  setMode("signin");
@@ -23937,8 +24078,7 @@ function SettoAdminApp() {
23937
24078
  return /* @__PURE__ */ jsx(AuthGate, { children: /* @__PURE__ */ jsx(SiteList, {}) });
23938
24079
  }
23939
24080
  function SiteList() {
23940
- const { supabase, session, config } = useSetto();
23941
- const [sites, setSites] = useState(null);
24081
+ const { supabase, session, sites, currentSite, config } = useSetto();
23942
24082
  const [error, setError] = useState(null);
23943
24083
  const redirectAfterSignIn = useRef(false);
23944
24084
  useEffect(() => {
@@ -23949,33 +24089,13 @@ function SiteList() {
23949
24089
  });
23950
24090
  return () => sub.subscription.unsubscribe();
23951
24091
  }, [supabase]);
23952
- useEffect(() => {
23953
- if (!session) return;
23954
- let cancelled = false;
23955
- supabase.from("sites").select("*").then(({ data, error: err }) => {
23956
- if (cancelled) return;
23957
- if (err) {
23958
- setError(err.message);
23959
- setSites([]);
23960
- return;
23961
- }
23962
- setSites(data ?? []);
23963
- });
23964
- return () => {
23965
- cancelled = true;
23966
- };
23967
- }, [supabase, session]);
23968
- const currentSite = useMemo(
23969
- () => sites?.find((s) => s.id === config.siteId) ?? null,
23970
- [sites, config.siteId]
23971
- );
23972
24092
  useEffect(() => {
23973
24093
  if (!redirectAfterSignIn.current || !session || sites === null || isAdminDeploymentView()) {
23974
24094
  return;
23975
24095
  }
23976
24096
  if (currentSite) {
23977
24097
  redirectAfterSignIn.current = false;
23978
- window.location.replace(editModeUrl());
24098
+ window.location.replace(siteHomeUrl());
23979
24099
  return;
23980
24100
  }
23981
24101
  redirectAfterSignIn.current = false;
@@ -24000,8 +24120,8 @@ function SiteList() {
24000
24120
  " · branch ",
24001
24121
  currentSite.branch
24002
24122
  ] }),
24003
- /* @__PURE__ */ jsx("div", { style: { display: "flex", gap: 8 }, children: /* @__PURE__ */ jsx("a", { href: editUrl(), style: primaryBtnLinkStyle, children: "Begynn å redigere" }) }),
24004
- /* @__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 })
24005
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,
24006
24126
  sites && !currentSite && sites.length > 0 ? /* @__PURE__ */ jsxs("section", { style: cardStyle, children: [
24007
24127
  /* @__PURE__ */ jsx("h2", { style: { margin: 0, fontSize: 16 }, children: "Andre sider du har tilgang til" }),
@@ -24019,7 +24139,10 @@ function SiteList() {
24019
24139
  ] })
24020
24140
  ] });
24021
24141
  }
24022
- function DeploymentList({ siteId }) {
24142
+ function DeploymentList({
24143
+ siteId,
24144
+ onError
24145
+ }) {
24023
24146
  const { supabase, api, config } = useSetto();
24024
24147
  const [rows, setRows] = useState(null);
24025
24148
  const [focusId, setFocusId] = useState(focusedDeploymentId);
@@ -24051,8 +24174,10 @@ function DeploymentList({ siteId }) {
24051
24174
  }, [api, config.siteId]);
24052
24175
  useEffect(() => {
24053
24176
  let cancelled = false;
24054
- supabase.from("deployments").select("*").eq("site_id", siteId).order("started_at", { ascending: false }).limit(10).then(({ data }) => {
24055
- 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 ?? []);
24056
24181
  });
24057
24182
  const channel = supabase.channel(`setto-site-deps-${siteId}`).on(
24058
24183
  "postgres_changes",
@@ -24071,7 +24196,7 @@ function DeploymentList({ siteId }) {
24071
24196
  cancelled = true;
24072
24197
  supabase.removeChannel(channel);
24073
24198
  };
24074
- }, [supabase, siteId]);
24199
+ }, [supabase, siteId, onError]);
24075
24200
  const showFocusPanel = focusId !== null && (focusRow === null || isDeploymentInProgress(focusRow.status));
24076
24201
  if (!rows) return /* @__PURE__ */ jsx("p", { style: mutedStyle, children: "Laster deploys…" });
24077
24202
  return /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 12 }, children: [
@@ -24156,9 +24281,6 @@ function SignOutButton() {
24156
24281
  const { supabase } = useSetto();
24157
24282
  return /* @__PURE__ */ jsx("button", { onClick: () => supabase.auth.signOut(), style: signOutBtnStyle, children: "Logg ut" });
24158
24283
  }
24159
- function editUrl() {
24160
- return editModeUrl("/");
24161
- }
24162
24284
  const loadingRedirectStyle = {
24163
24285
  minHeight: "100dvh",
24164
24286
  display: "flex",