@guard-rail/core 0.7.1 → 0.9.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.
- package/README.md +86 -41
- package/dist/adapters/supabase/auth.d.ts +13 -1
- package/dist/adapters/supabase/auth.js +42 -25
- package/dist/adapters/supabase/auth.js.map +1 -1
- package/dist/adapters/supabase/client.d.ts +16 -1
- package/dist/adapters/supabase/client.js +9 -0
- package/dist/adapters/supabase/client.js.map +1 -1
- package/dist/adapters/supabase/confirm.d.ts +5 -0
- package/dist/adapters/supabase/confirm.js +12 -1
- package/dist/adapters/supabase/confirm.js.map +1 -1
- package/dist/adapters/supabase/members.js +13 -2
- package/dist/adapters/supabase/members.js.map +1 -1
- package/dist/core/adapter-types.d.ts +80 -12
- package/dist/core/config.d.ts +2 -1
- package/dist/core/config.js +4 -0
- package/dist/core/config.js.map +1 -1
- package/dist/core/defs.d.ts +11 -1
- package/dist/core/defs.js +11 -1
- package/dist/core/defs.js.map +1 -1
- package/dist/core/types.d.ts +6 -0
- package/dist/email/resend.d.ts +80 -0
- package/dist/email/resend.js +118 -0
- package/dist/email/resend.js.map +1 -0
- package/dist/index.client.d.ts +6 -3
- package/dist/index.client.js +14 -0
- package/dist/index.client.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/react/Admin.js +105 -107
- package/dist/react/Admin.js.map +1 -1
- package/dist/react/AuthDrawer.d.ts +23 -0
- package/dist/react/AuthDrawer.js +165 -0
- package/dist/react/AuthDrawer.js.map +1 -0
- package/dist/react/AuthRedirectSync.js +1 -1
- package/dist/react/AuthRedirectSync.js.map +1 -1
- package/dist/react/Icons.d.ts +2 -1
- package/dist/react/Icons.js +2 -0
- package/dist/react/Icons.js.map +1 -1
- package/dist/react/Team.d.ts +7 -1
- package/dist/react/Team.js +23 -3
- package/dist/react/Team.js.map +1 -1
- package/dist/server/access.d.ts +3 -1
- package/dist/server/access.js +5 -3
- package/dist/server/access.js.map +1 -1
- package/dist/server/actions.d.ts +25 -4
- package/dist/server/actions.js +118 -11
- package/dist/server/actions.js.map +1 -1
- package/dist/server/wrapper.js +2 -1
- package/dist/server/wrapper.js.map +1 -1
- package/package.json +8 -10
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["// Server-safe entry. Client components are reached through the files that\n// carry their own \"use client\" directive, so importing this from a Server\n// Component does not pull the editor into the client bundle.\nexport { withGuardrail } from \"./server/wrapper\";\n\n// Team management and the set-password step, exported so an app can build\n// its own UI instead of using the bundled drawer / sign-in bar.\nexport {\n listTeam,\n inviteMember,\n updateMemberRole,\n removeMember,\n createAssetUpload,\n listAssets,\n setPasswordAction,\n skipSetPasswordAction,\n setFieldValue,\n} from \"./server/actions\";\nexport { GRail } from \"./react/GRail\";\nexport { GRepeater } from \"./react/Repeater\";\nexport type { GRepeaterProps, RepeaterItemProps } from \"./react/Repeater\";\nexport { configureGuardrail } from \"./core/config\";\nexport {\n listRepeaterItems,\n getRepeaterItemBySlug,\n} from \"./server/repeaterData\";\nexport { getFieldValue } from \"./server/fieldData\";\n// Role / edit-mode checks for a custom admin UI and the Server Actions behind\n// it. Plain server functions, not actions — see access.ts for why.\nexport { isAdmin, isEditor, canEdit } from \"./server/access\";\n// Mutations, exported so an app can build its own add/remove/reorder UI\n// instead of the bundled chrome — same rationale as the team actions above.\nexport {\n addRepeaterItem,\n removeRepeaterItem,\n reorderRepeaterItems,\n} from \"./server/repeaterActions\";\nexport { useGRail } from \"./react/Provider\";\n\nexport type {\n GRState,\n GRRole,\n GRMember,\n GRViewer,\n GRAsset,\n GRRepeaterItem,\n ActionState,\n SetPasswordState,\n OverrideRow,\n OverrideValue,\n OverrideType,\n PolymorphicProps,\n HTMLKeys,\n} from \"./core/types\";\nexport type {\n GRConfig,\n GRPrefetch,\n GRMembershipAdapter,\n GRInviteEvent,\n GRStorageAdapter,\n GRUploadTarget,\n GRRepeaterAdapter,\n GRDBAdapter,\n GRAuthAdapter,\n GRSession,\n GRStorageRow,\n AuthResult,\n AuthRedirectParams,\n} from \"./core/adapter-types\";\n"],"mappings":"AAGA,SAAS,qBAAqB;AAI9B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,aAAa;AACtB,SAAS,iBAAiB;AAE1B,SAAS,0BAA0B;AACnC;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP,SAAS,qBAAqB;AAG9B,SAAS,SAAS,UAAU,eAAe;
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["// Server-safe entry. Client components are reached through the files that\n// carry their own \"use client\" directive, so importing this from a Server\n// Component does not pull the editor into the client bundle.\nexport { withGuardrail } from \"./server/wrapper\";\n\n// Team management and the set-password step, exported so an app can build\n// its own UI instead of using the bundled drawer / sign-in bar.\nexport {\n listTeam,\n inviteMember,\n updateMemberRole,\n removeMember,\n createAssetUpload,\n listAssets,\n setPasswordAction,\n skipSetPasswordAction,\n forgotPasswordAction,\n sendPasswordResetAction,\n setFieldValue,\n} from \"./server/actions\";\nexport { GRail } from \"./react/GRail\";\nexport { GRepeater } from \"./react/Repeater\";\nexport type { GRepeaterProps, RepeaterItemProps } from \"./react/Repeater\";\nexport { configureGuardrail } from \"./core/config\";\nexport {\n listRepeaterItems,\n getRepeaterItemBySlug,\n} from \"./server/repeaterData\";\nexport { getFieldValue } from \"./server/fieldData\";\n// Role / edit-mode checks for a custom admin UI and the Server Actions behind\n// it. Plain server functions, not actions — see access.ts for why.\nexport { isAdmin, isEditor, canEdit } from \"./server/access\";\n// Invite delivery through Resend, as an onInvite handler — see email/resend.ts.\nexport { resendInvite, resendEmails } from \"./email/resend\";\nexport type {\n ResendInviteOptions,\n ResendEmailOptions,\n ResendInviteContent,\n ResendInviteTemplateEvent,\n ResendResetTemplateEvent,\n} from \"./email/resend\";\n// Mutations, exported so an app can build its own add/remove/reorder UI\n// instead of the bundled chrome — same rationale as the team actions above.\nexport {\n addRepeaterItem,\n removeRepeaterItem,\n reorderRepeaterItems,\n} from \"./server/repeaterActions\";\nexport { useGRail } from \"./react/Provider\";\n\nexport type {\n GRState,\n GRRole,\n GRMember,\n GRViewer,\n GRAsset,\n GRRepeaterItem,\n ActionState,\n SetPasswordState,\n OverrideRow,\n OverrideValue,\n OverrideType,\n PolymorphicProps,\n HTMLKeys,\n} from \"./core/types\";\nexport type {\n GRConfig,\n GRPrefetch,\n GRMembershipAdapter,\n GRInviteEvent,\n GRStorageAdapter,\n GRUploadTarget,\n GRRepeaterAdapter,\n GRDBAdapter,\n GRAuthAdapter,\n GRSession,\n GRStorageRow,\n AuthResult,\n AuthRedirectParams,\n GRPasswordResetEvent,\n} from \"./core/adapter-types\";\n"],"mappings":"AAGA,SAAS,qBAAqB;AAI9B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,aAAa;AACtB,SAAS,iBAAiB;AAE1B,SAAS,0BAA0B;AACnC;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP,SAAS,qBAAqB;AAG9B,SAAS,SAAS,UAAU,eAAe;AAE3C,SAAS,cAAc,oBAAoB;AAU3C;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,gBAAgB;","names":[]}
|
package/dist/react/Admin.js
CHANGED
|
@@ -2,19 +2,14 @@
|
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import React, { useActionState, useEffect, useState } from "react";
|
|
4
4
|
import { usePathname, useRouter } from "next/navigation";
|
|
5
|
-
import {
|
|
6
|
-
loginAction,
|
|
7
|
-
logoutAction,
|
|
8
|
-
setPasswordAction,
|
|
9
|
-
skipSetPasswordAction
|
|
10
|
-
} from "../server/actions";
|
|
5
|
+
import { loginAction, logoutAction } from "../server/actions";
|
|
11
6
|
import { useGRail } from "./Provider";
|
|
12
7
|
import { TeamDrawer } from "./Team";
|
|
8
|
+
import { AuthDrawer } from "./AuthDrawer";
|
|
13
9
|
import { CloseIcon, LogOutIcon, PeopleIcon } from "./Icons";
|
|
14
10
|
import { S, THEME_CSS, v } from "./styles";
|
|
15
11
|
import { GR_COOKIES, GR_EDIT_MODE_OFF_TTL_SECONDS } from "../core/defs";
|
|
16
12
|
const initialState = { email: "", password: "" };
|
|
17
|
-
const initialPasswordState = {};
|
|
18
13
|
function GRailAdmin(props) {
|
|
19
14
|
const { isEditMode } = useGRail();
|
|
20
15
|
if (!isEditMode) return null;
|
|
@@ -24,28 +19,33 @@ function GRailAdmin(props) {
|
|
|
24
19
|
] });
|
|
25
20
|
}
|
|
26
21
|
function AdminBody({ toolbarExtra }) {
|
|
27
|
-
const { isAuth, isEditMode, needsPassword, role, auth } = useGRail();
|
|
22
|
+
const { isAuth, isEditMode, needsPassword, authError, role, auth } = useGRail();
|
|
28
23
|
const [teamOpen, setTeamOpen] = useState(false);
|
|
24
|
+
const [forgotOpen, setForgotOpen] = useState(false);
|
|
25
|
+
const [errorDismissed, setErrorDismissed] = useState(false);
|
|
29
26
|
const [formState, formAction, isPending] = useActionState(
|
|
30
27
|
loginAction,
|
|
31
28
|
initialState
|
|
32
29
|
);
|
|
33
|
-
const [passwordState, passwordFormAction, isSettingPassword] = useActionState(
|
|
34
|
-
setPasswordAction,
|
|
35
|
-
initialPasswordState
|
|
36
|
-
);
|
|
37
30
|
const router = useRouter();
|
|
38
31
|
const pathname = usePathname();
|
|
39
32
|
useEffect(() => {
|
|
40
33
|
if (formState.success) router.refresh();
|
|
41
34
|
}, [formState.success, router]);
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const skipSetPassword = async () => {
|
|
46
|
-
await skipSetPasswordAction();
|
|
35
|
+
const dismissAuthError = () => {
|
|
36
|
+
document.cookie = `${GR_COOKIES.AUTH_ERROR}=; path=/; max-age=0; SameSite=Lax`;
|
|
37
|
+
setErrorDismissed(true);
|
|
47
38
|
router.refresh();
|
|
48
39
|
};
|
|
40
|
+
const linkErrorDrawer = authError && !errorDismissed ? /* @__PURE__ */ jsx(
|
|
41
|
+
AuthDrawer,
|
|
42
|
+
{
|
|
43
|
+
mode: "forgot",
|
|
44
|
+
title: "That link didn't work",
|
|
45
|
+
message: authError === "expired" ? "It has expired or was already used. Links work once. Enter your email to get a new one." : "We couldn't complete it. Enter your email to get a new link.",
|
|
46
|
+
onClose: dismissAuthError
|
|
47
|
+
}
|
|
48
|
+
) : null;
|
|
49
49
|
const exitEditMode = () => {
|
|
50
50
|
document.cookie = `${GR_COOKIES.EDIT_MODE}=false; path=/; max-age=${GR_EDIT_MODE_OFF_TTL_SECONDS}; SameSite=Lax`;
|
|
51
51
|
router.replace(pathname);
|
|
@@ -59,12 +59,76 @@ function AdminBody({ toolbarExtra }) {
|
|
|
59
59
|
if (!isEditMode) return null;
|
|
60
60
|
if (!isAuth) {
|
|
61
61
|
if (!auth.password) {
|
|
62
|
-
return /* @__PURE__ */
|
|
62
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
63
|
+
/* @__PURE__ */ jsx("div", { "data-guardrail": true, style: S.banner, children: /* @__PURE__ */ jsxs("div", { style: S.bannerForm, children: [
|
|
64
|
+
/* @__PURE__ */ jsxs("div", { style: S.brandStack, children: [
|
|
65
|
+
/* @__PURE__ */ jsx("span", { style: S.brand, children: "Guardrail" }),
|
|
66
|
+
!auth.signInUrl && /* @__PURE__ */ jsx("span", { style: { fontSize: 12, color: v("muted") }, children: "No sign-in method is configured for this site." })
|
|
67
|
+
] }),
|
|
68
|
+
auth.signInUrl && /* @__PURE__ */ jsx("a", { href: auth.signInUrl, style: { ...S.primaryBtn, textDecoration: "none" }, children: "Sign in" }),
|
|
69
|
+
/* @__PURE__ */ jsx(
|
|
70
|
+
"button",
|
|
71
|
+
{
|
|
72
|
+
type: "button",
|
|
73
|
+
style: { ...S.iconBtn, color: v("muted") },
|
|
74
|
+
onClick: exitEditMode,
|
|
75
|
+
title: "Not editing? Dismiss",
|
|
76
|
+
"aria-label": "Dismiss",
|
|
77
|
+
children: /* @__PURE__ */ jsx(CloseIcon, {})
|
|
78
|
+
}
|
|
79
|
+
)
|
|
80
|
+
] }) }),
|
|
81
|
+
linkErrorDrawer
|
|
82
|
+
] });
|
|
83
|
+
}
|
|
84
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
85
|
+
/* @__PURE__ */ jsx("div", { "data-guardrail": true, style: S.banner, children: /* @__PURE__ */ jsxs("form", { action: formAction, style: S.bannerForm, children: [
|
|
63
86
|
/* @__PURE__ */ jsxs("div", { style: S.brandStack, children: [
|
|
64
87
|
/* @__PURE__ */ jsx("span", { style: S.brand, children: "Guardrail" }),
|
|
65
|
-
|
|
88
|
+
formState.error && /* @__PURE__ */ jsx("span", { style: S.error, children: formState.error })
|
|
66
89
|
] }),
|
|
67
|
-
|
|
90
|
+
/* @__PURE__ */ jsx(
|
|
91
|
+
"input",
|
|
92
|
+
{
|
|
93
|
+
name: "email",
|
|
94
|
+
type: "email",
|
|
95
|
+
autoComplete: "email",
|
|
96
|
+
placeholder: "Email",
|
|
97
|
+
defaultValue: formState.email,
|
|
98
|
+
required: true,
|
|
99
|
+
style: S.input
|
|
100
|
+
}
|
|
101
|
+
),
|
|
102
|
+
/* @__PURE__ */ jsx(
|
|
103
|
+
"input",
|
|
104
|
+
{
|
|
105
|
+
name: "password",
|
|
106
|
+
type: "password",
|
|
107
|
+
autoComplete: "current-password",
|
|
108
|
+
placeholder: "Password",
|
|
109
|
+
required: true,
|
|
110
|
+
style: S.input
|
|
111
|
+
}
|
|
112
|
+
),
|
|
113
|
+
/* @__PURE__ */ jsx("button", { type: "submit", disabled: isPending, style: S.primaryBtn, children: isPending ? "Authenticating\u2026" : "Sign In" }),
|
|
114
|
+
/* @__PURE__ */ jsx(
|
|
115
|
+
"button",
|
|
116
|
+
{
|
|
117
|
+
type: "button",
|
|
118
|
+
onClick: () => setForgotOpen(true),
|
|
119
|
+
style: {
|
|
120
|
+
background: "none",
|
|
121
|
+
border: "none",
|
|
122
|
+
cursor: "pointer",
|
|
123
|
+
color: v("muted"),
|
|
124
|
+
fontSize: 12,
|
|
125
|
+
textDecoration: "underline",
|
|
126
|
+
padding: "0 4px",
|
|
127
|
+
whiteSpace: "nowrap"
|
|
128
|
+
},
|
|
129
|
+
children: "Forgot password?"
|
|
130
|
+
}
|
|
131
|
+
),
|
|
68
132
|
/* @__PURE__ */ jsx(
|
|
69
133
|
"button",
|
|
70
134
|
{
|
|
@@ -76,98 +140,32 @@ function AdminBody({ toolbarExtra }) {
|
|
|
76
140
|
children: /* @__PURE__ */ jsx(CloseIcon, {})
|
|
77
141
|
}
|
|
78
142
|
)
|
|
79
|
-
] }) })
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
/* @__PURE__ */ jsxs("div", { style: S.brandStack, children: [
|
|
83
|
-
/* @__PURE__ */ jsx("span", { style: S.brand, children: "Guardrail" }),
|
|
84
|
-
formState.error && /* @__PURE__ */ jsx("span", { style: S.error, children: formState.error })
|
|
85
|
-
] }),
|
|
86
|
-
/* @__PURE__ */ jsx(
|
|
87
|
-
"input",
|
|
88
|
-
{
|
|
89
|
-
name: "email",
|
|
90
|
-
type: "email",
|
|
91
|
-
autoComplete: "email",
|
|
92
|
-
placeholder: "Email",
|
|
93
|
-
defaultValue: formState.email,
|
|
94
|
-
required: true,
|
|
95
|
-
style: S.input
|
|
96
|
-
}
|
|
97
|
-
),
|
|
98
|
-
/* @__PURE__ */ jsx(
|
|
99
|
-
"input",
|
|
100
|
-
{
|
|
101
|
-
name: "password",
|
|
102
|
-
type: "password",
|
|
103
|
-
autoComplete: "current-password",
|
|
104
|
-
placeholder: "Password",
|
|
105
|
-
required: true,
|
|
106
|
-
style: S.input
|
|
107
|
-
}
|
|
108
|
-
),
|
|
109
|
-
/* @__PURE__ */ jsx("button", { type: "submit", disabled: isPending, style: S.primaryBtn, children: isPending ? "Authenticating\u2026" : "Sign In" }),
|
|
110
|
-
/* @__PURE__ */ jsx(
|
|
111
|
-
"button",
|
|
112
|
-
{
|
|
113
|
-
type: "button",
|
|
114
|
-
style: { ...S.iconBtn, color: v("muted") },
|
|
115
|
-
onClick: exitEditMode,
|
|
116
|
-
title: "Not editing? Dismiss",
|
|
117
|
-
"aria-label": "Dismiss",
|
|
118
|
-
children: /* @__PURE__ */ jsx(CloseIcon, {})
|
|
119
|
-
}
|
|
120
|
-
)
|
|
121
|
-
] }) });
|
|
122
|
-
}
|
|
123
|
-
if (needsPassword) {
|
|
124
|
-
return /* @__PURE__ */ jsx("div", { "data-guardrail": true, style: S.banner, children: /* @__PURE__ */ jsxs("form", { action: passwordFormAction, style: S.bannerForm, children: [
|
|
125
|
-
/* @__PURE__ */ jsxs("div", { style: S.brandStack, children: [
|
|
126
|
-
/* @__PURE__ */ jsx("span", { style: S.brand, children: "Guardrail" }),
|
|
127
|
-
passwordState.error ? /* @__PURE__ */ jsx("span", { style: S.error, children: passwordState.error }) : /* @__PURE__ */ jsx("span", { style: { fontSize: 12, color: v("muted") }, children: "Set a password to sign in next time" })
|
|
128
|
-
] }),
|
|
129
|
-
/* @__PURE__ */ jsx(
|
|
130
|
-
"input",
|
|
131
|
-
{
|
|
132
|
-
name: "password",
|
|
133
|
-
type: "password",
|
|
134
|
-
placeholder: "New password",
|
|
135
|
-
required: true,
|
|
136
|
-
minLength: 6,
|
|
137
|
-
style: S.input
|
|
138
|
-
}
|
|
139
|
-
),
|
|
140
|
-
/* @__PURE__ */ jsx("button", { type: "submit", disabled: isSettingPassword, style: S.primaryBtn, children: isSettingPassword ? "Saving\u2026" : "Set Password" }),
|
|
141
|
-
/* @__PURE__ */ jsx(
|
|
142
|
-
"button",
|
|
143
|
-
{
|
|
144
|
-
type: "button",
|
|
145
|
-
style: { ...S.iconBtn, color: v("muted") },
|
|
146
|
-
onClick: skipSetPassword,
|
|
147
|
-
title: "Skip for now",
|
|
148
|
-
"aria-label": "Skip",
|
|
149
|
-
children: /* @__PURE__ */ jsx(CloseIcon, {})
|
|
150
|
-
}
|
|
151
|
-
)
|
|
152
|
-
] }) });
|
|
143
|
+
] }) }),
|
|
144
|
+
linkErrorDrawer ?? (forgotOpen && /* @__PURE__ */ jsx(AuthDrawer, { mode: "forgot", onClose: () => setForgotOpen(false) }))
|
|
145
|
+
] });
|
|
153
146
|
}
|
|
147
|
+
if (needsPassword) return /* @__PURE__ */ jsx(AuthDrawer, { mode: "set-password" });
|
|
154
148
|
if (!role) {
|
|
155
|
-
return /* @__PURE__ */ jsxs(
|
|
156
|
-
/* @__PURE__ */
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
149
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
150
|
+
/* @__PURE__ */ jsxs("div", { "data-guardrail": true, style: S.toolbar, children: [
|
|
151
|
+
/* @__PURE__ */ jsx("span", { style: S.toolbarLabel, children: "No access to this site" }),
|
|
152
|
+
/* @__PURE__ */ jsx("span", { style: S.divider }),
|
|
153
|
+
/* @__PURE__ */ jsx(
|
|
154
|
+
"button",
|
|
155
|
+
{
|
|
156
|
+
style: { ...S.iconBtn, color: v("muted") },
|
|
157
|
+
onClick: signOut,
|
|
158
|
+
title: "Sign out",
|
|
159
|
+
"aria-label": "Sign out",
|
|
160
|
+
children: /* @__PURE__ */ jsx(LogOutIcon, {})
|
|
161
|
+
}
|
|
162
|
+
)
|
|
163
|
+
] }),
|
|
164
|
+
linkErrorDrawer
|
|
168
165
|
] });
|
|
169
166
|
}
|
|
170
167
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
168
|
+
linkErrorDrawer,
|
|
171
169
|
teamOpen && /* @__PURE__ */ jsx(TeamDrawer, { onClose: () => setTeamOpen(false) }),
|
|
172
170
|
/* @__PURE__ */ jsxs("div", { "data-guardrail": true, style: S.toolbar, children: [
|
|
173
171
|
/* @__PURE__ */ jsx("span", { style: S.dot }),
|
package/dist/react/Admin.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/react/Admin.tsx"],"sourcesContent":["\"use client\";\n\nimport React, { useActionState, useEffect, useState } from \"react\";\nimport { usePathname, useRouter } from \"next/navigation\";\nimport {\n loginAction,\n logoutAction,\n setPasswordAction,\n skipSetPasswordAction,\n} from \"../server/actions\";\nimport { useGRail } from \"./Provider\";\nimport { TeamDrawer } from \"./Team\";\nimport { CloseIcon, LogOutIcon, PeopleIcon } from \"./Icons\";\nimport { S, THEME_CSS, v } from \"./styles\";\nimport { GR_COOKIES, GR_EDIT_MODE_OFF_TTL_SECONDS } from \"../core/defs\";\nimport type { ActionState, SetPasswordState } from \"../core/types\";\n\nconst initialState: ActionState = { email: \"\", password: \"\" };\nconst initialPasswordState: SetPasswordState = {};\n\n/**\n * Owns the theme stylesheet so every piece of chrome — the sign-in bar, the\n * toolbar, and the field popups portaled to <body> — picks up the same\n * light/dark variables from one place.\n */\nexport function GRailAdmin(props: { toolbarExtra?: React.ReactNode }) {\n const { isEditMode } = useGRail();\n if (!isEditMode) return null;\n return (\n <>\n {/*\n precedence + href make React hoist this into <head> and dedupe it.\n Required, not cosmetic: this whole component renders as a sibling of\n <html> (withGuardrail can't reach inside the app's own layout), and\n React 19 rejects a bare <style> anywhere outside the main document.\n */}\n <style href=\"guardrail-theme\" precedence=\"default\">\n {THEME_CSS}\n </style>\n <AdminBody {...props} />\n </>\n );\n}\n\nfunction AdminBody({ toolbarExtra }: { toolbarExtra?: React.ReactNode }) {\n const { isAuth, isEditMode, needsPassword, role, auth } = useGRail();\n const [teamOpen, setTeamOpen] = useState(false);\n const [formState, formAction, isPending] = useActionState(\n loginAction,\n initialState,\n );\n const [passwordState, passwordFormAction, isSettingPassword] = useActionState(\n setPasswordAction,\n initialPasswordState,\n );\n const router = useRouter();\n const pathname = usePathname();\n\n useEffect(() => {\n if (formState.success) router.refresh();\n }, [formState.success, router]);\n\n useEffect(() => {\n if (passwordState.success) router.refresh();\n }, [passwordState.success, router]);\n\n const skipSetPassword = async () => {\n await skipSetPasswordAction();\n router.refresh();\n };\n\n // Ends the editing session, not the sign-in: the overlay goes away and the\n // person's session stays, so ?edit=true brings it straight back with no\n // credentials. The sign-in form only returns once the session itself is\n // gone (expired, or ended elsewhere). Explicit \"false\" rather than deleting\n // the cookie: with a live session, deleting it would leave edit mode on —\n // see resolveEditMode. router.replace drops ?edit=true from the URL (a bare\n // refresh would leave it there), and refresh re-renders without chrome.\n //\n // Also what the sign-in bar's dismiss button does, for someone who was never\n // authenticated and doesn't want to be here at all.\n const exitEditMode = () => {\n document.cookie = `${GR_COOKIES.EDIT_MODE}=false; path=/; max-age=${GR_EDIT_MODE_OFF_TTL_SECONDS}; SameSite=Lax`;\n router.replace(pathname);\n router.refresh();\n };\n\n // A real sign-out, kept only for the \"no access\" bar below: someone on the\n // wrong account has no other way to switch to the right one. logoutAction()\n // clears the edit-mode cookie itself — a client-side navigation right after\n // it used to race Next's own post-action route refresh and lose.\n const signOut = async () => {\n await logoutAction();\n router.replace(pathname);\n router.refresh();\n };\n\n // No intent signalled — render nothing at all.\n if (!isEditMode) return null;\n\n if (!isAuth) {\n // The adapter cannot take a password here, so hand sign-in off rather than\n // showing a form that could never succeed.\n if (!auth.password) {\n return (\n <div data-guardrail style={S.banner}>\n <div style={S.bannerForm}>\n <div style={S.brandStack}>\n <span style={S.brand}>Guardrail</span>\n {!auth.signInUrl && (\n <span style={{ fontSize: 12, color: v(\"muted\") }}>\n No sign-in method is configured for this site.\n </span>\n )}\n </div>\n {auth.signInUrl && (\n <a href={auth.signInUrl} style={{ ...S.primaryBtn, textDecoration: \"none\" }}>\n Sign in\n </a>\n )}\n <button\n type=\"button\"\n style={{ ...S.iconBtn, color: v(\"muted\") }}\n onClick={exitEditMode}\n title=\"Not editing? Dismiss\"\n aria-label=\"Dismiss\"\n >\n <CloseIcon />\n </button>\n </div>\n </div>\n );\n }\n\n return (\n <div data-guardrail style={S.banner}>\n <form action={formAction} style={S.bannerForm}>\n <div style={S.brandStack}>\n <span style={S.brand}>Guardrail</span>\n {formState.error && <span style={S.error}>{formState.error}</span>}\n </div>\n <input\n name=\"email\"\n type=\"email\"\n autoComplete=\"email\"\n placeholder=\"Email\"\n defaultValue={formState.email}\n required\n style={S.input}\n />\n <input\n name=\"password\"\n type=\"password\"\n autoComplete=\"current-password\"\n placeholder=\"Password\"\n required\n style={S.input}\n />\n <button type=\"submit\" disabled={isPending} style={S.primaryBtn}>\n {isPending ? \"Authenticating…\" : \"Sign In\"}\n </button>\n <button\n type=\"button\"\n style={{ ...S.iconBtn, color: v(\"muted\") }}\n onClick={exitEditMode}\n title=\"Not editing? Dismiss\"\n aria-label=\"Dismiss\"\n >\n <CloseIcon />\n </button>\n </form>\n </div>\n );\n }\n\n // A redirect-carried session (an invite or password-recovery link) just\n // completed — they're genuinely signed in, but have no password yet.\n // Ahead of the role check below: getCachedViewer() already claims a\n // pending invite as a side effect of resolving them, so role is normally\n // already set by the time this renders — this is purely about the\n // password, which is orthogonal to that. Skippable rather than a hard\n // gate: magic-link sign-in already works with no password at all, so\n // someone can dismiss this and keep using invite/magic links indefinitely.\n if (needsPassword) {\n return (\n <div data-guardrail style={S.banner}>\n <form action={passwordFormAction} style={S.bannerForm}>\n <div style={S.brandStack}>\n <span style={S.brand}>Guardrail</span>\n {passwordState.error ? (\n <span style={S.error}>{passwordState.error}</span>\n ) : (\n <span style={{ fontSize: 12, color: v(\"muted\") }}>\n Set a password to sign in next time\n </span>\n )}\n </div>\n <input\n name=\"password\"\n type=\"password\"\n placeholder=\"New password\"\n required\n minLength={6}\n style={S.input}\n />\n <button type=\"submit\" disabled={isSettingPassword} style={S.primaryBtn}>\n {isSettingPassword ? \"Saving…\" : \"Set Password\"}\n </button>\n <button\n type=\"button\"\n style={{ ...S.iconBtn, color: v(\"muted\") }}\n onClick={skipSetPassword}\n title=\"Skip for now\"\n aria-label=\"Skip\"\n >\n <CloseIcon />\n </button>\n </form>\n </div>\n );\n }\n\n // Signed in, but holds no role on this site.\n if (!role) {\n return (\n <div data-guardrail style={S.toolbar}>\n <span style={S.toolbarLabel}>No access to this site</span>\n <span style={S.divider} />\n <button\n style={{ ...S.iconBtn, color: v(\"muted\") }}\n onClick={signOut}\n title=\"Sign out\"\n aria-label=\"Sign out\"\n >\n <LogOutIcon />\n </button>\n </div>\n );\n }\n\n return (\n <>\n {teamOpen && <TeamDrawer onClose={() => setTeamOpen(false)} />}\n <div data-guardrail style={S.toolbar}>\n <span style={S.dot} />\n <span style={S.toolbarLabel}>Edit Mode</span>\n <span style={S.divider} />\n <button\n style={{ ...S.iconBtn, color: v(\"muted\"), width: 24, height: 24 }}\n onClick={() => setTeamOpen(true)}\n title=\"Team\"\n aria-label=\"Team\"\n >\n <PeopleIcon />\n </button>\n\n {toolbarExtra && (\n <>\n <span style={S.divider} />\n {toolbarExtra}\n </>\n )}\n\n <span style={S.divider} />\n <button\n style={{ ...S.iconBtn, color: v(\"muted\") }}\n onClick={exitEditMode}\n title=\"Exit edit mode\"\n aria-label=\"Exit edit mode\"\n >\n <LogOutIcon />\n </button>\n </div>\n </>\n );\n}\n"],"mappings":";AA6BI,mBAOE,KAPF;AA3BJ,OAAO,SAAS,gBAAgB,WAAW,gBAAgB;AAC3D,SAAS,aAAa,iBAAiB;AACvC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,gBAAgB;AACzB,SAAS,kBAAkB;AAC3B,SAAS,WAAW,YAAY,kBAAkB;AAClD,SAAS,GAAG,WAAW,SAAS;AAChC,SAAS,YAAY,oCAAoC;AAGzD,MAAM,eAA4B,EAAE,OAAO,IAAI,UAAU,GAAG;AAC5D,MAAM,uBAAyC,CAAC;AAOzC,SAAS,WAAW,OAA2C;AACpE,QAAM,EAAE,WAAW,IAAI,SAAS;AAChC,MAAI,CAAC,WAAY,QAAO;AACxB,SACE,iCAOE;AAAA,wBAAC,WAAM,MAAK,mBAAkB,YAAW,WACtC,qBACH;AAAA,IACA,oBAAC,aAAW,GAAG,OAAO;AAAA,KACxB;AAEJ;AAEA,SAAS,UAAU,EAAE,aAAa,GAAuC;AACvE,QAAM,EAAE,QAAQ,YAAY,eAAe,MAAM,KAAK,IAAI,SAAS;AACnE,QAAM,CAAC,UAAU,WAAW,IAAI,SAAS,KAAK;AAC9C,QAAM,CAAC,WAAW,YAAY,SAAS,IAAI;AAAA,IACzC;AAAA,IACA;AAAA,EACF;AACA,QAAM,CAAC,eAAe,oBAAoB,iBAAiB,IAAI;AAAA,IAC7D;AAAA,IACA;AAAA,EACF;AACA,QAAM,SAAS,UAAU;AACzB,QAAM,WAAW,YAAY;AAE7B,YAAU,MAAM;AACd,QAAI,UAAU,QAAS,QAAO,QAAQ;AAAA,EACxC,GAAG,CAAC,UAAU,SAAS,MAAM,CAAC;AAE9B,YAAU,MAAM;AACd,QAAI,cAAc,QAAS,QAAO,QAAQ;AAAA,EAC5C,GAAG,CAAC,cAAc,SAAS,MAAM,CAAC;AAElC,QAAM,kBAAkB,YAAY;AAClC,UAAM,sBAAsB;AAC5B,WAAO,QAAQ;AAAA,EACjB;AAYA,QAAM,eAAe,MAAM;AACzB,aAAS,SAAS,GAAG,WAAW,SAAS,2BAA2B,4BAA4B;AAChG,WAAO,QAAQ,QAAQ;AACvB,WAAO,QAAQ;AAAA,EACjB;AAMA,QAAM,UAAU,YAAY;AAC1B,UAAM,aAAa;AACnB,WAAO,QAAQ,QAAQ;AACvB,WAAO,QAAQ;AAAA,EACjB;AAGA,MAAI,CAAC,WAAY,QAAO;AAExB,MAAI,CAAC,QAAQ;AAGX,QAAI,CAAC,KAAK,UAAU;AAClB,aACE,oBAAC,SAAI,kBAAc,MAAC,OAAO,EAAE,QAC3B,+BAAC,SAAI,OAAO,EAAE,YACZ;AAAA,6BAAC,SAAI,OAAO,EAAE,YACZ;AAAA,8BAAC,UAAK,OAAO,EAAE,OAAO,uBAAS;AAAA,UAC9B,CAAC,KAAK,aACL,oBAAC,UAAK,OAAO,EAAE,UAAU,IAAI,OAAO,EAAE,OAAO,EAAE,GAAG,4DAElD;AAAA,WAEJ;AAAA,QACC,KAAK,aACJ,oBAAC,OAAE,MAAM,KAAK,WAAW,OAAO,EAAE,GAAG,EAAE,YAAY,gBAAgB,OAAO,GAAG,qBAE7E;AAAA,QAEF;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,OAAO,EAAE,GAAG,EAAE,SAAS,OAAO,EAAE,OAAO,EAAE;AAAA,YACzC,SAAS;AAAA,YACT,OAAM;AAAA,YACN,cAAW;AAAA,YAEX,8BAAC,aAAU;AAAA;AAAA,QACb;AAAA,SACF,GACF;AAAA,IAEJ;AAEA,WACE,oBAAC,SAAI,kBAAc,MAAC,OAAO,EAAE,QAC3B,+BAAC,UAAK,QAAQ,YAAY,OAAO,EAAE,YACjC;AAAA,2BAAC,SAAI,OAAO,EAAE,YACZ;AAAA,4BAAC,UAAK,OAAO,EAAE,OAAO,uBAAS;AAAA,QAC9B,UAAU,SAAS,oBAAC,UAAK,OAAO,EAAE,OAAQ,oBAAU,OAAM;AAAA,SAC7D;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,MAAK;AAAA,UACL,cAAa;AAAA,UACb,aAAY;AAAA,UACZ,cAAc,UAAU;AAAA,UACxB,UAAQ;AAAA,UACR,OAAO,EAAE;AAAA;AAAA,MACX;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,MAAK;AAAA,UACL,cAAa;AAAA,UACb,aAAY;AAAA,UACZ,UAAQ;AAAA,UACR,OAAO,EAAE;AAAA;AAAA,MACX;AAAA,MACA,oBAAC,YAAO,MAAK,UAAS,UAAU,WAAW,OAAO,EAAE,YACjD,sBAAY,yBAAoB,WACnC;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,OAAO,EAAE,GAAG,EAAE,SAAS,OAAO,EAAE,OAAO,EAAE;AAAA,UACzC,SAAS;AAAA,UACT,OAAM;AAAA,UACN,cAAW;AAAA,UAEX,8BAAC,aAAU;AAAA;AAAA,MACb;AAAA,OACF,GACF;AAAA,EAEJ;AAUA,MAAI,eAAe;AACjB,WACE,oBAAC,SAAI,kBAAc,MAAC,OAAO,EAAE,QAC3B,+BAAC,UAAK,QAAQ,oBAAoB,OAAO,EAAE,YACzC;AAAA,2BAAC,SAAI,OAAO,EAAE,YACZ;AAAA,4BAAC,UAAK,OAAO,EAAE,OAAO,uBAAS;AAAA,QAC9B,cAAc,QACb,oBAAC,UAAK,OAAO,EAAE,OAAQ,wBAAc,OAAM,IAE3C,oBAAC,UAAK,OAAO,EAAE,UAAU,IAAI,OAAO,EAAE,OAAO,EAAE,GAAG,iDAElD;AAAA,SAEJ;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,MAAK;AAAA,UACL,aAAY;AAAA,UACZ,UAAQ;AAAA,UACR,WAAW;AAAA,UACX,OAAO,EAAE;AAAA;AAAA,MACX;AAAA,MACA,oBAAC,YAAO,MAAK,UAAS,UAAU,mBAAmB,OAAO,EAAE,YACzD,8BAAoB,iBAAY,gBACnC;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,OAAO,EAAE,GAAG,EAAE,SAAS,OAAO,EAAE,OAAO,EAAE;AAAA,UACzC,SAAS;AAAA,UACT,OAAM;AAAA,UACN,cAAW;AAAA,UAEX,8BAAC,aAAU;AAAA;AAAA,MACb;AAAA,OACF,GACF;AAAA,EAEJ;AAGA,MAAI,CAAC,MAAM;AACT,WACE,qBAAC,SAAI,kBAAc,MAAC,OAAO,EAAE,SAC3B;AAAA,0BAAC,UAAK,OAAO,EAAE,cAAc,oCAAsB;AAAA,MACnD,oBAAC,UAAK,OAAO,EAAE,SAAS;AAAA,MACxB;AAAA,QAAC;AAAA;AAAA,UACC,OAAO,EAAE,GAAG,EAAE,SAAS,OAAO,EAAE,OAAO,EAAE;AAAA,UACzC,SAAS;AAAA,UACT,OAAM;AAAA,UACN,cAAW;AAAA,UAEX,8BAAC,cAAW;AAAA;AAAA,MACd;AAAA,OACF;AAAA,EAEJ;AAEA,SACE,iCACG;AAAA,gBAAY,oBAAC,cAAW,SAAS,MAAM,YAAY,KAAK,GAAG;AAAA,IAC5D,qBAAC,SAAI,kBAAc,MAAC,OAAO,EAAE,SAC3B;AAAA,0BAAC,UAAK,OAAO,EAAE,KAAK;AAAA,MACpB,oBAAC,UAAK,OAAO,EAAE,cAAc,uBAAS;AAAA,MACtC,oBAAC,UAAK,OAAO,EAAE,SAAS;AAAA,MACxB;AAAA,QAAC;AAAA;AAAA,UACC,OAAO,EAAE,GAAG,EAAE,SAAS,OAAO,EAAE,OAAO,GAAG,OAAO,IAAI,QAAQ,GAAG;AAAA,UAChE,SAAS,MAAM,YAAY,IAAI;AAAA,UAC/B,OAAM;AAAA,UACN,cAAW;AAAA,UAEX,8BAAC,cAAW;AAAA;AAAA,MACd;AAAA,MAEC,gBACC,iCACE;AAAA,4BAAC,UAAK,OAAO,EAAE,SAAS;AAAA,QACvB;AAAA,SACH;AAAA,MAGF,oBAAC,UAAK,OAAO,EAAE,SAAS;AAAA,MACxB;AAAA,QAAC;AAAA;AAAA,UACC,OAAO,EAAE,GAAG,EAAE,SAAS,OAAO,EAAE,OAAO,EAAE;AAAA,UACzC,SAAS;AAAA,UACT,OAAM;AAAA,UACN,cAAW;AAAA,UAEX,8BAAC,cAAW;AAAA;AAAA,MACd;AAAA,OACF;AAAA,KACF;AAEJ;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/react/Admin.tsx"],"sourcesContent":["\"use client\";\n\nimport React, { useActionState, useEffect, useState } from \"react\";\nimport { usePathname, useRouter } from \"next/navigation\";\nimport { loginAction, logoutAction } from \"../server/actions\";\nimport { useGRail } from \"./Provider\";\nimport { TeamDrawer } from \"./Team\";\nimport { AuthDrawer } from \"./AuthDrawer\";\nimport { CloseIcon, LogOutIcon, PeopleIcon } from \"./Icons\";\nimport { S, THEME_CSS, v } from \"./styles\";\nimport { GR_COOKIES, GR_EDIT_MODE_OFF_TTL_SECONDS } from \"../core/defs\";\nimport type { ActionState } from \"../core/types\";\n\nconst initialState: ActionState = { email: \"\", password: \"\" };\n\n/**\n * Owns the theme stylesheet so every piece of chrome — the sign-in bar, the\n * toolbar, and the field popups portaled to <body> — picks up the same\n * light/dark variables from one place.\n */\nexport function GRailAdmin(props: { toolbarExtra?: React.ReactNode }) {\n const { isEditMode } = useGRail();\n if (!isEditMode) return null;\n return (\n <>\n {/*\n precedence + href make React hoist this into <head> and dedupe it.\n Required, not cosmetic: this whole component renders as a sibling of\n <html> (withGuardrail can't reach inside the app's own layout), and\n React 19 rejects a bare <style> anywhere outside the main document.\n */}\n <style href=\"guardrail-theme\" precedence=\"default\">\n {THEME_CSS}\n </style>\n <AdminBody {...props} />\n </>\n );\n}\n\nfunction AdminBody({ toolbarExtra }: { toolbarExtra?: React.ReactNode }) {\n const { isAuth, isEditMode, needsPassword, authError, role, auth } = useGRail();\n const [teamOpen, setTeamOpen] = useState(false);\n const [forgotOpen, setForgotOpen] = useState(false);\n const [errorDismissed, setErrorDismissed] = useState(false);\n const [formState, formAction, isPending] = useActionState(\n loginAction,\n initialState,\n );\n const router = useRouter();\n const pathname = usePathname();\n\n useEffect(() => {\n if (formState.success) router.refresh();\n }, [formState.success, router]);\n\n // The message for an emailed link that didn't work. The cookie behind it is\n // read on the server, so dismissing clears it there too (and refreshes), but\n // hides it right away without waiting for that.\n const dismissAuthError = () => {\n document.cookie = `${GR_COOKIES.AUTH_ERROR}=; path=/; max-age=0; SameSite=Lax`;\n setErrorDismissed(true);\n router.refresh();\n };\n const linkErrorDrawer =\n authError && !errorDismissed ? (\n <AuthDrawer\n mode=\"forgot\"\n title=\"That link didn't work\"\n message={\n authError === \"expired\"\n ? \"It has expired or was already used. Links work once. Enter your email to get a new one.\"\n : \"We couldn't complete it. Enter your email to get a new link.\"\n }\n onClose={dismissAuthError}\n />\n ) : null;\n\n // Ends the editing session, not the sign-in: the overlay goes away and the\n // person's session stays, so ?edit=true brings it straight back with no\n // credentials. The sign-in form only returns once the session itself is\n // gone (expired, or ended elsewhere). Explicit \"false\" rather than deleting\n // the cookie: with a live session, deleting it would leave edit mode on —\n // see resolveEditMode. router.replace drops ?edit=true from the URL (a bare\n // refresh would leave it there), and refresh re-renders without chrome.\n //\n // Also what the sign-in bar's dismiss button does, for someone who was never\n // authenticated and doesn't want to be here at all.\n const exitEditMode = () => {\n document.cookie = `${GR_COOKIES.EDIT_MODE}=false; path=/; max-age=${GR_EDIT_MODE_OFF_TTL_SECONDS}; SameSite=Lax`;\n router.replace(pathname);\n router.refresh();\n };\n\n // A real sign-out, kept only for the \"no access\" bar below: someone on the\n // wrong account has no other way to switch to the right one. logoutAction()\n // clears the edit-mode cookie itself — a client-side navigation right after\n // it used to race Next's own post-action route refresh and lose.\n const signOut = async () => {\n await logoutAction();\n router.replace(pathname);\n router.refresh();\n };\n\n // No intent signalled — render nothing at all.\n if (!isEditMode) return null;\n\n if (!isAuth) {\n // The adapter cannot take a password here, so hand sign-in off rather than\n // showing a form that could never succeed.\n if (!auth.password) {\n return (\n <>\n <div data-guardrail style={S.banner}>\n <div style={S.bannerForm}>\n <div style={S.brandStack}>\n <span style={S.brand}>Guardrail</span>\n {!auth.signInUrl && (\n <span style={{ fontSize: 12, color: v(\"muted\") }}>\n No sign-in method is configured for this site.\n </span>\n )}\n </div>\n {auth.signInUrl && (\n <a href={auth.signInUrl} style={{ ...S.primaryBtn, textDecoration: \"none\" }}>\n Sign in\n </a>\n )}\n <button\n type=\"button\"\n style={{ ...S.iconBtn, color: v(\"muted\") }}\n onClick={exitEditMode}\n title=\"Not editing? Dismiss\"\n aria-label=\"Dismiss\"\n >\n <CloseIcon />\n </button>\n </div>\n </div>\n {linkErrorDrawer}\n </>\n );\n }\n\n return (\n <>\n <div data-guardrail style={S.banner}>\n <form action={formAction} style={S.bannerForm}>\n <div style={S.brandStack}>\n <span style={S.brand}>Guardrail</span>\n {formState.error && <span style={S.error}>{formState.error}</span>}\n </div>\n <input\n name=\"email\"\n type=\"email\"\n autoComplete=\"email\"\n placeholder=\"Email\"\n defaultValue={formState.email}\n required\n style={S.input}\n />\n <input\n name=\"password\"\n type=\"password\"\n autoComplete=\"current-password\"\n placeholder=\"Password\"\n required\n style={S.input}\n />\n <button type=\"submit\" disabled={isPending} style={S.primaryBtn}>\n {isPending ? \"Authenticating…\" : \"Sign In\"}\n </button>\n <button\n type=\"button\"\n onClick={() => setForgotOpen(true)}\n style={{\n background: \"none\",\n border: \"none\",\n cursor: \"pointer\",\n color: v(\"muted\"),\n fontSize: 12,\n textDecoration: \"underline\",\n padding: \"0 4px\",\n whiteSpace: \"nowrap\",\n }}\n >\n Forgot password?\n </button>\n <button\n type=\"button\"\n style={{ ...S.iconBtn, color: v(\"muted\") }}\n onClick={exitEditMode}\n title=\"Not editing? Dismiss\"\n aria-label=\"Dismiss\"\n >\n <CloseIcon />\n </button>\n </form>\n </div>\n {linkErrorDrawer ??\n (forgotOpen && <AuthDrawer mode=\"forgot\" onClose={() => setForgotOpen(false)} />)}\n </>\n );\n }\n\n // An invite or password-reset link just completed: they're signed in, but must\n // choose a password before anything else. Ahead of the role check below —\n // getCachedViewer() already claims a pending invite as a side effect of\n // resolving them, so the role is normally set by now; this is purely about the\n // password. Not skippable: a password is the only way back in once this\n // session ends, so the overlay and toolbar wait until one exists.\n if (needsPassword) return <AuthDrawer mode=\"set-password\" />;\n\n // Signed in, but holds no role on this site.\n if (!role) {\n return (\n <>\n <div data-guardrail style={S.toolbar}>\n <span style={S.toolbarLabel}>No access to this site</span>\n <span style={S.divider} />\n <button\n style={{ ...S.iconBtn, color: v(\"muted\") }}\n onClick={signOut}\n title=\"Sign out\"\n aria-label=\"Sign out\"\n >\n <LogOutIcon />\n </button>\n </div>\n {linkErrorDrawer}\n </>\n );\n }\n\n return (\n <>\n {linkErrorDrawer}\n {teamOpen && <TeamDrawer onClose={() => setTeamOpen(false)} />}\n <div data-guardrail style={S.toolbar}>\n <span style={S.dot} />\n <span style={S.toolbarLabel}>Edit Mode</span>\n <span style={S.divider} />\n <button\n style={{ ...S.iconBtn, color: v(\"muted\"), width: 24, height: 24 }}\n onClick={() => setTeamOpen(true)}\n title=\"Team\"\n aria-label=\"Team\"\n >\n <PeopleIcon />\n </button>\n\n {toolbarExtra && (\n <>\n <span style={S.divider} />\n {toolbarExtra}\n </>\n )}\n\n <span style={S.divider} />\n <button\n style={{ ...S.iconBtn, color: v(\"muted\") }}\n onClick={exitEditMode}\n title=\"Exit edit mode\"\n aria-label=\"Exit edit mode\"\n >\n <LogOutIcon />\n </button>\n </div>\n </>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The two password steps, in the same drawer the Team panel uses.
|
|
5
|
+
*
|
|
6
|
+
* Guardrail asks people for one credential, a password. Emailed links exist only
|
|
7
|
+
* to let someone choose or replace it, so this is where they land:
|
|
8
|
+
*
|
|
9
|
+
* - "set-password" — after an invite or reset link. Cannot be dismissed: until a
|
|
10
|
+
* password exists there is nothing to sign in with next time. The one way out
|
|
11
|
+
* is to sign out.
|
|
12
|
+
* - "forgot" — asks for an address and emails a reset link. Opened from the
|
|
13
|
+
* sign-in bar, and from the "that link didn't work" message with `message`.
|
|
14
|
+
*/
|
|
15
|
+
declare function AuthDrawer({ mode, title, message, onClose, }: {
|
|
16
|
+
mode: "set-password" | "forgot";
|
|
17
|
+
title?: string;
|
|
18
|
+
/** Plain-language context shown above the form, e.g. why a link failed. */
|
|
19
|
+
message?: string;
|
|
20
|
+
onClose?: () => void;
|
|
21
|
+
}): react_jsx_runtime.JSX.Element;
|
|
22
|
+
|
|
23
|
+
export { AuthDrawer };
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import React, { useActionState, useEffect, useState, useTransition } from "react";
|
|
4
|
+
import { useRouter } from "next/navigation";
|
|
5
|
+
import { forgotPasswordAction, logoutAction, setPasswordAction } from "../server/actions";
|
|
6
|
+
import { CloseIcon } from "./Icons";
|
|
7
|
+
import { S, v } from "./styles";
|
|
8
|
+
import { ANIMATION_CSS } from "./Team";
|
|
9
|
+
const initialPasswordState = {};
|
|
10
|
+
function AuthDrawer({
|
|
11
|
+
mode,
|
|
12
|
+
title,
|
|
13
|
+
message,
|
|
14
|
+
onClose
|
|
15
|
+
}) {
|
|
16
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
17
|
+
/* @__PURE__ */ jsx("style", { href: "guardrail-team-animation", precedence: "default", children: ANIMATION_CSS }),
|
|
18
|
+
/* @__PURE__ */ jsx(
|
|
19
|
+
"div",
|
|
20
|
+
{
|
|
21
|
+
"data-guardrail": true,
|
|
22
|
+
"data-guardrail-scrim": true,
|
|
23
|
+
style: S.scrim,
|
|
24
|
+
onClick: mode === "forgot" ? onClose : void 0
|
|
25
|
+
}
|
|
26
|
+
),
|
|
27
|
+
/* @__PURE__ */ jsxs(
|
|
28
|
+
"aside",
|
|
29
|
+
{
|
|
30
|
+
"data-guardrail": true,
|
|
31
|
+
"data-guardrail-team": true,
|
|
32
|
+
style: S.drawer,
|
|
33
|
+
role: "dialog",
|
|
34
|
+
"aria-label": mode === "set-password" ? "Set your password" : "Forgot password",
|
|
35
|
+
children: [
|
|
36
|
+
/* @__PURE__ */ jsxs("header", { style: S.drawerHead, children: [
|
|
37
|
+
/* @__PURE__ */ jsx("h2", { style: S.drawerTitle, children: title ?? (mode === "set-password" ? "Set your password" : "Forgot your password?") }),
|
|
38
|
+
mode === "forgot" && onClose && /* @__PURE__ */ jsx("button", { style: { ...S.iconBtn, color: v("muted") }, onClick: onClose, "aria-label": "Close", children: /* @__PURE__ */ jsx(CloseIcon, {}) })
|
|
39
|
+
] }),
|
|
40
|
+
/* @__PURE__ */ jsxs("div", { style: S.drawerBody, children: [
|
|
41
|
+
message && /* @__PURE__ */ jsx("p", { style: { fontSize: 13, lineHeight: 1.5, margin: "0 0 16px", color: v("fg") }, children: message }),
|
|
42
|
+
mode === "set-password" ? /* @__PURE__ */ jsx(SetPasswordForm, {}) : /* @__PURE__ */ jsx(ForgotPasswordForm, {})
|
|
43
|
+
] })
|
|
44
|
+
]
|
|
45
|
+
}
|
|
46
|
+
)
|
|
47
|
+
] });
|
|
48
|
+
}
|
|
49
|
+
function SetPasswordForm() {
|
|
50
|
+
const router = useRouter();
|
|
51
|
+
const [state, formAction, isPending] = useActionState(setPasswordAction, initialPasswordState);
|
|
52
|
+
const [signingOut, startSignOut] = useTransition();
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
if (state.success) router.refresh();
|
|
55
|
+
}, [state.success, router]);
|
|
56
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
57
|
+
/* @__PURE__ */ jsx("p", { style: { fontSize: 13, lineHeight: 1.5, margin: "0 0 16px", color: v("muted") }, children: "Choose a password. It's how you'll sign in from now on." }),
|
|
58
|
+
/* @__PURE__ */ jsxs("form", { action: formAction, children: [
|
|
59
|
+
/* @__PURE__ */ jsx("label", { style: S.label, htmlFor: "gr-new-password", children: "New password" }),
|
|
60
|
+
/* @__PURE__ */ jsx(
|
|
61
|
+
"input",
|
|
62
|
+
{
|
|
63
|
+
id: "gr-new-password",
|
|
64
|
+
style: S.field,
|
|
65
|
+
name: "password",
|
|
66
|
+
type: "password",
|
|
67
|
+
autoComplete: "new-password",
|
|
68
|
+
minLength: 8,
|
|
69
|
+
required: true,
|
|
70
|
+
autoFocus: true
|
|
71
|
+
}
|
|
72
|
+
),
|
|
73
|
+
/* @__PURE__ */ jsx("label", { style: S.label, htmlFor: "gr-confirm-password", children: "Confirm password" }),
|
|
74
|
+
/* @__PURE__ */ jsx(
|
|
75
|
+
"input",
|
|
76
|
+
{
|
|
77
|
+
id: "gr-confirm-password",
|
|
78
|
+
style: S.field,
|
|
79
|
+
name: "confirm",
|
|
80
|
+
type: "password",
|
|
81
|
+
autoComplete: "new-password",
|
|
82
|
+
minLength: 8,
|
|
83
|
+
required: true
|
|
84
|
+
}
|
|
85
|
+
),
|
|
86
|
+
/* @__PURE__ */ jsx("p", { style: { fontSize: 11, color: v("subtle"), margin: "0 0 12px" }, children: "At least 8 characters." }),
|
|
87
|
+
state.error && /* @__PURE__ */ jsx("div", { style: { ...S.error, marginBottom: 12 }, children: state.error }),
|
|
88
|
+
/* @__PURE__ */ jsx("button", { style: { ...S.primaryBtn, width: "100%" }, type: "submit", disabled: isPending || signingOut, children: isPending ? "Saving\u2026" : "Set password" })
|
|
89
|
+
] }),
|
|
90
|
+
/* @__PURE__ */ jsx(
|
|
91
|
+
"button",
|
|
92
|
+
{
|
|
93
|
+
type: "button",
|
|
94
|
+
disabled: isPending || signingOut,
|
|
95
|
+
style: {
|
|
96
|
+
background: "none",
|
|
97
|
+
border: "none",
|
|
98
|
+
cursor: "pointer",
|
|
99
|
+
color: v("muted"),
|
|
100
|
+
fontSize: 12,
|
|
101
|
+
textDecoration: "underline",
|
|
102
|
+
padding: 0,
|
|
103
|
+
marginTop: 16
|
|
104
|
+
},
|
|
105
|
+
onClick: () => startSignOut(async () => {
|
|
106
|
+
await logoutAction();
|
|
107
|
+
router.refresh();
|
|
108
|
+
}),
|
|
109
|
+
children: "Cancel and sign out"
|
|
110
|
+
}
|
|
111
|
+
)
|
|
112
|
+
] });
|
|
113
|
+
}
|
|
114
|
+
function ForgotPasswordForm() {
|
|
115
|
+
const [email, setEmail] = useState("");
|
|
116
|
+
const [status, setStatus] = useState("idle");
|
|
117
|
+
const [error, setError] = useState(null);
|
|
118
|
+
if (status === "sent") {
|
|
119
|
+
return /* @__PURE__ */ jsxs("p", { style: { fontSize: 13, lineHeight: 1.5, margin: 0, color: v("fg") }, children: [
|
|
120
|
+
"If ",
|
|
121
|
+
/* @__PURE__ */ jsx("strong", { children: email.trim() }),
|
|
122
|
+
" has access to this site, a link to choose a new password is on its way. It works once, so open it on this device."
|
|
123
|
+
] });
|
|
124
|
+
}
|
|
125
|
+
return /* @__PURE__ */ jsxs(
|
|
126
|
+
"form",
|
|
127
|
+
{
|
|
128
|
+
onSubmit: async (e) => {
|
|
129
|
+
e.preventDefault();
|
|
130
|
+
setStatus("sending");
|
|
131
|
+
setError(null);
|
|
132
|
+
const res = await forgotPasswordAction(email);
|
|
133
|
+
if (res.success) setStatus("sent");
|
|
134
|
+
else {
|
|
135
|
+
setError(res.error ?? "Something went wrong.");
|
|
136
|
+
setStatus("idle");
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
children: [
|
|
140
|
+
/* @__PURE__ */ jsx("p", { style: { fontSize: 13, lineHeight: 1.5, margin: "0 0 16px", color: v("muted") }, children: "Enter your email and we'll send a link to choose a new password." }),
|
|
141
|
+
/* @__PURE__ */ jsx("label", { style: S.label, htmlFor: "gr-forgot-email", children: "Email" }),
|
|
142
|
+
/* @__PURE__ */ jsx(
|
|
143
|
+
"input",
|
|
144
|
+
{
|
|
145
|
+
id: "gr-forgot-email",
|
|
146
|
+
style: S.field,
|
|
147
|
+
type: "email",
|
|
148
|
+
autoComplete: "email",
|
|
149
|
+
placeholder: "name@company.com",
|
|
150
|
+
value: email,
|
|
151
|
+
onChange: (e) => setEmail(e.target.value),
|
|
152
|
+
required: true,
|
|
153
|
+
autoFocus: true
|
|
154
|
+
}
|
|
155
|
+
),
|
|
156
|
+
error && /* @__PURE__ */ jsx("div", { style: { ...S.error, marginBottom: 12 }, children: error }),
|
|
157
|
+
/* @__PURE__ */ jsx("button", { style: { ...S.primaryBtn, width: "100%" }, type: "submit", disabled: status === "sending", children: status === "sending" ? "Sending\u2026" : "Send reset link" })
|
|
158
|
+
]
|
|
159
|
+
}
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
export {
|
|
163
|
+
AuthDrawer
|
|
164
|
+
};
|
|
165
|
+
//# sourceMappingURL=AuthDrawer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/react/AuthDrawer.tsx"],"sourcesContent":["\"use client\";\n\nimport React, { useActionState, useEffect, useState, useTransition } from \"react\";\nimport { useRouter } from \"next/navigation\";\nimport { forgotPasswordAction, logoutAction, setPasswordAction } from \"../server/actions\";\nimport { CloseIcon } from \"./Icons\";\nimport { S, v } from \"./styles\";\nimport { ANIMATION_CSS } from \"./Team\";\nimport type { SetPasswordState } from \"../core/types\";\n\nconst initialPasswordState: SetPasswordState = {};\n\n/**\n * The two password steps, in the same drawer the Team panel uses.\n *\n * Guardrail asks people for one credential, a password. Emailed links exist only\n * to let someone choose or replace it, so this is where they land:\n *\n * - \"set-password\" — after an invite or reset link. Cannot be dismissed: until a\n * password exists there is nothing to sign in with next time. The one way out\n * is to sign out.\n * - \"forgot\" — asks for an address and emails a reset link. Opened from the\n * sign-in bar, and from the \"that link didn't work\" message with `message`.\n */\nexport function AuthDrawer({\n mode,\n title,\n message,\n onClose,\n}: {\n mode: \"set-password\" | \"forgot\";\n title?: string;\n /** Plain-language context shown above the form, e.g. why a link failed. */\n message?: string;\n onClose?: () => void;\n}) {\n return (\n <>\n {/* Same sheet the Team drawer ships; React dedupes it by href. */}\n <style href=\"guardrail-team-animation\" precedence=\"default\">\n {ANIMATION_CSS}\n </style>\n <div\n data-guardrail\n data-guardrail-scrim\n style={S.scrim}\n // A set-password step is not something to click away from.\n onClick={mode === \"forgot\" ? onClose : undefined}\n />\n <aside\n data-guardrail\n data-guardrail-team\n style={S.drawer}\n role=\"dialog\"\n aria-label={mode === \"set-password\" ? \"Set your password\" : \"Forgot password\"}\n >\n <header style={S.drawerHead}>\n <h2 style={S.drawerTitle}>\n {title ?? (mode === \"set-password\" ? \"Set your password\" : \"Forgot your password?\")}\n </h2>\n {mode === \"forgot\" && onClose && (\n <button style={{ ...S.iconBtn, color: v(\"muted\") }} onClick={onClose} aria-label=\"Close\">\n <CloseIcon />\n </button>\n )}\n </header>\n <div style={S.drawerBody}>\n {message && (\n <p style={{ fontSize: 13, lineHeight: 1.5, margin: \"0 0 16px\", color: v(\"fg\") }}>{message}</p>\n )}\n {mode === \"set-password\" ? <SetPasswordForm /> : <ForgotPasswordForm />}\n </div>\n </aside>\n </>\n );\n}\n\nfunction SetPasswordForm() {\n const router = useRouter();\n const [state, formAction, isPending] = useActionState(setPasswordAction, initialPasswordState);\n const [signingOut, startSignOut] = useTransition();\n\n useEffect(() => {\n if (state.success) router.refresh();\n }, [state.success, router]);\n\n return (\n <>\n <p style={{ fontSize: 13, lineHeight: 1.5, margin: \"0 0 16px\", color: v(\"muted\") }}>\n Choose a password. It's how you'll sign in from now on.\n </p>\n <form action={formAction}>\n <label style={S.label} htmlFor=\"gr-new-password\">\n New password\n </label>\n <input\n id=\"gr-new-password\"\n style={S.field}\n name=\"password\"\n type=\"password\"\n autoComplete=\"new-password\"\n minLength={8}\n required\n autoFocus\n />\n <label style={S.label} htmlFor=\"gr-confirm-password\">\n Confirm password\n </label>\n <input\n id=\"gr-confirm-password\"\n style={S.field}\n name=\"confirm\"\n type=\"password\"\n autoComplete=\"new-password\"\n minLength={8}\n required\n />\n <p style={{ fontSize: 11, color: v(\"subtle\"), margin: \"0 0 12px\" }}>At least 8 characters.</p>\n {state.error && <div style={{ ...S.error, marginBottom: 12 }}>{state.error}</div>}\n <button style={{ ...S.primaryBtn, width: \"100%\" }} type=\"submit\" disabled={isPending || signingOut}>\n {isPending ? \"Saving…\" : \"Set password\"}\n </button>\n </form>\n <button\n type=\"button\"\n disabled={isPending || signingOut}\n style={{\n background: \"none\",\n border: \"none\",\n cursor: \"pointer\",\n color: v(\"muted\"),\n fontSize: 12,\n textDecoration: \"underline\",\n padding: 0,\n marginTop: 16,\n }}\n onClick={() =>\n startSignOut(async () => {\n await logoutAction();\n router.refresh();\n })\n }\n >\n Cancel and sign out\n </button>\n </>\n );\n}\n\nfunction ForgotPasswordForm() {\n const [email, setEmail] = useState(\"\");\n const [status, setStatus] = useState<\"idle\" | \"sending\" | \"sent\">(\"idle\");\n const [error, setError] = useState<string | null>(null);\n\n if (status === \"sent\") {\n return (\n <p style={{ fontSize: 13, lineHeight: 1.5, margin: 0, color: v(\"fg\") }}>\n If <strong>{email.trim()}</strong> has access to this site, a link to choose a new password is on its\n way. It works once, so open it on this device.\n </p>\n );\n }\n\n return (\n <form\n onSubmit={async (e) => {\n e.preventDefault();\n setStatus(\"sending\");\n setError(null);\n const res = await forgotPasswordAction(email);\n if (res.success) setStatus(\"sent\");\n else {\n setError(res.error ?? \"Something went wrong.\");\n setStatus(\"idle\");\n }\n }}\n >\n <p style={{ fontSize: 13, lineHeight: 1.5, margin: \"0 0 16px\", color: v(\"muted\") }}>\n Enter your email and we'll send a link to choose a new password.\n </p>\n <label style={S.label} htmlFor=\"gr-forgot-email\">\n Email\n </label>\n <input\n id=\"gr-forgot-email\"\n style={S.field}\n type=\"email\"\n autoComplete=\"email\"\n placeholder=\"name@company.com\"\n value={email}\n onChange={(e) => setEmail(e.target.value)}\n required\n autoFocus\n />\n {error && <div style={{ ...S.error, marginBottom: 12 }}>{error}</div>}\n <button style={{ ...S.primaryBtn, width: \"100%\" }} type=\"submit\" disabled={status === \"sending\"}>\n {status === \"sending\" ? \"Sending…\" : \"Send reset link\"}\n </button>\n </form>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -27,7 +27,7 @@ function AuthRedirectSync() {
|
|
|
27
27
|
url.searchParams.delete("token_hash");
|
|
28
28
|
url.searchParams.delete("type");
|
|
29
29
|
window.history.replaceState(null, "", url.toString());
|
|
30
|
-
|
|
30
|
+
window.location.reload();
|
|
31
31
|
});
|
|
32
32
|
}, [searchParams]);
|
|
33
33
|
return null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/react/AuthRedirectSync.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useRef } from \"react\";\nimport { useSearchParams } from \"next/navigation\";\nimport { completeAuthRedirectAction } from \"../server/actions\";\n\n/**\n * Notices a session that arrived via a redirect link — an invite, magic\n * link, or password-recovery email — and hands whatever it found to\n * completeAuthRedirectAction, which defers to the auth adapter's own\n * completeAuthRedirect for the actual exchange. Deliberately generic: this\n * file has no idea what a Supabase token looks like, only that a hash\n * carrying `access_token=` or a `code`/`token_hash` query param might be one.\n *\n * Has to run client-side and unconditionally (not gated behind edit mode,\n * unlike EditSyncer) — an invite link redirects to the site's plain URL, not\n * necessarily one with ?edit=true, and hash-based tokens never reach the\n * server on their own; only the browser ever sees window.location.hash.\n *\n * Mirrors the community-documented pattern for this exact problem, just\n * generalized across whichever redirect style a provider's own email\n * templates happen to produce, rather than requiring one specific route.\n */\nexport function AuthRedirectSync() {\n const searchParams = useSearchParams();\n const handled = useRef(false);\n\n useEffect(() => {\n if (handled.current) return;\n\n const rawHash = window.location.hash.startsWith(\"#\")\n ? window.location.hash.slice(1)\n : window.location.hash;\n // The one thing worth checking before assuming this hash is ours: every\n // normal page load has an empty hash, and this runs on all of them.\n const hasHashTokens = /(?:^|&)access_token=/.test(rawHash);\n\n const code = searchParams.get(\"code\") ?? undefined;\n const tokenHash = searchParams.get(\"token_hash\") ?? undefined;\n const type = searchParams.get(\"type\") ?? undefined;\n\n if (!hasHashTokens && !code && !tokenHash) return;\n handled.current = true;\n\n const params = {\n ...(hasHashTokens ? { hash: rawHash } : {}),\n ...(code ? { code } : {}),\n ...(tokenHash ? { tokenHash } : {}),\n ...(type ? { type } : {}),\n };\n\n completeAuthRedirectAction(params).then((res) => {\n // Stripped either way — a token that failed to exchange (already used,\n // expired) has no business lingering in the address bar or history\n // either.\n const url = new URL(window.location.href);\n url.hash = \"\";\n url.searchParams.delete(\"code\");\n url.searchParams.delete(\"token_hash\");\n url.searchParams.delete(\"type\");\n window.history.replaceState(null, \"\", url.toString());\n\n // A full reload, not router.refresh(): the session was just set via\n // cookies a Server Action wrote, and the current page's React tree\n // (including this component) was built from the pre-session render —\n // simplest to just start over
|
|
1
|
+
{"version":3,"sources":["../../src/react/AuthRedirectSync.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useRef } from \"react\";\nimport { useSearchParams } from \"next/navigation\";\nimport { completeAuthRedirectAction } from \"../server/actions\";\n\n/**\n * Notices a session that arrived via a redirect link — an invite, magic\n * link, or password-recovery email — and hands whatever it found to\n * completeAuthRedirectAction, which defers to the auth adapter's own\n * completeAuthRedirect for the actual exchange. Deliberately generic: this\n * file has no idea what a Supabase token looks like, only that a hash\n * carrying `access_token=` or a `code`/`token_hash` query param might be one.\n *\n * Has to run client-side and unconditionally (not gated behind edit mode,\n * unlike EditSyncer) — an invite link redirects to the site's plain URL, not\n * necessarily one with ?edit=true, and hash-based tokens never reach the\n * server on their own; only the browser ever sees window.location.hash.\n *\n * Mirrors the community-documented pattern for this exact problem, just\n * generalized across whichever redirect style a provider's own email\n * templates happen to produce, rather than requiring one specific route.\n */\nexport function AuthRedirectSync() {\n const searchParams = useSearchParams();\n const handled = useRef(false);\n\n useEffect(() => {\n if (handled.current) return;\n\n const rawHash = window.location.hash.startsWith(\"#\")\n ? window.location.hash.slice(1)\n : window.location.hash;\n // The one thing worth checking before assuming this hash is ours: every\n // normal page load has an empty hash, and this runs on all of them.\n const hasHashTokens = /(?:^|&)access_token=/.test(rawHash);\n\n const code = searchParams.get(\"code\") ?? undefined;\n const tokenHash = searchParams.get(\"token_hash\") ?? undefined;\n const type = searchParams.get(\"type\") ?? undefined;\n\n if (!hasHashTokens && !code && !tokenHash) return;\n handled.current = true;\n\n const params = {\n ...(hasHashTokens ? { hash: rawHash } : {}),\n ...(code ? { code } : {}),\n ...(tokenHash ? { tokenHash } : {}),\n ...(type ? { type } : {}),\n };\n\n completeAuthRedirectAction(params).then((res) => {\n // Stripped either way — a token that failed to exchange (already used,\n // expired) has no business lingering in the address bar or history\n // either.\n const url = new URL(window.location.href);\n url.hash = \"\";\n url.searchParams.delete(\"code\");\n url.searchParams.delete(\"token_hash\");\n url.searchParams.delete(\"type\");\n window.history.replaceState(null, \"\", url.toString());\n\n // A full reload, not router.refresh(): the session was just set via\n // cookies a Server Action wrote, and the current page's React tree\n // (including this component) was built from the pre-session render —\n // simplest to just start over. On failure the action set a cookie\n // explaining it, and a reload is what shows that, rather than leaving the\n // person on a page where nothing visibly happened.\n window.location.reload();\n });\n }, [searchParams]);\n\n return null;\n}\n"],"mappings":";AAEA,SAAS,WAAW,cAAc;AAClC,SAAS,uBAAuB;AAChC,SAAS,kCAAkC;AAmBpC,SAAS,mBAAmB;AACjC,QAAM,eAAe,gBAAgB;AACrC,QAAM,UAAU,OAAO,KAAK;AAE5B,YAAU,MAAM;AACd,QAAI,QAAQ,QAAS;AAErB,UAAM,UAAU,OAAO,SAAS,KAAK,WAAW,GAAG,IAC/C,OAAO,SAAS,KAAK,MAAM,CAAC,IAC5B,OAAO,SAAS;AAGpB,UAAM,gBAAgB,uBAAuB,KAAK,OAAO;AAEzD,UAAM,OAAO,aAAa,IAAI,MAAM,KAAK;AACzC,UAAM,YAAY,aAAa,IAAI,YAAY,KAAK;AACpD,UAAM,OAAO,aAAa,IAAI,MAAM,KAAK;AAEzC,QAAI,CAAC,iBAAiB,CAAC,QAAQ,CAAC,UAAW;AAC3C,YAAQ,UAAU;AAElB,UAAM,SAAS;AAAA,MACb,GAAI,gBAAgB,EAAE,MAAM,QAAQ,IAAI,CAAC;AAAA,MACzC,GAAI,OAAO,EAAE,KAAK,IAAI,CAAC;AAAA,MACvB,GAAI,YAAY,EAAE,UAAU,IAAI,CAAC;AAAA,MACjC,GAAI,OAAO,EAAE,KAAK,IAAI,CAAC;AAAA,IACzB;AAEA,+BAA2B,MAAM,EAAE,KAAK,CAAC,QAAQ;AAI/C,YAAM,MAAM,IAAI,IAAI,OAAO,SAAS,IAAI;AACxC,UAAI,OAAO;AACX,UAAI,aAAa,OAAO,MAAM;AAC9B,UAAI,aAAa,OAAO,YAAY;AACpC,UAAI,aAAa,OAAO,MAAM;AAC9B,aAAO,QAAQ,aAAa,MAAM,IAAI,IAAI,SAAS,CAAC;AAQpD,aAAO,SAAS,OAAO;AAAA,IACzB,CAAC;AAAA,EACH,GAAG,CAAC,YAAY,CAAC;AAEjB,SAAO;AACT;","names":[]}
|
package/dist/react/Icons.d.ts
CHANGED
|
@@ -11,5 +11,6 @@ declare const ArrowDownIcon: () => react_jsx_runtime.JSX.Element;
|
|
|
11
11
|
declare const LogOutIcon: () => react_jsx_runtime.JSX.Element;
|
|
12
12
|
declare const SpinnerIcon: () => react_jsx_runtime.JSX.Element;
|
|
13
13
|
declare const OpenIcon: () => react_jsx_runtime.JSX.Element;
|
|
14
|
+
declare const KeyIcon: () => react_jsx_runtime.JSX.Element;
|
|
14
15
|
|
|
15
|
-
export { ArrowDownIcon, ArrowUpIcon, CheckIcon, CloseIcon, LogOutIcon, OpenIcon, PencilIcon, PeopleIcon, PlusIcon, SpinnerIcon, TrashIcon };
|
|
16
|
+
export { ArrowDownIcon, ArrowUpIcon, CheckIcon, CloseIcon, KeyIcon, LogOutIcon, OpenIcon, PencilIcon, PeopleIcon, PlusIcon, SpinnerIcon, TrashIcon };
|
package/dist/react/Icons.js
CHANGED
|
@@ -63,11 +63,13 @@ const OpenIcon = () => /* @__PURE__ */ jsxs("svg", { ...base, strokeWidth: 2, "a
|
|
|
63
63
|
/* @__PURE__ */ jsx("polyline", { points: "15 3 21 3 21 9" }),
|
|
64
64
|
/* @__PURE__ */ jsx("line", { x1: "10", y1: "14", x2: "21", y2: "3" })
|
|
65
65
|
] });
|
|
66
|
+
const KeyIcon = () => /* @__PURE__ */ jsx("svg", { ...base, strokeWidth: 2, "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M21 2l-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.778 7.778 5.5 5.5 0 0 1 7.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4" }) });
|
|
66
67
|
export {
|
|
67
68
|
ArrowDownIcon,
|
|
68
69
|
ArrowUpIcon,
|
|
69
70
|
CheckIcon,
|
|
70
71
|
CloseIcon,
|
|
72
|
+
KeyIcon,
|
|
71
73
|
LogOutIcon,
|
|
72
74
|
OpenIcon,
|
|
73
75
|
PencilIcon,
|