@sevenfold/setto-client 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/admin/App.d.ts +4 -5
- package/dist/edit-mode/auth-gate.d.ts +3 -2
- package/dist/edit-mode/deployment-status.d.ts +35 -0
- package/dist/edit-mode/onboarding-dialog.d.ts +1 -0
- package/dist/edit-mode/overlay.d.ts +10 -0
- package/dist/edit-mode/publish-dialog.d.ts +18 -0
- package/dist/lib/api.d.ts +4 -0
- package/dist/lib/cookies.d.ts +4 -0
- package/dist/lib/urls.d.ts +6 -0
- package/dist/setto-client.js +1028 -130
- package/dist/setto-client.js.map +1 -1
- package/package.json +1 -1
package/dist/setto-client.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx, jsxs, Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect, useState, useRef, useLayoutEffect, createContext, useContext, useCallback, useMemo, useSyncExternalStore, forwardRef } from "react";
|
|
2
|
+
import { useEffect, useState, useRef, useLayoutEffect, createContext, useContext, useCallback, useMemo, useId, useSyncExternalStore, forwardRef } from "react";
|
|
3
3
|
import { useTranslation } from "react-i18next";
|
|
4
4
|
import { createPortal } from "react-dom";
|
|
5
5
|
function __rest(s, e) {
|
|
@@ -20760,6 +20760,29 @@ function createApi(args) {
|
|
|
20760
20760
|
});
|
|
20761
20761
|
if (!res.ok) throw await asError(res, "publish failed");
|
|
20762
20762
|
return parseJson(res);
|
|
20763
|
+
},
|
|
20764
|
+
async expireStaleDeployments(siteId) {
|
|
20765
|
+
const token = await bearer(supabase);
|
|
20766
|
+
const res = await fetch(
|
|
20767
|
+
`${apiUrl}/sites/${encodeURIComponent(siteId)}/deployments/expire-stale`,
|
|
20768
|
+
{
|
|
20769
|
+
method: "POST",
|
|
20770
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
20771
|
+
}
|
|
20772
|
+
);
|
|
20773
|
+
if (!res.ok) throw await asError(res, "expireStaleDeployments failed");
|
|
20774
|
+
return parseJson(res);
|
|
20775
|
+
},
|
|
20776
|
+
async cancelDeployment(siteId, deploymentId) {
|
|
20777
|
+
const token = await bearer(supabase);
|
|
20778
|
+
const res = await fetch(
|
|
20779
|
+
`${apiUrl}/sites/${encodeURIComponent(siteId)}/deployments/${encodeURIComponent(deploymentId)}/cancel`,
|
|
20780
|
+
{
|
|
20781
|
+
method: "POST",
|
|
20782
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
20783
|
+
}
|
|
20784
|
+
);
|
|
20785
|
+
if (!res.ok) throw await asError(res, "cancelDeployment failed");
|
|
20763
20786
|
}
|
|
20764
20787
|
};
|
|
20765
20788
|
}
|
|
@@ -21003,6 +21026,12 @@ function useSettoDocumentLayout(active) {
|
|
|
21003
21026
|
html.setto-edit-mode [data-setto-section]:hover {
|
|
21004
21027
|
box-shadow: inset 0 0 0 1px rgba(100, 10, 255, 0.25);
|
|
21005
21028
|
}
|
|
21029
|
+
|
|
21030
|
+
@media (max-width: 480px) {
|
|
21031
|
+
html.setto-edit-mode [data-setto-draft-count] {
|
|
21032
|
+
display: none;
|
|
21033
|
+
}
|
|
21034
|
+
}
|
|
21006
21035
|
`;
|
|
21007
21036
|
document.head.appendChild(styleEl);
|
|
21008
21037
|
}
|
|
@@ -21482,14 +21511,144 @@ function SectionToolbar() {
|
|
|
21482
21511
|
}
|
|
21483
21512
|
);
|
|
21484
21513
|
}
|
|
21485
|
-
|
|
21486
|
-
|
|
21487
|
-
|
|
21488
|
-
|
|
21489
|
-
|
|
21490
|
-
|
|
21514
|
+
function getCookie(name) {
|
|
21515
|
+
const match = document.cookie.match(new RegExp(`(?:^|; )${escapeRegExp(name)}=([^;]*)`));
|
|
21516
|
+
return match ? decodeURIComponent(match[1]) : null;
|
|
21517
|
+
}
|
|
21518
|
+
function setCookie(name, value, maxAgeSeconds = 365 * 24 * 60 * 60) {
|
|
21519
|
+
document.cookie = `${name}=${encodeURIComponent(value)}; path=/; max-age=${maxAgeSeconds}; SameSite=Lax`;
|
|
21520
|
+
}
|
|
21521
|
+
function escapeRegExp(s) {
|
|
21522
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
21523
|
+
}
|
|
21524
|
+
function EditOverlay({ children, onClose }) {
|
|
21525
|
+
useEffect(() => {
|
|
21526
|
+
const prev = document.body.style.overflow;
|
|
21527
|
+
document.body.style.overflow = "hidden";
|
|
21528
|
+
return () => {
|
|
21529
|
+
document.body.style.overflow = prev;
|
|
21530
|
+
};
|
|
21531
|
+
}, []);
|
|
21532
|
+
return createPortal(
|
|
21533
|
+
/* @__PURE__ */ jsx(
|
|
21534
|
+
"div",
|
|
21535
|
+
{
|
|
21536
|
+
role: "presentation",
|
|
21537
|
+
onClick: onClose,
|
|
21538
|
+
style: {
|
|
21539
|
+
position: "fixed",
|
|
21540
|
+
inset: 0,
|
|
21541
|
+
zIndex: 2147483647,
|
|
21542
|
+
display: "flex",
|
|
21543
|
+
alignItems: "center",
|
|
21544
|
+
justifyContent: "center",
|
|
21545
|
+
padding: 16,
|
|
21546
|
+
background: "rgba(0, 0, 0, 0.35)",
|
|
21547
|
+
backdropFilter: "blur(6px)",
|
|
21548
|
+
WebkitBackdropFilter: "blur(6px)"
|
|
21549
|
+
},
|
|
21550
|
+
children: /* @__PURE__ */ jsx(
|
|
21551
|
+
"div",
|
|
21552
|
+
{
|
|
21553
|
+
role: "dialog",
|
|
21554
|
+
"aria-modal": "true",
|
|
21555
|
+
onClick: (e) => e.stopPropagation(),
|
|
21556
|
+
style: {
|
|
21557
|
+
background: "#fff",
|
|
21558
|
+
borderRadius: 12,
|
|
21559
|
+
padding: "24px 28px",
|
|
21560
|
+
maxWidth: 420,
|
|
21561
|
+
width: "100%",
|
|
21562
|
+
boxShadow: "0 16px 48px rgba(0, 0, 0, 0.18)",
|
|
21563
|
+
fontFamily: 'system-ui, -apple-system, "Segoe UI", sans-serif',
|
|
21564
|
+
color: "#1a1a1a"
|
|
21565
|
+
},
|
|
21566
|
+
children
|
|
21567
|
+
}
|
|
21568
|
+
)
|
|
21569
|
+
}
|
|
21570
|
+
),
|
|
21571
|
+
document.body
|
|
21572
|
+
);
|
|
21573
|
+
}
|
|
21574
|
+
const COOKIE_NAME = "setto_edit_hint_dismissed";
|
|
21575
|
+
function EditOnboardingDialog() {
|
|
21576
|
+
const [open, setOpen] = useState(() => getCookie(COOKIE_NAME) !== "1");
|
|
21577
|
+
const [dontShowAgain, setDontShowAgain] = useState(false);
|
|
21578
|
+
if (!open) return null;
|
|
21579
|
+
const close = () => {
|
|
21580
|
+
if (dontShowAgain) setCookie(COOKIE_NAME, "1");
|
|
21581
|
+
setOpen(false);
|
|
21582
|
+
};
|
|
21583
|
+
return /* @__PURE__ */ jsxs(EditOverlay, { onClose: close, children: [
|
|
21584
|
+
/* @__PURE__ */ jsx("h2", { style: { margin: "0 0 12px", fontSize: 18, fontWeight: 600 }, children: "Velkommen til Setto" }),
|
|
21585
|
+
/* @__PURE__ */ jsx("p", { style: { margin: "0 0 20px", fontSize: 14, lineHeight: 1.5, color: "#444" }, children: "Klikk på tekstene for å redigere. Bruk CTRL+Klikk for å åpne lenker. Klikk på seksjoner for å endre farger mm." }),
|
|
21586
|
+
/* @__PURE__ */ jsxs(
|
|
21587
|
+
"label",
|
|
21588
|
+
{
|
|
21589
|
+
style: {
|
|
21590
|
+
display: "flex",
|
|
21591
|
+
alignItems: "center",
|
|
21592
|
+
gap: 8,
|
|
21593
|
+
marginBottom: 20,
|
|
21594
|
+
fontSize: 13,
|
|
21595
|
+
color: "#666",
|
|
21596
|
+
cursor: "pointer"
|
|
21597
|
+
},
|
|
21598
|
+
children: [
|
|
21599
|
+
/* @__PURE__ */ jsx(
|
|
21600
|
+
"input",
|
|
21601
|
+
{
|
|
21602
|
+
type: "checkbox",
|
|
21603
|
+
checked: dontShowAgain,
|
|
21604
|
+
onChange: (e) => setDontShowAgain(e.target.checked)
|
|
21605
|
+
}
|
|
21606
|
+
),
|
|
21607
|
+
"Ikke vis denne igjen"
|
|
21608
|
+
]
|
|
21609
|
+
}
|
|
21610
|
+
),
|
|
21611
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: close, style: primaryBtnStyle$1, children: "Forstått" })
|
|
21612
|
+
] });
|
|
21613
|
+
}
|
|
21614
|
+
const primaryBtnStyle$1 = {
|
|
21615
|
+
background: "#640AFF",
|
|
21616
|
+
color: "#fff",
|
|
21617
|
+
border: "none",
|
|
21618
|
+
borderRadius: 6,
|
|
21619
|
+
padding: "8px 20px",
|
|
21620
|
+
fontSize: 14,
|
|
21621
|
+
fontWeight: 500,
|
|
21622
|
+
cursor: "pointer",
|
|
21623
|
+
width: "100%"
|
|
21624
|
+
};
|
|
21625
|
+
const IN_PROGRESS_STATUSES = ["pending", "building"];
|
|
21626
|
+
function isDeploymentInProgress(status) {
|
|
21627
|
+
return IN_PROGRESS_STATUSES.includes(status);
|
|
21628
|
+
}
|
|
21629
|
+
const STALE_PENDING_MS = 20 * 60 * 1e3;
|
|
21630
|
+
const STALE_BUILDING_MS = 45 * 60 * 1e3;
|
|
21631
|
+
function isDeploymentStale(row) {
|
|
21632
|
+
if (row.status !== "pending" && row.status !== "building") return false;
|
|
21633
|
+
const now = Date.now();
|
|
21634
|
+
if (row.status === "pending") {
|
|
21635
|
+
return now - new Date(row.started_at).getTime() > STALE_PENDING_MS;
|
|
21636
|
+
}
|
|
21637
|
+
return now - new Date(row.updated_at).getTime() > STALE_BUILDING_MS;
|
|
21638
|
+
}
|
|
21639
|
+
async function fetchInProgressDeploymentId(supabase, siteId) {
|
|
21640
|
+
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);
|
|
21641
|
+
const rows = data ?? [];
|
|
21642
|
+
const fresh = rows.find((r) => !isDeploymentStale(r));
|
|
21643
|
+
return fresh?.id ?? null;
|
|
21644
|
+
}
|
|
21645
|
+
function useDeploymentStatus(supabase, deploymentId) {
|
|
21491
21646
|
const [row, setRow] = useState(null);
|
|
21492
21647
|
useEffect(() => {
|
|
21648
|
+
if (!deploymentId) {
|
|
21649
|
+
setRow(null);
|
|
21650
|
+
return;
|
|
21651
|
+
}
|
|
21493
21652
|
let channel = null;
|
|
21494
21653
|
let cancelled = false;
|
|
21495
21654
|
const load = async () => {
|
|
@@ -21506,59 +21665,17 @@ function BuildStatus({ supabase, deploymentId, onDone }) {
|
|
|
21506
21665
|
filter: `id=eq.${deploymentId}`
|
|
21507
21666
|
},
|
|
21508
21667
|
(payload) => {
|
|
21509
|
-
|
|
21510
|
-
setRow(next);
|
|
21511
|
-
if (next.status === "ready" || next.status === "error" || next.status === "canceled") {
|
|
21512
|
-
onDone?.(next.status);
|
|
21513
|
-
}
|
|
21668
|
+
setRow(payload.new);
|
|
21514
21669
|
}
|
|
21515
21670
|
).subscribe();
|
|
21516
21671
|
return () => {
|
|
21517
21672
|
cancelled = true;
|
|
21518
21673
|
if (channel) supabase.removeChannel(channel);
|
|
21519
21674
|
};
|
|
21520
|
-
}, [supabase, deploymentId
|
|
21521
|
-
|
|
21522
|
-
const errored = status === "error" || status === "canceled";
|
|
21523
|
-
return /* @__PURE__ */ jsxs("div", { style: { fontSize: 12 }, children: [
|
|
21524
|
-
/* @__PURE__ */ jsx("div", { style: { display: "flex", justifyContent: "space-between", gap: 4, marginBottom: 8 }, children: STEPS.map((step, i) => {
|
|
21525
|
-
const reached = isStepReached(status, i);
|
|
21526
|
-
const active = currentStepIndex(status) === i && !errored;
|
|
21527
|
-
return /* @__PURE__ */ jsxs("div", { style: { flex: 1, textAlign: "center" }, children: [
|
|
21528
|
-
/* @__PURE__ */ jsx(
|
|
21529
|
-
"div",
|
|
21530
|
-
{
|
|
21531
|
-
style: {
|
|
21532
|
-
height: 4,
|
|
21533
|
-
borderRadius: 2,
|
|
21534
|
-
background: errored ? "#fca5a5" : reached ? "#640AFF" : "#e5e5e5",
|
|
21535
|
-
marginBottom: 4
|
|
21536
|
-
}
|
|
21537
|
-
}
|
|
21538
|
-
),
|
|
21539
|
-
/* @__PURE__ */ jsxs("span", { style: { color: active ? "#1a1a1a" : "#888" }, children: [
|
|
21540
|
-
step.label,
|
|
21541
|
-
active ? " …" : ""
|
|
21542
|
-
] })
|
|
21543
|
-
] }, step.key);
|
|
21544
|
-
}) }),
|
|
21545
|
-
errored ? /* @__PURE__ */ jsxs("div", { style: { color: "#dc2626" }, children: [
|
|
21546
|
-
status === "error" ? "Bygg feilet." : "Avbrutt.",
|
|
21547
|
-
row?.error_message ? ` ${row.error_message}` : ""
|
|
21548
|
-
] }) : null,
|
|
21549
|
-
status === "ready" && row?.url ? /* @__PURE__ */ jsx(
|
|
21550
|
-
"a",
|
|
21551
|
-
{
|
|
21552
|
-
href: row.url,
|
|
21553
|
-
target: "_blank",
|
|
21554
|
-
rel: "noreferrer",
|
|
21555
|
-
style: { color: "#640AFF", fontSize: 12 },
|
|
21556
|
-
children: "Åpne deploy →"
|
|
21557
|
-
}
|
|
21558
|
-
) : null
|
|
21559
|
-
] });
|
|
21675
|
+
}, [supabase, deploymentId]);
|
|
21676
|
+
return { row, status: row?.status ?? null };
|
|
21560
21677
|
}
|
|
21561
|
-
function
|
|
21678
|
+
function deploymentStepIndex(status) {
|
|
21562
21679
|
switch (status) {
|
|
21563
21680
|
case "pending":
|
|
21564
21681
|
return 0;
|
|
@@ -21570,9 +21687,212 @@ function currentStepIndex(status) {
|
|
|
21570
21687
|
return -1;
|
|
21571
21688
|
}
|
|
21572
21689
|
}
|
|
21573
|
-
function
|
|
21574
|
-
return
|
|
21690
|
+
function isDeploymentStepReached(status, i) {
|
|
21691
|
+
return deploymentStepIndex(status) >= i;
|
|
21692
|
+
}
|
|
21693
|
+
const STEPS = ["Committed", "Building", "Deployed"];
|
|
21694
|
+
const PROGRESS_STYLE_ID = "setto-publish-progress-styles";
|
|
21695
|
+
function usePublishProgressStyles() {
|
|
21696
|
+
useEffect(() => {
|
|
21697
|
+
if (document.getElementById(PROGRESS_STYLE_ID)) return;
|
|
21698
|
+
const el = document.createElement("style");
|
|
21699
|
+
el.id = PROGRESS_STYLE_ID;
|
|
21700
|
+
el.textContent = `
|
|
21701
|
+
@keyframes setto-step-pulse {
|
|
21702
|
+
0%, 100% { opacity: 1; transform: scaleY(1); }
|
|
21703
|
+
50% { opacity: 0.72; transform: scaleY(0.82); }
|
|
21704
|
+
}
|
|
21705
|
+
@keyframes setto-step-shimmer {
|
|
21706
|
+
0% { background-position: 120% 0; }
|
|
21707
|
+
100% { background-position: -120% 0; }
|
|
21708
|
+
}
|
|
21709
|
+
.setto-step-bar-active {
|
|
21710
|
+
animation: setto-step-pulse 1.5s ease-in-out infinite;
|
|
21711
|
+
transform-origin: center bottom;
|
|
21712
|
+
}
|
|
21713
|
+
.setto-step-bar-shimmer {
|
|
21714
|
+
background: linear-gradient(
|
|
21715
|
+
90deg,
|
|
21716
|
+
#640aff 0%,
|
|
21717
|
+
#8b5cf6 45%,
|
|
21718
|
+
#640aff 90%
|
|
21719
|
+
) !important;
|
|
21720
|
+
background-size: 220% 100% !important;
|
|
21721
|
+
animation: setto-step-shimmer 2.2s ease-in-out infinite;
|
|
21722
|
+
}
|
|
21723
|
+
.setto-step-label-active {
|
|
21724
|
+
animation: setto-step-pulse 1.5s ease-in-out infinite;
|
|
21725
|
+
}
|
|
21726
|
+
`;
|
|
21727
|
+
document.head.appendChild(el);
|
|
21728
|
+
}, []);
|
|
21575
21729
|
}
|
|
21730
|
+
function PublishProgressSteps({
|
|
21731
|
+
status,
|
|
21732
|
+
compact,
|
|
21733
|
+
buildingElapsed
|
|
21734
|
+
}) {
|
|
21735
|
+
usePublishProgressStyles();
|
|
21736
|
+
const scopeId = useId().replace(/:/g, "");
|
|
21737
|
+
const errored = status === "error" || status === "canceled";
|
|
21738
|
+
const activeIndex = deploymentStepIndex(status);
|
|
21739
|
+
return /* @__PURE__ */ jsx(
|
|
21740
|
+
"div",
|
|
21741
|
+
{
|
|
21742
|
+
className: `setto-progress-${scopeId}`,
|
|
21743
|
+
style: {
|
|
21744
|
+
display: "flex",
|
|
21745
|
+
gap: compact ? 3 : 8,
|
|
21746
|
+
fontSize: compact ? 11 : 12,
|
|
21747
|
+
minWidth: compact ? 100 : 200
|
|
21748
|
+
},
|
|
21749
|
+
children: STEPS.map((label, i) => {
|
|
21750
|
+
const reached = isDeploymentStepReached(status, i);
|
|
21751
|
+
const active = activeIndex === i && !errored;
|
|
21752
|
+
const barClass = active ? "setto-step-bar-active setto-step-bar-shimmer" : void 0;
|
|
21753
|
+
return /* @__PURE__ */ jsxs("div", { style: { flex: 1, textAlign: "center", minWidth: 0 }, children: [
|
|
21754
|
+
/* @__PURE__ */ jsx(
|
|
21755
|
+
"div",
|
|
21756
|
+
{
|
|
21757
|
+
className: barClass,
|
|
21758
|
+
style: {
|
|
21759
|
+
height: compact ? 3 : 4,
|
|
21760
|
+
borderRadius: 2,
|
|
21761
|
+
background: errored ? "#fca5a5" : reached ? "#640AFF" : "#e5e5e5",
|
|
21762
|
+
marginBottom: compact ? 0 : 6,
|
|
21763
|
+
transition: "background 0.35s ease"
|
|
21764
|
+
}
|
|
21765
|
+
}
|
|
21766
|
+
),
|
|
21767
|
+
!compact ? /* @__PURE__ */ jsxs(
|
|
21768
|
+
"span",
|
|
21769
|
+
{
|
|
21770
|
+
className: active ? "setto-step-label-active" : void 0,
|
|
21771
|
+
style: {
|
|
21772
|
+
color: active ? "#1a1a1a" : "#888",
|
|
21773
|
+
fontWeight: active ? 500 : 400,
|
|
21774
|
+
display: "inline-block"
|
|
21775
|
+
},
|
|
21776
|
+
children: [
|
|
21777
|
+
label,
|
|
21778
|
+
active && status === "building" && buildingElapsed != null ? ` (${buildingElapsed}s)` : active ? " …" : ""
|
|
21779
|
+
]
|
|
21780
|
+
}
|
|
21781
|
+
) : null
|
|
21782
|
+
] }, label);
|
|
21783
|
+
})
|
|
21784
|
+
}
|
|
21785
|
+
);
|
|
21786
|
+
}
|
|
21787
|
+
function PublishProgressBar({ status, compact, href }) {
|
|
21788
|
+
const inner = /* @__PURE__ */ jsx(PublishProgressSteps, { status, compact });
|
|
21789
|
+
const wrapStyle = {
|
|
21790
|
+
fontSize: compact ? 11 : 12,
|
|
21791
|
+
minWidth: compact ? 100 : 200
|
|
21792
|
+
};
|
|
21793
|
+
if (href) {
|
|
21794
|
+
return /* @__PURE__ */ jsx(
|
|
21795
|
+
"a",
|
|
21796
|
+
{
|
|
21797
|
+
href,
|
|
21798
|
+
title: "Se publiseringsstatus",
|
|
21799
|
+
style: {
|
|
21800
|
+
...wrapStyle,
|
|
21801
|
+
display: "block",
|
|
21802
|
+
textDecoration: "none",
|
|
21803
|
+
color: "inherit",
|
|
21804
|
+
cursor: "pointer",
|
|
21805
|
+
borderRadius: 4,
|
|
21806
|
+
padding: compact ? "4px 2px" : void 0
|
|
21807
|
+
},
|
|
21808
|
+
children: inner
|
|
21809
|
+
}
|
|
21810
|
+
);
|
|
21811
|
+
}
|
|
21812
|
+
return /* @__PURE__ */ jsx("div", { style: wrapStyle, children: inner });
|
|
21813
|
+
}
|
|
21814
|
+
function PublishDialog({
|
|
21815
|
+
row,
|
|
21816
|
+
status,
|
|
21817
|
+
onClose,
|
|
21818
|
+
onComplete
|
|
21819
|
+
}) {
|
|
21820
|
+
const buildingStartedAt = useRef(null);
|
|
21821
|
+
const [elapsed, setElapsed] = useState(0);
|
|
21822
|
+
const progressStatus = row?.status ?? status ?? "pending";
|
|
21823
|
+
const errored = progressStatus === "error" || progressStatus === "canceled";
|
|
21824
|
+
const isReady = progressStatus === "ready";
|
|
21825
|
+
const isBuilding = progressStatus === "building";
|
|
21826
|
+
useEffect(() => {
|
|
21827
|
+
if (isBuilding && buildingStartedAt.current === null) {
|
|
21828
|
+
buildingStartedAt.current = Date.now();
|
|
21829
|
+
}
|
|
21830
|
+
if (!isBuilding) buildingStartedAt.current = null;
|
|
21831
|
+
}, [isBuilding]);
|
|
21832
|
+
useEffect(() => {
|
|
21833
|
+
if (!isBuilding) return;
|
|
21834
|
+
const id = window.setInterval(() => {
|
|
21835
|
+
if (buildingStartedAt.current !== null) {
|
|
21836
|
+
setElapsed(Math.floor((Date.now() - buildingStartedAt.current) / 1e3));
|
|
21837
|
+
}
|
|
21838
|
+
}, 1e3);
|
|
21839
|
+
return () => clearInterval(id);
|
|
21840
|
+
}, [isBuilding]);
|
|
21841
|
+
return /* @__PURE__ */ jsx(EditOverlay, { onClose: void 0, children: isReady ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
21842
|
+
/* @__PURE__ */ jsx("h2", { style: { margin: "0 0 12px", fontSize: 18, fontWeight: 600 }, children: "Publisert" }),
|
|
21843
|
+
/* @__PURE__ */ jsx("p", { style: { margin: "0 0 20px", fontSize: 14, lineHeight: 1.5, color: "#444" }, children: "Endringene dine er live." }),
|
|
21844
|
+
row?.url ? /* @__PURE__ */ jsx(
|
|
21845
|
+
"a",
|
|
21846
|
+
{
|
|
21847
|
+
href: row.url,
|
|
21848
|
+
target: "_blank",
|
|
21849
|
+
rel: "noreferrer",
|
|
21850
|
+
style: { ...linkStyle$1, display: "block", marginBottom: 16 },
|
|
21851
|
+
children: "Åpne publisert side →"
|
|
21852
|
+
}
|
|
21853
|
+
) : null,
|
|
21854
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: onComplete, style: primaryBtnStyle, children: "OK" })
|
|
21855
|
+
] }) : errored ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
21856
|
+
/* @__PURE__ */ jsx("h2", { style: { margin: "0 0 12px", fontSize: 18, fontWeight: 600, color: "#dc2626" }, children: "Publisering feilet" }),
|
|
21857
|
+
/* @__PURE__ */ jsxs("p", { style: { margin: "0 0 20px", fontSize: 14, lineHeight: 1.5, color: "#444" }, children: [
|
|
21858
|
+
progressStatus === "error" ? "Bygg feilet." : "Avbrutt.",
|
|
21859
|
+
row?.error_message ? ` ${row.error_message}` : ""
|
|
21860
|
+
] }),
|
|
21861
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: onComplete, style: secondaryBtnStyle, children: "OK" })
|
|
21862
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
21863
|
+
/* @__PURE__ */ jsx("h2", { style: { margin: "0 0 16px", fontSize: 16, fontWeight: 600 }, children: "Publiserer endringer" }),
|
|
21864
|
+
/* @__PURE__ */ jsx("div", { style: { marginBottom: 20 }, children: /* @__PURE__ */ jsx(
|
|
21865
|
+
PublishProgressSteps,
|
|
21866
|
+
{
|
|
21867
|
+
status: progressStatus,
|
|
21868
|
+
buildingElapsed: isBuilding ? elapsed : void 0
|
|
21869
|
+
}
|
|
21870
|
+
) }),
|
|
21871
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: onClose, style: secondaryBtnStyle, children: "Lukk" })
|
|
21872
|
+
] }) });
|
|
21873
|
+
}
|
|
21874
|
+
const primaryBtnStyle = {
|
|
21875
|
+
background: "#640AFF",
|
|
21876
|
+
color: "#fff",
|
|
21877
|
+
border: "none",
|
|
21878
|
+
borderRadius: 6,
|
|
21879
|
+
padding: "8px 20px",
|
|
21880
|
+
fontSize: 14,
|
|
21881
|
+
fontWeight: 500,
|
|
21882
|
+
cursor: "pointer",
|
|
21883
|
+
width: "100%"
|
|
21884
|
+
};
|
|
21885
|
+
const secondaryBtnStyle = {
|
|
21886
|
+
...primaryBtnStyle,
|
|
21887
|
+
background: "transparent",
|
|
21888
|
+
color: "#666",
|
|
21889
|
+
border: "1px solid #ddd"
|
|
21890
|
+
};
|
|
21891
|
+
const linkStyle$1 = {
|
|
21892
|
+
color: "#640AFF",
|
|
21893
|
+
fontSize: 14,
|
|
21894
|
+
textDecoration: "none"
|
|
21895
|
+
};
|
|
21576
21896
|
function guessLangFromPath(path, languages) {
|
|
21577
21897
|
for (const lng of languages) {
|
|
21578
21898
|
if (path.includes(`/${lng}.`) || path.includes(`/${lng}/`)) return lng;
|
|
@@ -21640,15 +21960,119 @@ function EditToolbar() {
|
|
|
21640
21960
|
);
|
|
21641
21961
|
const [publishing, setPublishing] = useState(false);
|
|
21642
21962
|
const [activeDeployment, setActiveDeployment] = useState(null);
|
|
21963
|
+
const [publishDialogOpen, setPublishDialogOpen] = useState(false);
|
|
21643
21964
|
const [error, setError] = useState(null);
|
|
21965
|
+
const [publishedNotice, setPublishedNotice] = useState(false);
|
|
21966
|
+
const [menuOpen, setMenuOpen] = useState(false);
|
|
21967
|
+
const menuRef = useRef(null);
|
|
21968
|
+
const publishedNoticeTimer = useRef(null);
|
|
21969
|
+
const { row: deploymentRow, status: deploymentStatus } = useDeploymentStatus(
|
|
21970
|
+
supabase,
|
|
21971
|
+
activeDeployment
|
|
21972
|
+
);
|
|
21973
|
+
useEffect(() => {
|
|
21974
|
+
let cancelled = false;
|
|
21975
|
+
const resume = async () => {
|
|
21976
|
+
try {
|
|
21977
|
+
await api.expireStaleDeployments(config.siteId);
|
|
21978
|
+
} catch {
|
|
21979
|
+
}
|
|
21980
|
+
const id = await fetchInProgressDeploymentId(supabase, config.siteId);
|
|
21981
|
+
if (!cancelled && id) {
|
|
21982
|
+
setActiveDeployment((current) => current ?? id);
|
|
21983
|
+
}
|
|
21984
|
+
};
|
|
21985
|
+
void resume();
|
|
21986
|
+
return () => {
|
|
21987
|
+
cancelled = true;
|
|
21988
|
+
};
|
|
21989
|
+
}, [api, supabase, config.siteId]);
|
|
21644
21990
|
const textDraftCount = snap.drafts.size;
|
|
21645
21991
|
const themeDraftCount = themeStore?.size() ?? 0;
|
|
21646
21992
|
const draftCount = textDraftCount + themeDraftCount;
|
|
21993
|
+
const hasDrafts = draftCount > 0;
|
|
21994
|
+
const showToolbarProgress = activeDeployment !== null && !publishDialogOpen && deploymentRow !== null && isDeploymentInProgress(deploymentRow.status) && !isDeploymentStale(deploymentRow);
|
|
21995
|
+
const showPublishedNotice = publishedNotice && !publishDialogOpen;
|
|
21996
|
+
useEffect(() => {
|
|
21997
|
+
return () => {
|
|
21998
|
+
if (publishedNoticeTimer.current) clearTimeout(publishedNoticeTimer.current);
|
|
21999
|
+
};
|
|
22000
|
+
}, []);
|
|
22001
|
+
useEffect(() => {
|
|
22002
|
+
if (!activeDeployment || !deploymentRow) return;
|
|
22003
|
+
const { status } = deploymentRow;
|
|
22004
|
+
if (status === "ready") {
|
|
22005
|
+
if (publishDialogOpen) return;
|
|
22006
|
+
setPublishedNotice(true);
|
|
22007
|
+
setActiveDeployment(null);
|
|
22008
|
+
if (publishedNoticeTimer.current) clearTimeout(publishedNoticeTimer.current);
|
|
22009
|
+
publishedNoticeTimer.current = setTimeout(() => setPublishedNotice(false), 6e3);
|
|
22010
|
+
return;
|
|
22011
|
+
}
|
|
22012
|
+
if (publishDialogOpen) return;
|
|
22013
|
+
if (status === "error" || status === "canceled") {
|
|
22014
|
+
setError(
|
|
22015
|
+
status === "canceled" ? "Publisering avbrutt." : deploymentRow.error_message?.includes("webhook") ? "Publisering utløpt (ingen webhook)." : "Publisering feilet."
|
|
22016
|
+
);
|
|
22017
|
+
setActiveDeployment(null);
|
|
22018
|
+
return;
|
|
22019
|
+
}
|
|
22020
|
+
if (isDeploymentStale(deploymentRow)) {
|
|
22021
|
+
setError("Publisering utløpt (ingen webhook).");
|
|
22022
|
+
setActiveDeployment(null);
|
|
22023
|
+
}
|
|
22024
|
+
}, [activeDeployment, deploymentRow, publishDialogOpen]);
|
|
22025
|
+
useEffect(() => {
|
|
22026
|
+
if (!activeDeployment) return;
|
|
22027
|
+
const syncActiveDeployment = async () => {
|
|
22028
|
+
const { data } = await supabase.from("deployments").select("status").eq("id", activeDeployment).maybeSingle();
|
|
22029
|
+
if (!data) {
|
|
22030
|
+
setActiveDeployment(null);
|
|
22031
|
+
return;
|
|
22032
|
+
}
|
|
22033
|
+
const status = data.status;
|
|
22034
|
+
if (status === "ready") {
|
|
22035
|
+
if (!publishDialogOpen) {
|
|
22036
|
+
setPublishedNotice(true);
|
|
22037
|
+
if (publishedNoticeTimer.current) clearTimeout(publishedNoticeTimer.current);
|
|
22038
|
+
publishedNoticeTimer.current = setTimeout(() => setPublishedNotice(false), 6e3);
|
|
22039
|
+
}
|
|
22040
|
+
setActiveDeployment(null);
|
|
22041
|
+
return;
|
|
22042
|
+
}
|
|
22043
|
+
if (!isDeploymentInProgress(status)) {
|
|
22044
|
+
if (status === "canceled") setError("Publisering avbrutt.");
|
|
22045
|
+
else if (status === "error") setError("Publisering feilet.");
|
|
22046
|
+
setActiveDeployment(null);
|
|
22047
|
+
}
|
|
22048
|
+
};
|
|
22049
|
+
const onVisible = () => {
|
|
22050
|
+
if (document.visibilityState === "visible") void syncActiveDeployment();
|
|
22051
|
+
};
|
|
22052
|
+
window.addEventListener("focus", syncActiveDeployment);
|
|
22053
|
+
document.addEventListener("visibilitychange", onVisible);
|
|
22054
|
+
return () => {
|
|
22055
|
+
window.removeEventListener("focus", syncActiveDeployment);
|
|
22056
|
+
document.removeEventListener("visibilitychange", onVisible);
|
|
22057
|
+
};
|
|
22058
|
+
}, [activeDeployment, publishDialogOpen, supabase]);
|
|
21647
22059
|
const languages = i18n.languages?.length ? Array.from(new Set(i18n.languages)) : ["no", "en"];
|
|
22060
|
+
useEffect(() => {
|
|
22061
|
+
if (!menuOpen) return;
|
|
22062
|
+
const close = (e) => {
|
|
22063
|
+
if (menuRef.current && !menuRef.current.contains(e.target)) {
|
|
22064
|
+
setMenuOpen(false);
|
|
22065
|
+
}
|
|
22066
|
+
};
|
|
22067
|
+
document.addEventListener("mousedown", close);
|
|
22068
|
+
return () => document.removeEventListener("mousedown", close);
|
|
22069
|
+
}, [menuOpen]);
|
|
21648
22070
|
const publish = async () => {
|
|
21649
22071
|
if (!store) return;
|
|
21650
22072
|
setPublishing(true);
|
|
22073
|
+
setPublishDialogOpen(true);
|
|
21651
22074
|
setError(null);
|
|
22075
|
+
setPublishedNotice(false);
|
|
21652
22076
|
try {
|
|
21653
22077
|
const bundles = store.serialiseBundles();
|
|
21654
22078
|
const files = Object.entries(bundles).map(([lng, data]) => ({
|
|
@@ -21667,63 +22091,112 @@ function EditToolbar() {
|
|
|
21667
22091
|
setActiveDeployment(result.deploymentId);
|
|
21668
22092
|
} catch (err) {
|
|
21669
22093
|
setError(err instanceof Error ? err.message : "unknown error");
|
|
22094
|
+
setPublishDialogOpen(false);
|
|
21670
22095
|
} finally {
|
|
21671
22096
|
setPublishing(false);
|
|
21672
22097
|
}
|
|
21673
22098
|
};
|
|
21674
|
-
|
|
21675
|
-
|
|
21676
|
-
|
|
21677
|
-
|
|
21678
|
-
|
|
21679
|
-
|
|
21680
|
-
|
|
21681
|
-
|
|
21682
|
-
|
|
21683
|
-
|
|
21684
|
-
|
|
21685
|
-
|
|
21686
|
-
|
|
21687
|
-
|
|
21688
|
-
|
|
21689
|
-
|
|
21690
|
-
|
|
21691
|
-
|
|
21692
|
-
|
|
21693
|
-
|
|
21694
|
-
|
|
22099
|
+
const completePublish = () => {
|
|
22100
|
+
setPublishDialogOpen(false);
|
|
22101
|
+
setActiveDeployment(null);
|
|
22102
|
+
setPublishedNotice(false);
|
|
22103
|
+
if (publishedNoticeTimer.current) clearTimeout(publishedNoticeTimer.current);
|
|
22104
|
+
};
|
|
22105
|
+
const dismissPublishedNotice = () => {
|
|
22106
|
+
setPublishedNotice(false);
|
|
22107
|
+
if (publishedNoticeTimer.current) clearTimeout(publishedNoticeTimer.current);
|
|
22108
|
+
};
|
|
22109
|
+
const signOut = () => {
|
|
22110
|
+
setMenuOpen(false);
|
|
22111
|
+
void supabase.auth.signOut();
|
|
22112
|
+
};
|
|
22113
|
+
const goToHistory = () => {
|
|
22114
|
+
setMenuOpen(false);
|
|
22115
|
+
window.location.href = "/admin";
|
|
22116
|
+
};
|
|
22117
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
22118
|
+
/* @__PURE__ */ jsxs("header", { style: barStyle, role: "toolbar", "aria-label": "Setto editor", children: [
|
|
22119
|
+
/* @__PURE__ */ jsx("div", { style: leftStyle, children: /* @__PURE__ */ jsx("strong", { style: { fontSize: 13 }, children: "Setto" }) }),
|
|
22120
|
+
/* @__PURE__ */ jsxs("div", { style: rightStyle, children: [
|
|
22121
|
+
error ? /* @__PURE__ */ jsx("span", { style: errorStyle, children: error }) : null,
|
|
22122
|
+
hasDrafts && !publishing && activeDeployment === null ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
22123
|
+
/* @__PURE__ */ jsxs("span", { style: draftStyle, "data-setto-draft-count": true, children: [
|
|
22124
|
+
draftCount,
|
|
22125
|
+
" ",
|
|
22126
|
+
draftCount === 1 ? "endring" : "endringer"
|
|
22127
|
+
] }),
|
|
22128
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: publish, style: publishBtnStyle, children: "Publiser" })
|
|
21695
22129
|
] }) : null,
|
|
21696
|
-
/* @__PURE__ */
|
|
22130
|
+
showPublishedNotice ? /* @__PURE__ */ jsxs(
|
|
21697
22131
|
"button",
|
|
21698
22132
|
{
|
|
21699
22133
|
type: "button",
|
|
21700
|
-
onClick:
|
|
21701
|
-
|
|
21702
|
-
|
|
21703
|
-
|
|
21704
|
-
|
|
21705
|
-
|
|
21706
|
-
|
|
22134
|
+
onClick: dismissPublishedNotice,
|
|
22135
|
+
style: publishedNoticeStyle,
|
|
22136
|
+
title: "Lukk",
|
|
22137
|
+
children: [
|
|
22138
|
+
/* @__PURE__ */ jsx(PublishedCheckIcon, {}),
|
|
22139
|
+
"Publisert"
|
|
22140
|
+
]
|
|
21707
22141
|
}
|
|
21708
|
-
)
|
|
21709
|
-
|
|
21710
|
-
|
|
21711
|
-
|
|
22142
|
+
) : null,
|
|
22143
|
+
showToolbarProgress && deploymentRow ? /* @__PURE__ */ jsx(
|
|
22144
|
+
PublishProgressBar,
|
|
22145
|
+
{
|
|
22146
|
+
status: deploymentRow.status,
|
|
22147
|
+
compact: true,
|
|
22148
|
+
href: `/admin?deployment=${encodeURIComponent(activeDeployment)}`
|
|
22149
|
+
}
|
|
22150
|
+
) : null,
|
|
22151
|
+
/* @__PURE__ */ jsxs("div", { ref: menuRef, style: { position: "relative" }, children: [
|
|
22152
|
+
/* @__PURE__ */ jsx(
|
|
22153
|
+
"button",
|
|
22154
|
+
{
|
|
22155
|
+
type: "button",
|
|
22156
|
+
onClick: () => setMenuOpen((o) => !o),
|
|
22157
|
+
"aria-label": "Meny",
|
|
22158
|
+
"aria-expanded": menuOpen,
|
|
22159
|
+
style: {
|
|
22160
|
+
...menuBtnStyle,
|
|
22161
|
+
background: menuOpen ? "#f0f0f0" : void 0
|
|
22162
|
+
},
|
|
22163
|
+
onMouseEnter: (e) => {
|
|
22164
|
+
e.currentTarget.style.background = "#f0f0f0";
|
|
22165
|
+
},
|
|
22166
|
+
onMouseLeave: (e) => {
|
|
22167
|
+
e.currentTarget.style.background = menuOpen ? "#f0f0f0" : "transparent";
|
|
22168
|
+
},
|
|
22169
|
+
children: "⋮"
|
|
22170
|
+
}
|
|
22171
|
+
),
|
|
22172
|
+
menuOpen ? /* @__PURE__ */ jsxs("div", { style: dropdownStyle, role: "menu", children: [
|
|
22173
|
+
/* @__PURE__ */ jsx(MenuItem, { onClick: goToHistory, icon: /* @__PURE__ */ jsx(HistoryIcon, {}), children: "Historikk" }),
|
|
22174
|
+
/* @__PURE__ */ jsx(MenuItem, { onClick: signOut, icon: /* @__PURE__ */ jsx(LogOutIcon, {}), children: "Logg ut" })
|
|
22175
|
+
] }) : null
|
|
22176
|
+
] })
|
|
22177
|
+
] })
|
|
22178
|
+
] }),
|
|
22179
|
+
/* @__PURE__ */ jsx(EditOnboardingDialog, {}),
|
|
22180
|
+
publishDialogOpen && (publishing || activeDeployment !== null) ? /* @__PURE__ */ jsx(
|
|
22181
|
+
PublishDialog,
|
|
22182
|
+
{
|
|
22183
|
+
deploymentId: activeDeployment ?? "",
|
|
22184
|
+
row: deploymentRow,
|
|
22185
|
+
status: deploymentStatus ?? "pending",
|
|
22186
|
+
publishing,
|
|
22187
|
+
onClose: () => setPublishDialogOpen(false),
|
|
22188
|
+
onComplete: completePublish
|
|
22189
|
+
}
|
|
22190
|
+
) : null
|
|
21712
22191
|
] });
|
|
21713
22192
|
}
|
|
21714
|
-
function exitEditMode() {
|
|
21715
|
-
const u = new URL(window.location.href);
|
|
21716
|
-
u.searchParams.delete("setto");
|
|
21717
|
-
window.history.replaceState({}, "", u.toString());
|
|
21718
|
-
window.dispatchEvent(new PopStateEvent("popstate"));
|
|
21719
|
-
}
|
|
21720
22193
|
const barStyle = {
|
|
21721
22194
|
height: "100%",
|
|
21722
22195
|
display: "flex",
|
|
21723
22196
|
alignItems: "center",
|
|
21724
22197
|
justifyContent: "space-between",
|
|
21725
|
-
gap:
|
|
21726
|
-
padding: "0
|
|
22198
|
+
gap: 12,
|
|
22199
|
+
padding: "0 12px",
|
|
21727
22200
|
background: "#ffffff",
|
|
21728
22201
|
color: "#1a1a1a",
|
|
21729
22202
|
fontFamily: 'system-ui, -apple-system, "Segoe UI", sans-serif',
|
|
@@ -21734,24 +22207,34 @@ const barStyle = {
|
|
|
21734
22207
|
const leftStyle = {
|
|
21735
22208
|
display: "flex",
|
|
21736
22209
|
alignItems: "center",
|
|
21737
|
-
gap: 6,
|
|
21738
22210
|
minWidth: 0
|
|
21739
22211
|
};
|
|
21740
22212
|
const rightStyle = {
|
|
21741
22213
|
display: "flex",
|
|
21742
22214
|
alignItems: "center",
|
|
21743
|
-
gap:
|
|
22215
|
+
gap: 8,
|
|
21744
22216
|
flexShrink: 0
|
|
21745
22217
|
};
|
|
21746
|
-
const
|
|
21747
|
-
|
|
21748
|
-
const hintStyle = {
|
|
21749
|
-
color: "#999",
|
|
22218
|
+
const draftStyle = {
|
|
22219
|
+
color: "#666",
|
|
21750
22220
|
fontSize: 12,
|
|
21751
|
-
|
|
22221
|
+
whiteSpace: "nowrap"
|
|
21752
22222
|
};
|
|
21753
|
-
const draftStyle = { color: "#666", fontSize: 12 };
|
|
21754
22223
|
const errorStyle = { color: "#dc2626", fontSize: 12 };
|
|
22224
|
+
const publishedNoticeStyle = {
|
|
22225
|
+
display: "inline-flex",
|
|
22226
|
+
alignItems: "center",
|
|
22227
|
+
gap: 5,
|
|
22228
|
+
padding: "4px 10px",
|
|
22229
|
+
borderRadius: 6,
|
|
22230
|
+
border: "1px solid #bbf7d0",
|
|
22231
|
+
background: "#f0fdf4",
|
|
22232
|
+
color: "#15803d",
|
|
22233
|
+
fontSize: 12,
|
|
22234
|
+
fontWeight: 500,
|
|
22235
|
+
cursor: "pointer",
|
|
22236
|
+
whiteSpace: "nowrap"
|
|
22237
|
+
};
|
|
21755
22238
|
const publishBtnStyle = {
|
|
21756
22239
|
background: "#640AFF",
|
|
21757
22240
|
color: "#fff",
|
|
@@ -21760,17 +22243,107 @@ const publishBtnStyle = {
|
|
|
21760
22243
|
padding: "6px 14px",
|
|
21761
22244
|
fontSize: 13,
|
|
21762
22245
|
fontWeight: 500,
|
|
21763
|
-
cursor: "pointer"
|
|
22246
|
+
cursor: "pointer",
|
|
22247
|
+
whiteSpace: "nowrap"
|
|
21764
22248
|
};
|
|
21765
|
-
const
|
|
22249
|
+
const menuBtnStyle = {
|
|
21766
22250
|
background: "transparent",
|
|
21767
22251
|
color: "#666",
|
|
21768
|
-
border: "
|
|
22252
|
+
border: "none",
|
|
21769
22253
|
borderRadius: 6,
|
|
21770
|
-
padding: "
|
|
21771
|
-
fontSize:
|
|
22254
|
+
padding: "4px 8px",
|
|
22255
|
+
fontSize: 20,
|
|
22256
|
+
lineHeight: 1,
|
|
21772
22257
|
cursor: "pointer"
|
|
21773
22258
|
};
|
|
22259
|
+
const dropdownStyle = {
|
|
22260
|
+
position: "absolute",
|
|
22261
|
+
top: "100%",
|
|
22262
|
+
right: 0,
|
|
22263
|
+
marginTop: 4,
|
|
22264
|
+
background: "#fff",
|
|
22265
|
+
border: "1px solid #e8e8e8",
|
|
22266
|
+
borderRadius: 8,
|
|
22267
|
+
boxShadow: "0 4px 16px rgba(0, 0, 0, 0.12)",
|
|
22268
|
+
minWidth: 140,
|
|
22269
|
+
overflow: "hidden",
|
|
22270
|
+
zIndex: 1
|
|
22271
|
+
};
|
|
22272
|
+
const menuItemStyle = {
|
|
22273
|
+
display: "flex",
|
|
22274
|
+
alignItems: "center",
|
|
22275
|
+
gap: 8,
|
|
22276
|
+
width: "100%",
|
|
22277
|
+
background: "transparent",
|
|
22278
|
+
border: "none",
|
|
22279
|
+
padding: "10px 14px",
|
|
22280
|
+
fontSize: 13,
|
|
22281
|
+
textAlign: "left",
|
|
22282
|
+
cursor: "pointer",
|
|
22283
|
+
color: "#1a1a1a"
|
|
22284
|
+
};
|
|
22285
|
+
function MenuItem({
|
|
22286
|
+
onClick,
|
|
22287
|
+
icon,
|
|
22288
|
+
children
|
|
22289
|
+
}) {
|
|
22290
|
+
return /* @__PURE__ */ jsxs(
|
|
22291
|
+
"button",
|
|
22292
|
+
{
|
|
22293
|
+
type: "button",
|
|
22294
|
+
onClick,
|
|
22295
|
+
style: menuItemStyle,
|
|
22296
|
+
role: "menuitem",
|
|
22297
|
+
onMouseEnter: (e) => {
|
|
22298
|
+
e.currentTarget.style.background = "#f5f5f5";
|
|
22299
|
+
},
|
|
22300
|
+
onMouseLeave: (e) => {
|
|
22301
|
+
e.currentTarget.style.background = "transparent";
|
|
22302
|
+
},
|
|
22303
|
+
children: [
|
|
22304
|
+
/* @__PURE__ */ jsx("span", { style: menuIconWrapStyle, "aria-hidden": true, children: icon }),
|
|
22305
|
+
children
|
|
22306
|
+
]
|
|
22307
|
+
}
|
|
22308
|
+
);
|
|
22309
|
+
}
|
|
22310
|
+
const menuIconWrapStyle = {
|
|
22311
|
+
display: "flex",
|
|
22312
|
+
alignItems: "center",
|
|
22313
|
+
justifyContent: "center",
|
|
22314
|
+
flexShrink: 0,
|
|
22315
|
+
color: "#666"
|
|
22316
|
+
};
|
|
22317
|
+
function PublishedCheckIcon() {
|
|
22318
|
+
return /* @__PURE__ */ jsx(
|
|
22319
|
+
"svg",
|
|
22320
|
+
{
|
|
22321
|
+
width: "14",
|
|
22322
|
+
height: "14",
|
|
22323
|
+
viewBox: "0 0 24 24",
|
|
22324
|
+
fill: "none",
|
|
22325
|
+
stroke: "currentColor",
|
|
22326
|
+
strokeWidth: "2.5",
|
|
22327
|
+
strokeLinecap: "round",
|
|
22328
|
+
strokeLinejoin: "round",
|
|
22329
|
+
"aria-hidden": true,
|
|
22330
|
+
children: /* @__PURE__ */ jsx("polyline", { points: "20 6 9 17 4 12" })
|
|
22331
|
+
}
|
|
22332
|
+
);
|
|
22333
|
+
}
|
|
22334
|
+
function HistoryIcon() {
|
|
22335
|
+
return /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
22336
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10" }),
|
|
22337
|
+
/* @__PURE__ */ jsx("polyline", { points: "12 6 12 12 16 14" })
|
|
22338
|
+
] });
|
|
22339
|
+
}
|
|
22340
|
+
function LogOutIcon() {
|
|
22341
|
+
return /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
22342
|
+
/* @__PURE__ */ jsx("path", { d: "M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" }),
|
|
22343
|
+
/* @__PURE__ */ jsx("polyline", { points: "16 17 21 12 16 7" }),
|
|
22344
|
+
/* @__PURE__ */ jsx("line", { x1: "21", y1: "12", x2: "9", y2: "12" })
|
|
22345
|
+
] });
|
|
22346
|
+
}
|
|
21774
22347
|
function EditModeShell({ children, themeStore }) {
|
|
21775
22348
|
useSettoDocumentLayout(true);
|
|
21776
22349
|
const [toolbarRoot, setToolbarRoot] = useState(null);
|
|
@@ -21919,6 +22492,7 @@ function T({ k }) {
|
|
|
21919
22492
|
const handleInput = (e) => {
|
|
21920
22493
|
store?.set(k, i18n.language, e.currentTarget.textContent ?? "");
|
|
21921
22494
|
};
|
|
22495
|
+
const lang = toBcp47(i18n.language);
|
|
21922
22496
|
return /* @__PURE__ */ jsx(
|
|
21923
22497
|
"span",
|
|
21924
22498
|
{
|
|
@@ -21926,6 +22500,8 @@ function T({ k }) {
|
|
|
21926
22500
|
"data-setto-key": k,
|
|
21927
22501
|
contentEditable: true,
|
|
21928
22502
|
suppressContentEditableWarning: true,
|
|
22503
|
+
lang,
|
|
22504
|
+
spellCheck: true,
|
|
21929
22505
|
role: "textbox",
|
|
21930
22506
|
tabIndex: 0,
|
|
21931
22507
|
onFocus: () => setFocused(true),
|
|
@@ -21943,6 +22519,12 @@ function T({ k }) {
|
|
|
21943
22519
|
}
|
|
21944
22520
|
);
|
|
21945
22521
|
}
|
|
22522
|
+
function toBcp47(code) {
|
|
22523
|
+
const base = (code.split("-")[0] ?? code).toLowerCase();
|
|
22524
|
+
if (base === "no" || base === "nb" || base === "nn") return "nb-NO";
|
|
22525
|
+
if (base === "en") return "en";
|
|
22526
|
+
return code;
|
|
22527
|
+
}
|
|
21946
22528
|
function isTextEditClick(target) {
|
|
21947
22529
|
if (target.closest("[data-setto-key]")) return true;
|
|
21948
22530
|
if (target.closest("[data-setto-ui]")) return true;
|
|
@@ -22061,23 +22643,60 @@ const SettoBlock = forwardRef(
|
|
|
22061
22643
|
);
|
|
22062
22644
|
}
|
|
22063
22645
|
);
|
|
22646
|
+
function editModeUrl(pathname = "/") {
|
|
22647
|
+
const u = new URL(window.location.href);
|
|
22648
|
+
u.pathname = pathname;
|
|
22649
|
+
u.search = "";
|
|
22650
|
+
u.searchParams.set("setto", "edit");
|
|
22651
|
+
return u.toString();
|
|
22652
|
+
}
|
|
22653
|
+
function isAdminRoute() {
|
|
22654
|
+
return window.location.pathname.startsWith("/admin");
|
|
22655
|
+
}
|
|
22656
|
+
function isAdminDeploymentView() {
|
|
22657
|
+
return isAdminRoute() && new URLSearchParams(window.location.search).has("deployment");
|
|
22658
|
+
}
|
|
22659
|
+
function adminRedirectUrl() {
|
|
22660
|
+
return `${window.location.origin}/admin`;
|
|
22661
|
+
}
|
|
22662
|
+
function authCallbackType() {
|
|
22663
|
+
const hash = window.location.hash.replace(/^#/, "");
|
|
22664
|
+
if (!hash) return null;
|
|
22665
|
+
const type = new URLSearchParams(hash).get("type");
|
|
22666
|
+
if (type === "invite" || type === "recovery") return type;
|
|
22667
|
+
return null;
|
|
22668
|
+
}
|
|
22669
|
+
function clearAuthHashFromUrl() {
|
|
22670
|
+
const { pathname, search } = window.location;
|
|
22671
|
+
window.history.replaceState(null, "", pathname + search);
|
|
22672
|
+
}
|
|
22064
22673
|
function AuthGate({ children }) {
|
|
22065
22674
|
const { supabase, session, authLoading } = useSetto();
|
|
22066
22675
|
const [mode, setMode] = useState("signin");
|
|
22067
22676
|
const [email, setEmail] = useState("");
|
|
22068
22677
|
const [password, setPassword] = useState("");
|
|
22678
|
+
const [confirmPassword, setConfirmPassword] = useState("");
|
|
22069
22679
|
const [error, setError] = useState(null);
|
|
22680
|
+
const [info, setInfo] = useState(null);
|
|
22070
22681
|
const [busy, setBusy] = useState(false);
|
|
22682
|
+
useEffect(() => {
|
|
22683
|
+
const callback = authCallbackType();
|
|
22684
|
+
if (callback) setMode("set_password");
|
|
22685
|
+
}, []);
|
|
22071
22686
|
if (authLoading) {
|
|
22072
22687
|
return /* @__PURE__ */ jsx("div", { style: loadingStyle, children: "Laster …" });
|
|
22073
22688
|
}
|
|
22074
|
-
if (session) return /* @__PURE__ */ jsx(Fragment, { children });
|
|
22075
|
-
|
|
22689
|
+
if (session && mode !== "set_password") return /* @__PURE__ */ jsx(Fragment, { children });
|
|
22690
|
+
if (mode === "set_password" && !session) {
|
|
22691
|
+
return /* @__PURE__ */ jsx("div", { style: loadingStyle, children: authCallbackType() ? "Aktiverer invitasjon …" : "Åpne lenken fra e-posten for å sette passord." });
|
|
22692
|
+
}
|
|
22693
|
+
const submitSignIn = async (e) => {
|
|
22076
22694
|
e.preventDefault();
|
|
22077
22695
|
setBusy(true);
|
|
22078
22696
|
setError(null);
|
|
22697
|
+
setInfo(null);
|
|
22079
22698
|
try {
|
|
22080
|
-
const result =
|
|
22699
|
+
const result = await supabase.auth.signInWithPassword({ email, password });
|
|
22081
22700
|
if (result.error) throw result.error;
|
|
22082
22701
|
if (result.data.session) {
|
|
22083
22702
|
await supabase.auth.setSession({
|
|
@@ -22091,9 +22710,127 @@ function AuthGate({ children }) {
|
|
|
22091
22710
|
setBusy(false);
|
|
22092
22711
|
}
|
|
22093
22712
|
};
|
|
22094
|
-
|
|
22713
|
+
const submitForgot = async (e) => {
|
|
22714
|
+
e.preventDefault();
|
|
22715
|
+
setBusy(true);
|
|
22716
|
+
setError(null);
|
|
22717
|
+
setInfo(null);
|
|
22718
|
+
try {
|
|
22719
|
+
const { error: resetError } = await supabase.auth.resetPasswordForEmail(email, {
|
|
22720
|
+
redirectTo: adminRedirectUrl()
|
|
22721
|
+
});
|
|
22722
|
+
if (resetError) throw resetError;
|
|
22723
|
+
setInfo("Vi har sendt en lenke for å nullstille passordet. Sjekk innboksen.");
|
|
22724
|
+
} catch (err) {
|
|
22725
|
+
setError(err instanceof Error ? err.message : "Ukjent feil");
|
|
22726
|
+
} finally {
|
|
22727
|
+
setBusy(false);
|
|
22728
|
+
}
|
|
22729
|
+
};
|
|
22730
|
+
const submitSetPassword = async (e) => {
|
|
22731
|
+
e.preventDefault();
|
|
22732
|
+
if (password !== confirmPassword) {
|
|
22733
|
+
setError("Passordene er ikke like.");
|
|
22734
|
+
return;
|
|
22735
|
+
}
|
|
22736
|
+
if (password.length < 8) {
|
|
22737
|
+
setError("Passordet må være minst 8 tegn.");
|
|
22738
|
+
return;
|
|
22739
|
+
}
|
|
22740
|
+
setBusy(true);
|
|
22741
|
+
setError(null);
|
|
22742
|
+
setInfo(null);
|
|
22743
|
+
try {
|
|
22744
|
+
const { error: updateError } = await supabase.auth.updateUser({ password });
|
|
22745
|
+
if (updateError) throw updateError;
|
|
22746
|
+
clearAuthHashFromUrl();
|
|
22747
|
+
const { data: sessionData } = await supabase.auth.getSession();
|
|
22748
|
+
if (sessionData.session) {
|
|
22749
|
+
window.location.replace(editModeUrl());
|
|
22750
|
+
return;
|
|
22751
|
+
}
|
|
22752
|
+
setMode("signin");
|
|
22753
|
+
setPassword("");
|
|
22754
|
+
setConfirmPassword("");
|
|
22755
|
+
} catch (err) {
|
|
22756
|
+
setError(err instanceof Error ? err.message : "Ukjent feil");
|
|
22757
|
+
} finally {
|
|
22758
|
+
setBusy(false);
|
|
22759
|
+
}
|
|
22760
|
+
};
|
|
22761
|
+
if (mode === "set_password") {
|
|
22762
|
+
const callback = authCallbackType();
|
|
22763
|
+
return /* @__PURE__ */ jsx("div", { style: shellStyle$1, children: /* @__PURE__ */ jsxs("form", { onSubmit: submitSetPassword, style: formStyle, children: [
|
|
22764
|
+
/* @__PURE__ */ jsx("h1", { style: { margin: 0, fontSize: 22 }, children: "Setto" }),
|
|
22765
|
+
/* @__PURE__ */ jsx("p", { style: { margin: 0, color: "#888", fontSize: 13 }, children: callback === "invite" ? "Velg et passord for å fullføre invitasjonen." : "Velg et nytt passord." }),
|
|
22766
|
+
/* @__PURE__ */ jsx(
|
|
22767
|
+
"input",
|
|
22768
|
+
{
|
|
22769
|
+
type: "password",
|
|
22770
|
+
value: password,
|
|
22771
|
+
onChange: (e) => setPassword(e.target.value),
|
|
22772
|
+
placeholder: "Nytt passord",
|
|
22773
|
+
autoComplete: "new-password",
|
|
22774
|
+
required: true,
|
|
22775
|
+
minLength: 8,
|
|
22776
|
+
style: inputStyle
|
|
22777
|
+
}
|
|
22778
|
+
),
|
|
22779
|
+
/* @__PURE__ */ jsx(
|
|
22780
|
+
"input",
|
|
22781
|
+
{
|
|
22782
|
+
type: "password",
|
|
22783
|
+
value: confirmPassword,
|
|
22784
|
+
onChange: (e) => setConfirmPassword(e.target.value),
|
|
22785
|
+
placeholder: "Gjenta passord",
|
|
22786
|
+
autoComplete: "new-password",
|
|
22787
|
+
required: true,
|
|
22788
|
+
minLength: 8,
|
|
22789
|
+
style: inputStyle
|
|
22790
|
+
}
|
|
22791
|
+
),
|
|
22792
|
+
error ? /* @__PURE__ */ jsx("div", { style: { color: "#ff7a7a", fontSize: 12 }, children: error }) : null,
|
|
22793
|
+
info ? /* @__PURE__ */ jsx("div", { style: { color: "#9fd89f", fontSize: 12 }, children: info }) : null,
|
|
22794
|
+
/* @__PURE__ */ jsx("button", { type: "submit", disabled: busy, style: btnStyle, children: busy ? "..." : "Lagre passord" })
|
|
22795
|
+
] }) });
|
|
22796
|
+
}
|
|
22797
|
+
if (mode === "forgot") {
|
|
22798
|
+
return /* @__PURE__ */ jsx("div", { style: shellStyle$1, children: /* @__PURE__ */ jsxs("form", { onSubmit: submitForgot, style: formStyle, children: [
|
|
22799
|
+
/* @__PURE__ */ jsx("h1", { style: { margin: 0, fontSize: 22 }, children: "Setto" }),
|
|
22800
|
+
/* @__PURE__ */ jsx("p", { style: { margin: 0, color: "#888", fontSize: 13 }, children: "Skriv inn e-posten din, så sender vi en lenke for å nullstille passordet." }),
|
|
22801
|
+
/* @__PURE__ */ jsx(
|
|
22802
|
+
"input",
|
|
22803
|
+
{
|
|
22804
|
+
type: "email",
|
|
22805
|
+
value: email,
|
|
22806
|
+
onChange: (e) => setEmail(e.target.value),
|
|
22807
|
+
placeholder: "E-post",
|
|
22808
|
+
autoComplete: "email",
|
|
22809
|
+
required: true,
|
|
22810
|
+
style: inputStyle
|
|
22811
|
+
}
|
|
22812
|
+
),
|
|
22813
|
+
error ? /* @__PURE__ */ jsx("div", { style: { color: "#ff7a7a", fontSize: 12 }, children: error }) : null,
|
|
22814
|
+
info ? /* @__PURE__ */ jsx("div", { style: { color: "#9fd89f", fontSize: 12 }, children: info }) : null,
|
|
22815
|
+
/* @__PURE__ */ jsx("button", { type: "submit", disabled: busy, style: btnStyle, children: busy ? "..." : "Send lenke" }),
|
|
22816
|
+
/* @__PURE__ */ jsx(
|
|
22817
|
+
"button",
|
|
22818
|
+
{
|
|
22819
|
+
type: "button",
|
|
22820
|
+
onClick: () => {
|
|
22821
|
+
setMode("signin");
|
|
22822
|
+
setError(null);
|
|
22823
|
+
setInfo(null);
|
|
22824
|
+
},
|
|
22825
|
+
style: linkStyle,
|
|
22826
|
+
children: "Tilbake til innlogging"
|
|
22827
|
+
}
|
|
22828
|
+
)
|
|
22829
|
+
] }) });
|
|
22830
|
+
}
|
|
22831
|
+
return /* @__PURE__ */ jsx("div", { style: shellStyle$1, children: /* @__PURE__ */ jsxs("form", { onSubmit: submitSignIn, style: formStyle, children: [
|
|
22095
22832
|
/* @__PURE__ */ jsx("h1", { style: { margin: 0, fontSize: 22 }, children: "Setto" }),
|
|
22096
|
-
/* @__PURE__ */ jsx("p", { style: { margin: 0, color: "#888", fontSize: 13 }, children:
|
|
22833
|
+
/* @__PURE__ */ jsx("p", { style: { margin: 0, color: "#888", fontSize: 13 }, children: "Logg inn for å redigere innholdet. Har du fått invitasjon, bruk lenken i e-posten første gang." }),
|
|
22097
22834
|
/* @__PURE__ */ jsx(
|
|
22098
22835
|
"input",
|
|
22099
22836
|
{
|
|
@@ -22113,20 +22850,25 @@ function AuthGate({ children }) {
|
|
|
22113
22850
|
value: password,
|
|
22114
22851
|
onChange: (e) => setPassword(e.target.value),
|
|
22115
22852
|
placeholder: "Passord",
|
|
22116
|
-
autoComplete:
|
|
22853
|
+
autoComplete: "current-password",
|
|
22117
22854
|
required: true,
|
|
22118
22855
|
style: inputStyle
|
|
22119
22856
|
}
|
|
22120
22857
|
),
|
|
22121
22858
|
error ? /* @__PURE__ */ jsx("div", { style: { color: "#ff7a7a", fontSize: 12 }, children: error }) : null,
|
|
22122
|
-
/* @__PURE__ */ jsx("
|
|
22859
|
+
info ? /* @__PURE__ */ jsx("div", { style: { color: "#9fd89f", fontSize: 12 }, children: info }) : null,
|
|
22860
|
+
/* @__PURE__ */ jsx("button", { type: "submit", disabled: busy, style: btnStyle, children: busy ? "..." : "Logg inn" }),
|
|
22123
22861
|
/* @__PURE__ */ jsx(
|
|
22124
22862
|
"button",
|
|
22125
22863
|
{
|
|
22126
22864
|
type: "button",
|
|
22127
|
-
onClick: () =>
|
|
22865
|
+
onClick: () => {
|
|
22866
|
+
setMode("forgot");
|
|
22867
|
+
setError(null);
|
|
22868
|
+
setInfo(null);
|
|
22869
|
+
},
|
|
22128
22870
|
style: linkStyle,
|
|
22129
|
-
children:
|
|
22871
|
+
children: "Glemt passord?"
|
|
22130
22872
|
}
|
|
22131
22873
|
)
|
|
22132
22874
|
] }) });
|
|
@@ -22180,6 +22922,16 @@ const loadingStyle = {
|
|
|
22180
22922
|
fontSize: 14,
|
|
22181
22923
|
color: "#888"
|
|
22182
22924
|
};
|
|
22925
|
+
function deploymentAdminUrl(deploymentId) {
|
|
22926
|
+
const u = new URL(window.location.href);
|
|
22927
|
+
u.pathname = "/admin";
|
|
22928
|
+
u.search = "";
|
|
22929
|
+
u.searchParams.set("deployment", deploymentId);
|
|
22930
|
+
return u.pathname + u.search;
|
|
22931
|
+
}
|
|
22932
|
+
function focusedDeploymentId() {
|
|
22933
|
+
return new URLSearchParams(window.location.search).get("deployment");
|
|
22934
|
+
}
|
|
22183
22935
|
function SettoAdminApp() {
|
|
22184
22936
|
return /* @__PURE__ */ jsx(AuthGate, { children: /* @__PURE__ */ jsx(SiteList, {}) });
|
|
22185
22937
|
}
|
|
@@ -22187,6 +22939,15 @@ function SiteList() {
|
|
|
22187
22939
|
const { supabase, session, config } = useSetto();
|
|
22188
22940
|
const [sites, setSites] = useState(null);
|
|
22189
22941
|
const [error, setError] = useState(null);
|
|
22942
|
+
const redirectAfterSignIn = useRef(false);
|
|
22943
|
+
useEffect(() => {
|
|
22944
|
+
const { data: sub } = supabase.auth.onAuthStateChange((event) => {
|
|
22945
|
+
if (event === "SIGNED_IN" && !isAdminDeploymentView()) {
|
|
22946
|
+
redirectAfterSignIn.current = true;
|
|
22947
|
+
}
|
|
22948
|
+
});
|
|
22949
|
+
return () => sub.subscription.unsubscribe();
|
|
22950
|
+
}, [supabase]);
|
|
22190
22951
|
useEffect(() => {
|
|
22191
22952
|
if (!session) return;
|
|
22192
22953
|
let cancelled = false;
|
|
@@ -22207,6 +22968,20 @@ function SiteList() {
|
|
|
22207
22968
|
() => sites?.find((s) => s.id === config.siteId) ?? null,
|
|
22208
22969
|
[sites, config.siteId]
|
|
22209
22970
|
);
|
|
22971
|
+
useEffect(() => {
|
|
22972
|
+
if (!redirectAfterSignIn.current || !session || sites === null || isAdminDeploymentView()) {
|
|
22973
|
+
return;
|
|
22974
|
+
}
|
|
22975
|
+
if (currentSite) {
|
|
22976
|
+
redirectAfterSignIn.current = false;
|
|
22977
|
+
window.location.replace(editModeUrl());
|
|
22978
|
+
return;
|
|
22979
|
+
}
|
|
22980
|
+
redirectAfterSignIn.current = false;
|
|
22981
|
+
}, [session, sites, currentSite]);
|
|
22982
|
+
if (redirectAfterSignIn.current && session && (sites === null || currentSite)) {
|
|
22983
|
+
return /* @__PURE__ */ jsx("div", { style: loadingRedirectStyle, children: "Åpner redigering …" });
|
|
22984
|
+
}
|
|
22210
22985
|
return /* @__PURE__ */ jsxs("div", { style: shellStyle, children: [
|
|
22211
22986
|
/* @__PURE__ */ jsxs("header", { style: headerStyle, children: [
|
|
22212
22987
|
/* @__PURE__ */ jsx("h1", { style: { margin: 0, fontSize: 22 }, children: "Setto" }),
|
|
@@ -22244,8 +23019,35 @@ function SiteList() {
|
|
|
22244
23019
|
] });
|
|
22245
23020
|
}
|
|
22246
23021
|
function DeploymentList({ siteId }) {
|
|
22247
|
-
const { supabase } = useSetto();
|
|
23022
|
+
const { supabase, api, config } = useSetto();
|
|
22248
23023
|
const [rows, setRows] = useState(null);
|
|
23024
|
+
const [focusId, setFocusId] = useState(focusedDeploymentId);
|
|
23025
|
+
const [canceling, setCanceling] = useState(false);
|
|
23026
|
+
const [cancelError, setCancelError] = useState(null);
|
|
23027
|
+
const { row: focusRow } = useDeploymentStatus(supabase, focusId);
|
|
23028
|
+
useEffect(() => {
|
|
23029
|
+
const onPopState = () => setFocusId(focusedDeploymentId());
|
|
23030
|
+
window.addEventListener("popstate", onPopState);
|
|
23031
|
+
return () => window.removeEventListener("popstate", onPopState);
|
|
23032
|
+
}, []);
|
|
23033
|
+
const cancelDeployment = async () => {
|
|
23034
|
+
if (!focusId) return;
|
|
23035
|
+
setCanceling(true);
|
|
23036
|
+
setCancelError(null);
|
|
23037
|
+
try {
|
|
23038
|
+
await api.cancelDeployment(config.siteId, focusId);
|
|
23039
|
+
setFocusId(null);
|
|
23040
|
+
window.history.replaceState(null, "", "/admin");
|
|
23041
|
+
} catch (err) {
|
|
23042
|
+
setCancelError(err instanceof Error ? err.message : "Kunne ikke avbryte");
|
|
23043
|
+
} finally {
|
|
23044
|
+
setCanceling(false);
|
|
23045
|
+
}
|
|
23046
|
+
};
|
|
23047
|
+
useEffect(() => {
|
|
23048
|
+
void api.expireStaleDeployments(config.siteId).catch(() => {
|
|
23049
|
+
});
|
|
23050
|
+
}, [api, config.siteId]);
|
|
22249
23051
|
useEffect(() => {
|
|
22250
23052
|
let cancelled = false;
|
|
22251
23053
|
supabase.from("deployments").select("*").eq("site_id", siteId).order("started_at", { ascending: false }).limit(10).then(({ data }) => {
|
|
@@ -22269,25 +23071,103 @@ function DeploymentList({ siteId }) {
|
|
|
22269
23071
|
supabase.removeChannel(channel);
|
|
22270
23072
|
};
|
|
22271
23073
|
}, [supabase, siteId]);
|
|
23074
|
+
const showFocusPanel = focusId !== null && (focusRow === null || isDeploymentInProgress(focusRow.status));
|
|
22272
23075
|
if (!rows) return /* @__PURE__ */ jsx("p", { style: mutedStyle, children: "Laster deploys…" });
|
|
22273
|
-
|
|
22274
|
-
|
|
22275
|
-
|
|
22276
|
-
|
|
22277
|
-
|
|
22278
|
-
|
|
22279
|
-
|
|
23076
|
+
return /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 12 }, children: [
|
|
23077
|
+
showFocusPanel ? /* @__PURE__ */ jsxs("section", { style: activeDepStyle, children: [
|
|
23078
|
+
/* @__PURE__ */ jsx("h3", { style: { margin: 0, fontSize: 15 }, children: "Publisering pågår" }),
|
|
23079
|
+
focusRow ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
23080
|
+
/* @__PURE__ */ jsx(PublishProgressBar, { status: focusRow.status }),
|
|
23081
|
+
/* @__PURE__ */ jsxs("p", { style: mutedStyle, children: [
|
|
23082
|
+
"Commit ",
|
|
23083
|
+
/* @__PURE__ */ jsx("code", { style: { color: "#bda6ff" }, children: focusRow.commit_sha.slice(0, 7) }),
|
|
23084
|
+
focusRow.vercel_deployment_id ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
23085
|
+
" ",
|
|
23086
|
+
"· Vercel",
|
|
23087
|
+
" ",
|
|
23088
|
+
/* @__PURE__ */ jsxs("code", { style: { color: "#888", fontSize: 11 }, children: [
|
|
23089
|
+
focusRow.vercel_deployment_id.slice(0, 12),
|
|
23090
|
+
"…"
|
|
23091
|
+
] })
|
|
23092
|
+
] }) : null
|
|
23093
|
+
] }),
|
|
23094
|
+
cancelError ? /* @__PURE__ */ jsx("p", { style: errStyle, children: cancelError }) : null,
|
|
23095
|
+
/* @__PURE__ */ jsx(
|
|
23096
|
+
"button",
|
|
23097
|
+
{
|
|
23098
|
+
type: "button",
|
|
23099
|
+
onClick: () => void cancelDeployment(),
|
|
23100
|
+
disabled: canceling,
|
|
23101
|
+
style: cancelBtnStyle,
|
|
23102
|
+
children: canceling ? "Avbryter …" : "Avbryt publisering"
|
|
23103
|
+
}
|
|
23104
|
+
)
|
|
23105
|
+
] }) : /* @__PURE__ */ jsx("p", { style: mutedStyle, children: "Laster status …" })
|
|
23106
|
+
] }) : null,
|
|
23107
|
+
rows.length === 0 ? /* @__PURE__ */ jsx("p", { style: mutedStyle, children: "Ingen deploys ennå." }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
23108
|
+
/* @__PURE__ */ jsx("h3", { style: { margin: 0, fontSize: 14, color: "#aaa" }, children: "Historikk" }),
|
|
23109
|
+
/* @__PURE__ */ jsx("ul", { style: { listStyle: "none", padding: 0, margin: 0, display: "flex", flexDirection: "column", gap: 6 }, children: rows.map((r) => {
|
|
23110
|
+
const focused = r.id === focusId;
|
|
23111
|
+
const inProgress = isDeploymentInProgress(r.status);
|
|
23112
|
+
return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
|
|
23113
|
+
"a",
|
|
23114
|
+
{
|
|
23115
|
+
href: inProgress ? deploymentAdminUrl(r.id) : void 0,
|
|
23116
|
+
style: {
|
|
23117
|
+
...depRowStyle,
|
|
23118
|
+
textDecoration: "none",
|
|
23119
|
+
color: "inherit",
|
|
23120
|
+
outline: focused ? "1px solid #640AFF" : void 0,
|
|
23121
|
+
cursor: inProgress ? "pointer" : "default"
|
|
23122
|
+
},
|
|
23123
|
+
onClick: inProgress ? (e) => {
|
|
23124
|
+
e.preventDefault();
|
|
23125
|
+
setFocusId(r.id);
|
|
23126
|
+
window.history.pushState(null, "", deploymentAdminUrl(r.id));
|
|
23127
|
+
} : void 0,
|
|
23128
|
+
children: [
|
|
23129
|
+
/* @__PURE__ */ jsx("span", { style: depDotStyle(r.status) }),
|
|
23130
|
+
/* @__PURE__ */ jsx("code", { style: { fontSize: 12, color: "#aaa" }, children: r.commit_sha.slice(0, 7) }),
|
|
23131
|
+
/* @__PURE__ */ jsx("span", { style: { flex: 1, fontSize: 12 }, children: statusLabel(r) }),
|
|
23132
|
+
/* @__PURE__ */ jsx("time", { style: { fontSize: 11, color: "#888" }, children: new Date(r.started_at).toLocaleString() })
|
|
23133
|
+
]
|
|
23134
|
+
}
|
|
23135
|
+
) }, r.id);
|
|
23136
|
+
}) })
|
|
23137
|
+
] })
|
|
23138
|
+
] });
|
|
23139
|
+
}
|
|
23140
|
+
function statusLabel(row) {
|
|
23141
|
+
switch (row.status) {
|
|
23142
|
+
case "pending":
|
|
23143
|
+
return "Venter";
|
|
23144
|
+
case "building":
|
|
23145
|
+
return "Bygger";
|
|
23146
|
+
case "ready":
|
|
23147
|
+
return "Publisert";
|
|
23148
|
+
case "error":
|
|
23149
|
+
return row.error_message?.includes("webhook") ? "Utløpt" : "Feilet";
|
|
23150
|
+
case "canceled":
|
|
23151
|
+
return "Avbrutt";
|
|
23152
|
+
}
|
|
22280
23153
|
}
|
|
22281
23154
|
function SignOutButton() {
|
|
22282
23155
|
const { supabase } = useSetto();
|
|
22283
23156
|
return /* @__PURE__ */ jsx("button", { onClick: () => supabase.auth.signOut(), style: signOutBtnStyle, children: "Logg ut" });
|
|
22284
23157
|
}
|
|
22285
23158
|
function editUrl() {
|
|
22286
|
-
|
|
22287
|
-
u.pathname = "/";
|
|
22288
|
-
u.searchParams.set("setto", "edit");
|
|
22289
|
-
return u.toString();
|
|
23159
|
+
return editModeUrl("/");
|
|
22290
23160
|
}
|
|
23161
|
+
const loadingRedirectStyle = {
|
|
23162
|
+
minHeight: "100dvh",
|
|
23163
|
+
display: "flex",
|
|
23164
|
+
alignItems: "center",
|
|
23165
|
+
justifyContent: "center",
|
|
23166
|
+
background: "#0a0a0d",
|
|
23167
|
+
color: "#888",
|
|
23168
|
+
fontSize: 14,
|
|
23169
|
+
fontFamily: 'system-ui, -apple-system, "Segoe UI", sans-serif'
|
|
23170
|
+
};
|
|
22291
23171
|
const shellStyle = {
|
|
22292
23172
|
minHeight: "100dvh",
|
|
22293
23173
|
background: "#0a0a0d",
|
|
@@ -22347,6 +23227,14 @@ const signOutBtnStyle = {
|
|
|
22347
23227
|
fontSize: 12,
|
|
22348
23228
|
cursor: "pointer"
|
|
22349
23229
|
};
|
|
23230
|
+
const activeDepStyle = {
|
|
23231
|
+
background: "#1a1a20",
|
|
23232
|
+
borderRadius: 8,
|
|
23233
|
+
padding: 14,
|
|
23234
|
+
display: "flex",
|
|
23235
|
+
flexDirection: "column",
|
|
23236
|
+
gap: 10
|
|
23237
|
+
};
|
|
22350
23238
|
const depRowStyle = {
|
|
22351
23239
|
display: "flex",
|
|
22352
23240
|
alignItems: "center",
|
|
@@ -22355,6 +23243,16 @@ const depRowStyle = {
|
|
|
22355
23243
|
padding: "6px 10px",
|
|
22356
23244
|
borderRadius: 6
|
|
22357
23245
|
};
|
|
23246
|
+
const cancelBtnStyle = {
|
|
23247
|
+
alignSelf: "flex-start",
|
|
23248
|
+
background: "transparent",
|
|
23249
|
+
color: "#ff7a7a",
|
|
23250
|
+
border: "1px solid #4a3030",
|
|
23251
|
+
borderRadius: 6,
|
|
23252
|
+
padding: "8px 14px",
|
|
23253
|
+
fontSize: 13,
|
|
23254
|
+
cursor: "pointer"
|
|
23255
|
+
};
|
|
22358
23256
|
function depDotStyle(status) {
|
|
22359
23257
|
const colour = status === "ready" ? "#34d399" : status === "error" || status === "canceled" ? "#ff7a7a" : status === "building" ? "#fbbf24" : "#888";
|
|
22360
23258
|
return {
|