@sevenfold/setto-client 0.2.0 → 0.2.2

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.
@@ -2,11 +2,10 @@
2
2
  * Drop-in admin SPA. Mount under a route like `<Route path="/admin/*" .../>`.
3
3
  *
4
4
  * Behaviour after login:
5
- * - Lists sites the user is a member of (read via Supabase RLS).
6
- * - Single-site sites: redirect to `/?setto=edit` automatically.
7
- * - Multi-site: show a picker.
5
+ * - Redirects to `/?setto=edit` on the site home (edit mode active).
6
+ * - Shows the dashboard only when the user lacks access to this site, or on
7
+ * `/admin?deployment=…` for publish progress / history.
8
8
  *
9
- * While the user is on `/admin`, this component shows the site dashboard.
10
- * Editing happens on the public site at `/?setto=edit` via "Begynn å redigere".
9
+ * Editing happens on the public site at `/?setto=edit`.
11
10
  */
12
11
  export declare function SettoAdminApp(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,6 @@
1
+ /** Site home with inline edit mode active. */
2
+ export declare function editModeUrl(pathname?: string): string;
3
+ export declare function isAdminRoute(): boolean;
4
+ /** Admin deployment progress panel (`/admin?deployment=…`). */
5
+ export declare function isAdminDeploymentView(): boolean;
6
+ export declare function adminRedirectUrl(): string;
@@ -22643,19 +22643,41 @@ const SettoBlock = forwardRef(
22643
22643
  );
22644
22644
  }
22645
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
+ }
22646
22659
  function adminRedirectUrl() {
22647
22660
  return `${window.location.origin}/admin`;
22648
22661
  }
22649
22662
  function authCallbackType() {
22650
22663
  const hash = window.location.hash.replace(/^#/, "");
22651
- if (!hash) return null;
22652
- const type = new URLSearchParams(hash).get("type");
22653
- if (type === "invite" || type === "recovery") return type;
22664
+ if (hash) {
22665
+ const type = new URLSearchParams(hash).get("type");
22666
+ if (type === "invite" || type === "recovery") return type;
22667
+ }
22668
+ const queryType = new URLSearchParams(window.location.search).get("type");
22669
+ if (queryType === "invite" || queryType === "recovery") return queryType;
22654
22670
  return null;
22655
22671
  }
22656
- function clearAuthHashFromUrl() {
22657
- const { pathname, search } = window.location;
22658
- window.history.replaceState(null, "", pathname + search);
22672
+ function inviteTokenHashFromUrl() {
22673
+ return new URLSearchParams(window.location.search).get("token_hash");
22674
+ }
22675
+ function clearAuthParamsFromUrl() {
22676
+ const url = new URL(window.location.href);
22677
+ url.hash = "";
22678
+ url.searchParams.delete("token_hash");
22679
+ url.searchParams.delete("type");
22680
+ window.history.replaceState(null, "", `${url.pathname}${url.search}`);
22659
22681
  }
22660
22682
  function AuthGate({ children }) {
22661
22683
  const { supabase, session, authLoading } = useSetto();
@@ -22666,16 +22688,38 @@ function AuthGate({ children }) {
22666
22688
  const [error, setError] = useState(null);
22667
22689
  const [info, setInfo] = useState(null);
22668
22690
  const [busy, setBusy] = useState(false);
22691
+ const [activatingInvite, setActivatingInvite] = useState(false);
22669
22692
  useEffect(() => {
22670
- const callback = authCallbackType();
22671
- if (callback) setMode("set_password");
22672
- }, []);
22693
+ const tokenHash = inviteTokenHashFromUrl();
22694
+ const callbackType = authCallbackType();
22695
+ if (!tokenHash || !callbackType) {
22696
+ if (callbackType) setMode("set_password");
22697
+ return;
22698
+ }
22699
+ let cancelled = false;
22700
+ setActivatingInvite(true);
22701
+ void supabase.auth.verifyOtp({ token_hash: tokenHash, type: callbackType }).then(({ error: verifyError }) => {
22702
+ if (cancelled) return;
22703
+ clearAuthParamsFromUrl();
22704
+ if (verifyError) {
22705
+ setError(verifyError.message);
22706
+ setMode("signin");
22707
+ return;
22708
+ }
22709
+ setMode("set_password");
22710
+ }).finally(() => {
22711
+ if (!cancelled) setActivatingInvite(false);
22712
+ });
22713
+ return () => {
22714
+ cancelled = true;
22715
+ };
22716
+ }, [supabase]);
22673
22717
  if (authLoading) {
22674
22718
  return /* @__PURE__ */ jsx("div", { style: loadingStyle, children: "Laster …" });
22675
22719
  }
22676
22720
  if (session && mode !== "set_password") return /* @__PURE__ */ jsx(Fragment, { children });
22677
- if (mode === "set_password" && !session) {
22678
- return /* @__PURE__ */ jsx("div", { style: loadingStyle, children: authCallbackType() ? "Aktiverer invitasjon …" : "Åpne lenken fra e-posten for å sette passord." });
22721
+ if (mode === "set_password" && !session || activatingInvite) {
22722
+ return /* @__PURE__ */ jsx("div", { style: loadingStyle, children: activatingInvite || authCallbackType() ? "Aktiverer invitasjon …" : "Åpne lenken fra e-posten for å sette passord." });
22679
22723
  }
22680
22724
  const submitSignIn = async (e) => {
22681
22725
  e.preventDefault();
@@ -22730,7 +22774,12 @@ function AuthGate({ children }) {
22730
22774
  try {
22731
22775
  const { error: updateError } = await supabase.auth.updateUser({ password });
22732
22776
  if (updateError) throw updateError;
22733
- clearAuthHashFromUrl();
22777
+ clearAuthParamsFromUrl();
22778
+ const { data: sessionData } = await supabase.auth.getSession();
22779
+ if (sessionData.session) {
22780
+ window.location.replace(editModeUrl());
22781
+ return;
22782
+ }
22734
22783
  setMode("signin");
22735
22784
  setPassword("");
22736
22785
  setConfirmPassword("");
@@ -22921,6 +22970,15 @@ function SiteList() {
22921
22970
  const { supabase, session, config } = useSetto();
22922
22971
  const [sites, setSites] = useState(null);
22923
22972
  const [error, setError] = useState(null);
22973
+ const redirectAfterSignIn = useRef(false);
22974
+ useEffect(() => {
22975
+ const { data: sub } = supabase.auth.onAuthStateChange((event) => {
22976
+ if (event === "SIGNED_IN" && !isAdminDeploymentView()) {
22977
+ redirectAfterSignIn.current = true;
22978
+ }
22979
+ });
22980
+ return () => sub.subscription.unsubscribe();
22981
+ }, [supabase]);
22924
22982
  useEffect(() => {
22925
22983
  if (!session) return;
22926
22984
  let cancelled = false;
@@ -22941,6 +22999,20 @@ function SiteList() {
22941
22999
  () => sites?.find((s) => s.id === config.siteId) ?? null,
22942
23000
  [sites, config.siteId]
22943
23001
  );
23002
+ useEffect(() => {
23003
+ if (!redirectAfterSignIn.current || !session || sites === null || isAdminDeploymentView()) {
23004
+ return;
23005
+ }
23006
+ if (currentSite) {
23007
+ redirectAfterSignIn.current = false;
23008
+ window.location.replace(editModeUrl());
23009
+ return;
23010
+ }
23011
+ redirectAfterSignIn.current = false;
23012
+ }, [session, sites, currentSite]);
23013
+ if (redirectAfterSignIn.current && session && (sites === null || currentSite)) {
23014
+ return /* @__PURE__ */ jsx("div", { style: loadingRedirectStyle, children: "Åpner redigering …" });
23015
+ }
22944
23016
  return /* @__PURE__ */ jsxs("div", { style: shellStyle, children: [
22945
23017
  /* @__PURE__ */ jsxs("header", { style: headerStyle, children: [
22946
23018
  /* @__PURE__ */ jsx("h1", { style: { margin: 0, fontSize: 22 }, children: "Setto" }),
@@ -23115,11 +23187,18 @@ function SignOutButton() {
23115
23187
  return /* @__PURE__ */ jsx("button", { onClick: () => supabase.auth.signOut(), style: signOutBtnStyle, children: "Logg ut" });
23116
23188
  }
23117
23189
  function editUrl() {
23118
- const u = new URL(window.location.href);
23119
- u.pathname = "/";
23120
- u.searchParams.set("setto", "edit");
23121
- return u.toString();
23190
+ return editModeUrl("/");
23122
23191
  }
23192
+ const loadingRedirectStyle = {
23193
+ minHeight: "100dvh",
23194
+ display: "flex",
23195
+ alignItems: "center",
23196
+ justifyContent: "center",
23197
+ background: "#0a0a0d",
23198
+ color: "#888",
23199
+ fontSize: 14,
23200
+ fontFamily: 'system-ui, -apple-system, "Segoe UI", sans-serif'
23201
+ };
23123
23202
  const shellStyle = {
23124
23203
  minHeight: "100dvh",
23125
23204
  background: "#0a0a0d",