@guard-rail/core 0.10.0 → 0.11.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 -1
- package/dist/cli/doctor.js +138 -3
- package/dist/cli/doctor.js.map +1 -1
- package/dist/core/csp.d.ts +32 -0
- package/dist/core/csp.js +26 -0
- package/dist/core/csp.js.map +1 -0
- package/dist/index.client.d.ts +1 -0
- package/dist/index.client.js +3 -0
- package/dist/index.client.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/react/Admin.d.ts +5 -3
- package/dist/react/Admin.js +29 -41
- package/dist/react/Admin.js.map +1 -1
- package/dist/react/AuthDrawer.js +22 -34
- package/dist/react/AuthDrawer.js.map +1 -1
- package/dist/react/GRail/client.js +27 -47
- package/dist/react/GRail/client.js.map +1 -1
- package/dist/react/MediaPicker.js +12 -16
- package/dist/react/MediaPicker.js.map +1 -1
- package/dist/react/Repeater/client.js +12 -13
- package/dist/react/Repeater/client.js.map +1 -1
- package/dist/react/Team.d.ts +1 -7
- package/dist/react/Team.js +21 -46
- package/dist/react/Team.js.map +1 -1
- package/dist/react/guardrail.css +563 -0
- package/package.json +11 -10
- package/dist/react/styles.d.ts +0 -504
- package/dist/react/styles.js +0 -500
- package/dist/react/styles.js.map +0 -1
package/dist/react/Admin.js
CHANGED
|
@@ -7,16 +7,13 @@ import { useGRail } from "./Provider";
|
|
|
7
7
|
import { TeamDrawer } from "./Team";
|
|
8
8
|
import { AuthDrawer } from "./AuthDrawer";
|
|
9
9
|
import { CloseIcon, LogOutIcon, PeopleIcon } from "./Icons";
|
|
10
|
-
import
|
|
10
|
+
import "./guardrail.css";
|
|
11
11
|
import { GR_COOKIES, GR_EDIT_MODE_OFF_TTL_SECONDS } from "../core/defs";
|
|
12
12
|
const initialState = { email: "", password: "" };
|
|
13
13
|
function GRailAdmin(props) {
|
|
14
14
|
const { isEditMode } = useGRail();
|
|
15
15
|
if (!isEditMode) return null;
|
|
16
|
-
return /* @__PURE__ */
|
|
17
|
-
/* @__PURE__ */ jsx("style", { href: "guardrail-theme", precedence: "default", children: THEME_CSS }),
|
|
18
|
-
/* @__PURE__ */ jsx(AdminBody, { ...props })
|
|
19
|
-
] });
|
|
16
|
+
return /* @__PURE__ */ jsx(AdminBody, { ...props });
|
|
20
17
|
}
|
|
21
18
|
function AdminBody({ toolbarExtra }) {
|
|
22
19
|
const { isAuth, isEditMode, needsPassword, authError, role, auth } = useGRail();
|
|
@@ -60,17 +57,17 @@ function AdminBody({ toolbarExtra }) {
|
|
|
60
57
|
if (!isAuth) {
|
|
61
58
|
if (!auth.password) {
|
|
62
59
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
63
|
-
/* @__PURE__ */ jsx("div", { "data-guardrail": true,
|
|
64
|
-
/* @__PURE__ */ jsxs("div", {
|
|
65
|
-
/* @__PURE__ */ jsx("span", {
|
|
66
|
-
!auth.signInUrl && /* @__PURE__ */ jsx("span", {
|
|
60
|
+
/* @__PURE__ */ jsx("div", { "data-guardrail": true, className: "gr-banner", children: /* @__PURE__ */ jsxs("div", { className: "gr-banner-form", children: [
|
|
61
|
+
/* @__PURE__ */ jsxs("div", { className: "gr-brand-stack", children: [
|
|
62
|
+
/* @__PURE__ */ jsx("span", { className: "gr-brand", children: "Guardrail" }),
|
|
63
|
+
!auth.signInUrl && /* @__PURE__ */ jsx("span", { className: "gr-fs-12 gr-text-muted", children: "No sign-in method is configured for this site." })
|
|
67
64
|
] }),
|
|
68
|
-
auth.signInUrl && /* @__PURE__ */ jsx("a", { href: auth.signInUrl,
|
|
65
|
+
auth.signInUrl && /* @__PURE__ */ jsx("a", { href: auth.signInUrl, className: "gr-primary-btn gr-no-underline", children: "Sign in" }),
|
|
69
66
|
/* @__PURE__ */ jsx(
|
|
70
67
|
"button",
|
|
71
68
|
{
|
|
72
69
|
type: "button",
|
|
73
|
-
|
|
70
|
+
className: "gr-icon-btn gr-text-muted",
|
|
74
71
|
onClick: exitEditMode,
|
|
75
72
|
title: "Not editing? Dismiss",
|
|
76
73
|
"aria-label": "Dismiss",
|
|
@@ -82,10 +79,10 @@ function AdminBody({ toolbarExtra }) {
|
|
|
82
79
|
] });
|
|
83
80
|
}
|
|
84
81
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
85
|
-
/* @__PURE__ */ jsx("div", { "data-guardrail": true,
|
|
86
|
-
/* @__PURE__ */ jsxs("div", {
|
|
87
|
-
/* @__PURE__ */ jsx("span", {
|
|
88
|
-
formState.error && /* @__PURE__ */ jsx("span", {
|
|
82
|
+
/* @__PURE__ */ jsx("div", { "data-guardrail": true, className: "gr-banner", children: /* @__PURE__ */ jsxs("form", { action: formAction, className: "gr-banner-form", children: [
|
|
83
|
+
/* @__PURE__ */ jsxs("div", { className: "gr-brand-stack", children: [
|
|
84
|
+
/* @__PURE__ */ jsx("span", { className: "gr-brand", children: "Guardrail" }),
|
|
85
|
+
formState.error && /* @__PURE__ */ jsx("span", { className: "gr-error", children: formState.error })
|
|
89
86
|
] }),
|
|
90
87
|
/* @__PURE__ */ jsx(
|
|
91
88
|
"input",
|
|
@@ -96,7 +93,7 @@ function AdminBody({ toolbarExtra }) {
|
|
|
96
93
|
placeholder: "Email",
|
|
97
94
|
defaultValue: formState.email,
|
|
98
95
|
required: true,
|
|
99
|
-
|
|
96
|
+
className: "gr-input"
|
|
100
97
|
}
|
|
101
98
|
),
|
|
102
99
|
/* @__PURE__ */ jsx(
|
|
@@ -107,25 +104,16 @@ function AdminBody({ toolbarExtra }) {
|
|
|
107
104
|
autoComplete: "current-password",
|
|
108
105
|
placeholder: "Password",
|
|
109
106
|
required: true,
|
|
110
|
-
|
|
107
|
+
className: "gr-input"
|
|
111
108
|
}
|
|
112
109
|
),
|
|
113
|
-
/* @__PURE__ */ jsx("button", { type: "submit", disabled: isPending,
|
|
110
|
+
/* @__PURE__ */ jsx("button", { type: "submit", disabled: isPending, className: "gr-primary-btn", children: isPending ? "Authenticating\u2026" : "Sign In" }),
|
|
114
111
|
/* @__PURE__ */ jsx(
|
|
115
112
|
"button",
|
|
116
113
|
{
|
|
117
114
|
type: "button",
|
|
118
115
|
onClick: () => setForgotOpen(true),
|
|
119
|
-
|
|
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
|
-
},
|
|
116
|
+
className: "gr-text-btn",
|
|
129
117
|
children: "Forgot password?"
|
|
130
118
|
}
|
|
131
119
|
),
|
|
@@ -133,7 +121,7 @@ function AdminBody({ toolbarExtra }) {
|
|
|
133
121
|
"button",
|
|
134
122
|
{
|
|
135
123
|
type: "button",
|
|
136
|
-
|
|
124
|
+
className: "gr-icon-btn gr-text-muted",
|
|
137
125
|
onClick: exitEditMode,
|
|
138
126
|
title: "Not editing? Dismiss",
|
|
139
127
|
"aria-label": "Dismiss",
|
|
@@ -147,13 +135,13 @@ function AdminBody({ toolbarExtra }) {
|
|
|
147
135
|
if (needsPassword) return /* @__PURE__ */ jsx(AuthDrawer, { mode: "set-password" });
|
|
148
136
|
if (!role) {
|
|
149
137
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
150
|
-
/* @__PURE__ */ jsxs("div", { "data-guardrail": true,
|
|
151
|
-
/* @__PURE__ */ jsx("span", {
|
|
152
|
-
/* @__PURE__ */ jsx("span", {
|
|
138
|
+
/* @__PURE__ */ jsxs("div", { "data-guardrail": true, className: "gr-toolbar", children: [
|
|
139
|
+
/* @__PURE__ */ jsx("span", { className: "gr-toolbar-label", children: "No access to this site" }),
|
|
140
|
+
/* @__PURE__ */ jsx("span", { className: "gr-divider" }),
|
|
153
141
|
/* @__PURE__ */ jsx(
|
|
154
142
|
"button",
|
|
155
143
|
{
|
|
156
|
-
|
|
144
|
+
className: "gr-icon-btn gr-text-muted",
|
|
157
145
|
onClick: signOut,
|
|
158
146
|
title: "Sign out",
|
|
159
147
|
"aria-label": "Sign out",
|
|
@@ -167,14 +155,14 @@ function AdminBody({ toolbarExtra }) {
|
|
|
167
155
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
168
156
|
linkErrorDrawer,
|
|
169
157
|
teamOpen && /* @__PURE__ */ jsx(TeamDrawer, { onClose: () => setTeamOpen(false) }),
|
|
170
|
-
/* @__PURE__ */ jsxs("div", { "data-guardrail": true,
|
|
171
|
-
/* @__PURE__ */ jsx("span", {
|
|
172
|
-
/* @__PURE__ */ jsx("span", {
|
|
173
|
-
/* @__PURE__ */ jsx("span", {
|
|
158
|
+
/* @__PURE__ */ jsxs("div", { "data-guardrail": true, className: "gr-toolbar", children: [
|
|
159
|
+
/* @__PURE__ */ jsx("span", { className: "gr-dot" }),
|
|
160
|
+
/* @__PURE__ */ jsx("span", { className: "gr-toolbar-label", children: "Edit Mode" }),
|
|
161
|
+
/* @__PURE__ */ jsx("span", { className: "gr-divider" }),
|
|
174
162
|
/* @__PURE__ */ jsx(
|
|
175
163
|
"button",
|
|
176
164
|
{
|
|
177
|
-
|
|
165
|
+
className: "gr-icon-btn gr-text-muted gr-size-24",
|
|
178
166
|
onClick: () => setTeamOpen(true),
|
|
179
167
|
title: "Team",
|
|
180
168
|
"aria-label": "Team",
|
|
@@ -182,14 +170,14 @@ function AdminBody({ toolbarExtra }) {
|
|
|
182
170
|
}
|
|
183
171
|
),
|
|
184
172
|
toolbarExtra && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
185
|
-
/* @__PURE__ */ jsx("span", {
|
|
173
|
+
/* @__PURE__ */ jsx("span", { className: "gr-divider" }),
|
|
186
174
|
toolbarExtra
|
|
187
175
|
] }),
|
|
188
|
-
/* @__PURE__ */ jsx("span", {
|
|
176
|
+
/* @__PURE__ */ jsx("span", { className: "gr-divider" }),
|
|
189
177
|
/* @__PURE__ */ jsx(
|
|
190
178
|
"button",
|
|
191
179
|
{
|
|
192
|
-
|
|
180
|
+
className: "gr-icon-btn gr-text-muted",
|
|
193
181
|
onClick: exitEditMode,
|
|
194
182
|
title: "Exit edit mode",
|
|
195
183
|
"aria-label": "Exit edit mode",
|
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 { 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":[]}
|
|
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 \"./guardrail.css\";\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 * Imports the chrome's stylesheet (guardrail.css), which also defines the\n * light/dark theme variables, so every piece of chrome — the sign-in bar, the\n * toolbar, and the field popups portaled to <body> — picks them up from one\n * place. A stylesheet rather than inline styles so the chrome works under a\n * strict Content-Security-Policy.\n */\nexport function GRailAdmin(props: { toolbarExtra?: React.ReactNode }) {\n const { isEditMode } = useGRail();\n if (!isEditMode) return null;\n return <AdminBody {...props} />;\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 className=\"gr-banner\">\n <div className=\"gr-banner-form\">\n <div className=\"gr-brand-stack\">\n <span className=\"gr-brand\">Guardrail</span>\n {!auth.signInUrl && (\n <span className=\"gr-fs-12 gr-text-muted\">\n No sign-in method is configured for this site.\n </span>\n )}\n </div>\n {auth.signInUrl && (\n <a href={auth.signInUrl} className=\"gr-primary-btn gr-no-underline\">\n Sign in\n </a>\n )}\n <button\n type=\"button\"\n className=\"gr-icon-btn gr-text-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 className=\"gr-banner\">\n <form action={formAction} className=\"gr-banner-form\">\n <div className=\"gr-brand-stack\">\n <span className=\"gr-brand\">Guardrail</span>\n {formState.error && <span className=\"gr-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 className=\"gr-input\"\n />\n <input\n name=\"password\"\n type=\"password\"\n autoComplete=\"current-password\"\n placeholder=\"Password\"\n required\n className=\"gr-input\"\n />\n <button type=\"submit\" disabled={isPending} className=\"gr-primary-btn\">\n {isPending ? \"Authenticating…\" : \"Sign In\"}\n </button>\n <button\n type=\"button\"\n onClick={() => setForgotOpen(true)}\n className=\"gr-text-btn\"\n >\n Forgot password?\n </button>\n <button\n type=\"button\"\n className=\"gr-icon-btn gr-text-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 className=\"gr-toolbar\">\n <span className=\"gr-toolbar-label\">No access to this site</span>\n <span className=\"gr-divider\" />\n <button\n className=\"gr-icon-btn gr-text-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 className=\"gr-toolbar\">\n <span className=\"gr-dot\" />\n <span className=\"gr-toolbar-label\">Edit Mode</span>\n <span className=\"gr-divider\" />\n <button\n className=\"gr-icon-btn gr-text-muted gr-size-24\"\n onClick={() => setTeamOpen(true)}\n title=\"Team\"\n aria-label=\"Team\"\n >\n <PeopleIcon />\n </button>\n\n {toolbarExtra && (\n <>\n <span className=\"gr-divider\" />\n {toolbarExtra}\n </>\n )}\n\n <span className=\"gr-divider\" />\n <button\n className=\"gr-icon-btn gr-text-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":[]}
|
package/dist/react/AuthDrawer.js
CHANGED
|
@@ -4,8 +4,6 @@ import React, { useActionState, useEffect, useState, useTransition } from "react
|
|
|
4
4
|
import { useRouter } from "next/navigation";
|
|
5
5
|
import { forgotPasswordAction, logoutAction, setPasswordAction } from "../server/actions";
|
|
6
6
|
import { CloseIcon } from "./Icons";
|
|
7
|
-
import { S, v } from "./styles";
|
|
8
|
-
import { ANIMATION_CSS } from "./Team";
|
|
9
7
|
const initialPasswordState = {};
|
|
10
8
|
function AuthDrawer({
|
|
11
9
|
mode,
|
|
@@ -14,13 +12,12 @@ function AuthDrawer({
|
|
|
14
12
|
onClose
|
|
15
13
|
}) {
|
|
16
14
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
17
|
-
/* @__PURE__ */ jsx("style", { href: "guardrail-team-animation", precedence: "default", children: ANIMATION_CSS }),
|
|
18
15
|
/* @__PURE__ */ jsx(
|
|
19
16
|
"div",
|
|
20
17
|
{
|
|
21
18
|
"data-guardrail": true,
|
|
22
19
|
"data-guardrail-scrim": true,
|
|
23
|
-
|
|
20
|
+
className: "gr-scrim",
|
|
24
21
|
onClick: mode === "forgot" ? onClose : void 0
|
|
25
22
|
}
|
|
26
23
|
),
|
|
@@ -29,16 +26,16 @@ function AuthDrawer({
|
|
|
29
26
|
{
|
|
30
27
|
"data-guardrail": true,
|
|
31
28
|
"data-guardrail-team": true,
|
|
32
|
-
|
|
29
|
+
className: "gr-drawer",
|
|
33
30
|
role: "dialog",
|
|
34
31
|
"aria-label": mode === "set-password" ? "Set your password" : "Forgot password",
|
|
35
32
|
children: [
|
|
36
|
-
/* @__PURE__ */ jsxs("header", {
|
|
37
|
-
/* @__PURE__ */ jsx("h2", {
|
|
38
|
-
mode === "forgot" && onClose && /* @__PURE__ */ jsx("button", {
|
|
33
|
+
/* @__PURE__ */ jsxs("header", { className: "gr-drawer-head", children: [
|
|
34
|
+
/* @__PURE__ */ jsx("h2", { className: "gr-drawer-title", children: title ?? (mode === "set-password" ? "Set your password" : "Forgot your password?") }),
|
|
35
|
+
mode === "forgot" && onClose && /* @__PURE__ */ jsx("button", { className: "gr-icon-btn gr-text-muted", onClick: onClose, "aria-label": "Close", children: /* @__PURE__ */ jsx(CloseIcon, {}) })
|
|
39
36
|
] }),
|
|
40
|
-
/* @__PURE__ */ jsxs("div", {
|
|
41
|
-
message && /* @__PURE__ */ jsx("p", {
|
|
37
|
+
/* @__PURE__ */ jsxs("div", { className: "gr-drawer-body", children: [
|
|
38
|
+
message && /* @__PURE__ */ jsx("p", { className: "gr-fs-13 gr-lh-15 gr-m-0 gr-mb-16 gr-text-fg", children: message }),
|
|
42
39
|
mode === "set-password" ? /* @__PURE__ */ jsx(SetPasswordForm, {}) : /* @__PURE__ */ jsx(ForgotPasswordForm, {})
|
|
43
40
|
] })
|
|
44
41
|
]
|
|
@@ -54,14 +51,14 @@ function SetPasswordForm() {
|
|
|
54
51
|
if (state.success) router.refresh();
|
|
55
52
|
}, [state.success, router]);
|
|
56
53
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
57
|
-
/* @__PURE__ */ jsx("p", {
|
|
54
|
+
/* @__PURE__ */ jsx("p", { className: "gr-fs-13 gr-lh-15 gr-m-0 gr-mb-16 gr-text-muted", children: "Choose a password. It's how you'll sign in from now on." }),
|
|
58
55
|
/* @__PURE__ */ jsxs("form", { action: formAction, children: [
|
|
59
|
-
/* @__PURE__ */ jsx("label", {
|
|
56
|
+
/* @__PURE__ */ jsx("label", { className: "gr-label", htmlFor: "gr-new-password", children: "New password" }),
|
|
60
57
|
/* @__PURE__ */ jsx(
|
|
61
58
|
"input",
|
|
62
59
|
{
|
|
63
60
|
id: "gr-new-password",
|
|
64
|
-
|
|
61
|
+
className: "gr-field",
|
|
65
62
|
name: "password",
|
|
66
63
|
type: "password",
|
|
67
64
|
autoComplete: "new-password",
|
|
@@ -70,12 +67,12 @@ function SetPasswordForm() {
|
|
|
70
67
|
autoFocus: true
|
|
71
68
|
}
|
|
72
69
|
),
|
|
73
|
-
/* @__PURE__ */ jsx("label", {
|
|
70
|
+
/* @__PURE__ */ jsx("label", { className: "gr-label", htmlFor: "gr-confirm-password", children: "Confirm password" }),
|
|
74
71
|
/* @__PURE__ */ jsx(
|
|
75
72
|
"input",
|
|
76
73
|
{
|
|
77
74
|
id: "gr-confirm-password",
|
|
78
|
-
|
|
75
|
+
className: "gr-field",
|
|
79
76
|
name: "confirm",
|
|
80
77
|
type: "password",
|
|
81
78
|
autoComplete: "new-password",
|
|
@@ -83,25 +80,16 @@ function SetPasswordForm() {
|
|
|
83
80
|
required: true
|
|
84
81
|
}
|
|
85
82
|
),
|
|
86
|
-
/* @__PURE__ */ jsx("p", {
|
|
87
|
-
state.error && /* @__PURE__ */ jsx("div", {
|
|
88
|
-
/* @__PURE__ */ jsx("button", {
|
|
83
|
+
/* @__PURE__ */ jsx("p", { className: "gr-fs-11 gr-text-subtle gr-m-0 gr-mb-12", children: "At least 8 characters." }),
|
|
84
|
+
state.error && /* @__PURE__ */ jsx("div", { className: "gr-error gr-mb-12", children: state.error }),
|
|
85
|
+
/* @__PURE__ */ jsx("button", { className: "gr-primary-btn gr-w-full", type: "submit", disabled: isPending || signingOut, children: isPending ? "Saving\u2026" : "Set password" })
|
|
89
86
|
] }),
|
|
90
87
|
/* @__PURE__ */ jsx(
|
|
91
88
|
"button",
|
|
92
89
|
{
|
|
93
90
|
type: "button",
|
|
94
91
|
disabled: isPending || signingOut,
|
|
95
|
-
|
|
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
|
-
},
|
|
92
|
+
className: "gr-text-btn gr-p-0 gr-mt-16",
|
|
105
93
|
onClick: () => startSignOut(async () => {
|
|
106
94
|
await logoutAction();
|
|
107
95
|
router.refresh();
|
|
@@ -116,7 +104,7 @@ function ForgotPasswordForm() {
|
|
|
116
104
|
const [status, setStatus] = useState("idle");
|
|
117
105
|
const [error, setError] = useState(null);
|
|
118
106
|
if (status === "sent") {
|
|
119
|
-
return /* @__PURE__ */ jsxs("p", {
|
|
107
|
+
return /* @__PURE__ */ jsxs("p", { className: "gr-fs-13 gr-lh-15 gr-m-0 gr-text-fg", children: [
|
|
120
108
|
"If ",
|
|
121
109
|
/* @__PURE__ */ jsx("strong", { children: email.trim() }),
|
|
122
110
|
" 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."
|
|
@@ -137,13 +125,13 @@ function ForgotPasswordForm() {
|
|
|
137
125
|
}
|
|
138
126
|
},
|
|
139
127
|
children: [
|
|
140
|
-
/* @__PURE__ */ jsx("p", {
|
|
141
|
-
/* @__PURE__ */ jsx("label", {
|
|
128
|
+
/* @__PURE__ */ jsx("p", { className: "gr-fs-13 gr-lh-15 gr-m-0 gr-mb-16 gr-text-muted", children: "Enter your email and we'll send a link to choose a new password." }),
|
|
129
|
+
/* @__PURE__ */ jsx("label", { className: "gr-label", htmlFor: "gr-forgot-email", children: "Email" }),
|
|
142
130
|
/* @__PURE__ */ jsx(
|
|
143
131
|
"input",
|
|
144
132
|
{
|
|
145
133
|
id: "gr-forgot-email",
|
|
146
|
-
|
|
134
|
+
className: "gr-field",
|
|
147
135
|
type: "email",
|
|
148
136
|
autoComplete: "email",
|
|
149
137
|
placeholder: "name@company.com",
|
|
@@ -153,8 +141,8 @@ function ForgotPasswordForm() {
|
|
|
153
141
|
autoFocus: true
|
|
154
142
|
}
|
|
155
143
|
),
|
|
156
|
-
error && /* @__PURE__ */ jsx("div", {
|
|
157
|
-
/* @__PURE__ */ jsx("button", {
|
|
144
|
+
error && /* @__PURE__ */ jsx("div", { className: "gr-error gr-mb-12", children: error }),
|
|
145
|
+
/* @__PURE__ */ jsx("button", { className: "gr-primary-btn gr-w-full", type: "submit", disabled: status === "sending", children: status === "sending" ? "Sending\u2026" : "Send reset link" })
|
|
158
146
|
]
|
|
159
147
|
}
|
|
160
148
|
);
|
|
@@ -1 +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":";AAqCI,mBAEE,KAiBE,YAnBJ;AAnCJ,OAAO,SAAS,gBAAgB,WAAW,UAAU,qBAAqB;AAC1E,SAAS,iBAAiB;AAC1B,SAAS,sBAAsB,cAAc,yBAAyB;AACtE,SAAS,iBAAiB;AAC1B,SAAS,GAAG,SAAS;AACrB,SAAS,qBAAqB;AAG9B,MAAM,uBAAyC,CAAC;AAczC,SAAS,WAAW;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAMG;AACD,SACE,iCAEE;AAAA,wBAAC,WAAM,MAAK,4BAA2B,YAAW,WAC/C,yBACH;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,kBAAc;AAAA,QACd,wBAAoB;AAAA,QACpB,OAAO,EAAE;AAAA,QAET,SAAS,SAAS,WAAW,UAAU;AAAA;AAAA,IACzC;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,kBAAc;AAAA,QACd,uBAAmB;AAAA,QACnB,OAAO,EAAE;AAAA,QACT,MAAK;AAAA,QACL,cAAY,SAAS,iBAAiB,sBAAsB;AAAA,QAE5D;AAAA,+BAAC,YAAO,OAAO,EAAE,YACf;AAAA,gCAAC,QAAG,OAAO,EAAE,aACV,oBAAU,SAAS,iBAAiB,sBAAsB,0BAC7D;AAAA,YACC,SAAS,YAAY,WACpB,oBAAC,YAAO,OAAO,EAAE,GAAG,EAAE,SAAS,OAAO,EAAE,OAAO,EAAE,GAAG,SAAS,SAAS,cAAW,SAC/E,8BAAC,aAAU,GACb;AAAA,aAEJ;AAAA,UACA,qBAAC,SAAI,OAAO,EAAE,YACX;AAAA,uBACC,oBAAC,OAAE,OAAO,EAAE,UAAU,IAAI,YAAY,KAAK,QAAQ,YAAY,OAAO,EAAE,IAAI,EAAE,GAAI,mBAAQ;AAAA,YAE3F,SAAS,iBAAiB,oBAAC,mBAAgB,IAAK,oBAAC,sBAAmB;AAAA,aACvE;AAAA;AAAA;AAAA,IACF;AAAA,KACF;AAEJ;AAEA,SAAS,kBAAkB;AACzB,QAAM,SAAS,UAAU;AACzB,QAAM,CAAC,OAAO,YAAY,SAAS,IAAI,eAAe,mBAAmB,oBAAoB;AAC7F,QAAM,CAAC,YAAY,YAAY,IAAI,cAAc;AAEjD,YAAU,MAAM;AACd,QAAI,MAAM,QAAS,QAAO,QAAQ;AAAA,EACpC,GAAG,CAAC,MAAM,SAAS,MAAM,CAAC;AAE1B,SACE,iCACE;AAAA,wBAAC,OAAE,OAAO,EAAE,UAAU,IAAI,YAAY,KAAK,QAAQ,YAAY,OAAO,EAAE,OAAO,EAAE,GAAG,qEAEpF;AAAA,IACA,qBAAC,UAAK,QAAQ,YACZ;AAAA,0BAAC,WAAM,OAAO,EAAE,OAAO,SAAQ,mBAAkB,0BAEjD;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,IAAG;AAAA,UACH,OAAO,EAAE;AAAA,UACT,MAAK;AAAA,UACL,MAAK;AAAA,UACL,cAAa;AAAA,UACb,WAAW;AAAA,UACX,UAAQ;AAAA,UACR,WAAS;AAAA;AAAA,MACX;AAAA,MACA,oBAAC,WAAM,OAAO,EAAE,OAAO,SAAQ,uBAAsB,8BAErD;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,IAAG;AAAA,UACH,OAAO,EAAE;AAAA,UACT,MAAK;AAAA,UACL,MAAK;AAAA,UACL,cAAa;AAAA,UACb,WAAW;AAAA,UACX,UAAQ;AAAA;AAAA,MACV;AAAA,MACA,oBAAC,OAAE,OAAO,EAAE,UAAU,IAAI,OAAO,EAAE,QAAQ,GAAG,QAAQ,WAAW,GAAG,oCAAsB;AAAA,MACzF,MAAM,SAAS,oBAAC,SAAI,OAAO,EAAE,GAAG,EAAE,OAAO,cAAc,GAAG,GAAI,gBAAM,OAAM;AAAA,MAC3E,oBAAC,YAAO,OAAO,EAAE,GAAG,EAAE,YAAY,OAAO,OAAO,GAAG,MAAK,UAAS,UAAU,aAAa,YACrF,sBAAY,iBAAY,gBAC3B;AAAA,OACF;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,UAAU,aAAa;AAAA,QACvB,OAAO;AAAA,UACL,YAAY;AAAA,UACZ,QAAQ;AAAA,UACR,QAAQ;AAAA,UACR,OAAO,EAAE,OAAO;AAAA,UAChB,UAAU;AAAA,UACV,gBAAgB;AAAA,UAChB,SAAS;AAAA,UACT,WAAW;AAAA,QACb;AAAA,QACA,SAAS,MACP,aAAa,YAAY;AACvB,gBAAM,aAAa;AACnB,iBAAO,QAAQ;AAAA,QACjB,CAAC;AAAA,QAEJ;AAAA;AAAA,IAED;AAAA,KACF;AAEJ;AAEA,SAAS,qBAAqB;AAC5B,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAS,EAAE;AACrC,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAsC,MAAM;AACxE,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAwB,IAAI;AAEtD,MAAI,WAAW,QAAQ;AACrB,WACE,qBAAC,OAAE,OAAO,EAAE,UAAU,IAAI,YAAY,KAAK,QAAQ,GAAG,OAAO,EAAE,IAAI,EAAE,GAAG;AAAA;AAAA,MACnE,oBAAC,YAAQ,gBAAM,KAAK,GAAE;AAAA,MAAS;AAAA,OAEpC;AAAA,EAEJ;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,UAAU,OAAO,MAAM;AACrB,UAAE,eAAe;AACjB,kBAAU,SAAS;AACnB,iBAAS,IAAI;AACb,cAAM,MAAM,MAAM,qBAAqB,KAAK;AAC5C,YAAI,IAAI,QAAS,WAAU,MAAM;AAAA,aAC5B;AACH,mBAAS,IAAI,SAAS,uBAAuB;AAC7C,oBAAU,MAAM;AAAA,QAClB;AAAA,MACF;AAAA,MAEA;AAAA,4BAAC,OAAE,OAAO,EAAE,UAAU,IAAI,YAAY,KAAK,QAAQ,YAAY,OAAO,EAAE,OAAO,EAAE,GAAG,8EAEpF;AAAA,QACA,oBAAC,WAAM,OAAO,EAAE,OAAO,SAAQ,mBAAkB,mBAEjD;AAAA,QACA;AAAA,UAAC;AAAA;AAAA,YACC,IAAG;AAAA,YACH,OAAO,EAAE;AAAA,YACT,MAAK;AAAA,YACL,cAAa;AAAA,YACb,aAAY;AAAA,YACZ,OAAO;AAAA,YACP,UAAU,CAAC,MAAM,SAAS,EAAE,OAAO,KAAK;AAAA,YACxC,UAAQ;AAAA,YACR,WAAS;AAAA;AAAA,QACX;AAAA,QACC,SAAS,oBAAC,SAAI,OAAO,EAAE,GAAG,EAAE,OAAO,cAAc,GAAG,GAAI,iBAAM;AAAA,QAC/D,oBAAC,YAAO,OAAO,EAAE,GAAG,EAAE,YAAY,OAAO,OAAO,GAAG,MAAK,UAAS,UAAU,WAAW,WACnF,qBAAW,YAAY,kBAAa,mBACvC;AAAA;AAAA;AAAA,EACF;AAEJ;","names":[]}
|
|
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 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 <div\n data-guardrail\n data-guardrail-scrim\n className=\"gr-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 className=\"gr-drawer\"\n role=\"dialog\"\n aria-label={mode === \"set-password\" ? \"Set your password\" : \"Forgot password\"}\n >\n <header className=\"gr-drawer-head\">\n <h2 className=\"gr-drawer-title\">\n {title ?? (mode === \"set-password\" ? \"Set your password\" : \"Forgot your password?\")}\n </h2>\n {mode === \"forgot\" && onClose && (\n <button className=\"gr-icon-btn gr-text-muted\" onClick={onClose} aria-label=\"Close\">\n <CloseIcon />\n </button>\n )}\n </header>\n <div className=\"gr-drawer-body\">\n {message && (\n <p className=\"gr-fs-13 gr-lh-15 gr-m-0 gr-mb-16 gr-text-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 className=\"gr-fs-13 gr-lh-15 gr-m-0 gr-mb-16 gr-text-muted\">\n Choose a password. It's how you'll sign in from now on.\n </p>\n <form action={formAction}>\n <label className=\"gr-label\" htmlFor=\"gr-new-password\">\n New password\n </label>\n <input\n id=\"gr-new-password\"\n className=\"gr-field\"\n name=\"password\"\n type=\"password\"\n autoComplete=\"new-password\"\n minLength={8}\n required\n autoFocus\n />\n <label className=\"gr-label\" htmlFor=\"gr-confirm-password\">\n Confirm password\n </label>\n <input\n id=\"gr-confirm-password\"\n className=\"gr-field\"\n name=\"confirm\"\n type=\"password\"\n autoComplete=\"new-password\"\n minLength={8}\n required\n />\n <p className=\"gr-fs-11 gr-text-subtle gr-m-0 gr-mb-12\">At least 8 characters.</p>\n {state.error && <div className=\"gr-error gr-mb-12\">{state.error}</div>}\n <button className=\"gr-primary-btn gr-w-full\" type=\"submit\" disabled={isPending || signingOut}>\n {isPending ? \"Saving…\" : \"Set password\"}\n </button>\n </form>\n <button\n type=\"button\"\n disabled={isPending || signingOut}\n className=\"gr-text-btn gr-p-0 gr-mt-16\"\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 className=\"gr-fs-13 gr-lh-15 gr-m-0 gr-text-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 className=\"gr-fs-13 gr-lh-15 gr-m-0 gr-mb-16 gr-text-muted\">\n Enter your email and we'll send a link to choose a new password.\n </p>\n <label className=\"gr-label\" htmlFor=\"gr-forgot-email\">\n Email\n </label>\n <input\n id=\"gr-forgot-email\"\n className=\"gr-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 className=\"gr-error gr-mb-12\">{error}</div>}\n <button className=\"gr-primary-btn gr-w-full\" type=\"submit\" disabled={status === \"sending\"}>\n {status === \"sending\" ? \"Sending…\" : \"Send reset link\"}\n </button>\n </form>\n );\n}\n"],"mappings":";AAmCI,mBACE,KAcE,YAfJ;AAjCJ,OAAO,SAAS,gBAAgB,WAAW,UAAU,qBAAqB;AAC1E,SAAS,iBAAiB;AAC1B,SAAS,sBAAsB,cAAc,yBAAyB;AACtE,SAAS,iBAAiB;AAG1B,MAAM,uBAAyC,CAAC;AAczC,SAAS,WAAW;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAMG;AACD,SACE,iCACE;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,kBAAc;AAAA,QACd,wBAAoB;AAAA,QACpB,WAAU;AAAA,QAEV,SAAS,SAAS,WAAW,UAAU;AAAA;AAAA,IACzC;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,kBAAc;AAAA,QACd,uBAAmB;AAAA,QACnB,WAAU;AAAA,QACV,MAAK;AAAA,QACL,cAAY,SAAS,iBAAiB,sBAAsB;AAAA,QAE5D;AAAA,+BAAC,YAAO,WAAU,kBAChB;AAAA,gCAAC,QAAG,WAAU,mBACX,oBAAU,SAAS,iBAAiB,sBAAsB,0BAC7D;AAAA,YACC,SAAS,YAAY,WACpB,oBAAC,YAAO,WAAU,6BAA4B,SAAS,SAAS,cAAW,SACzE,8BAAC,aAAU,GACb;AAAA,aAEJ;AAAA,UACA,qBAAC,SAAI,WAAU,kBACZ;AAAA,uBACC,oBAAC,OAAE,WAAU,gDAAgD,mBAAQ;AAAA,YAEtE,SAAS,iBAAiB,oBAAC,mBAAgB,IAAK,oBAAC,sBAAmB;AAAA,aACvE;AAAA;AAAA;AAAA,IACF;AAAA,KACF;AAEJ;AAEA,SAAS,kBAAkB;AACzB,QAAM,SAAS,UAAU;AACzB,QAAM,CAAC,OAAO,YAAY,SAAS,IAAI,eAAe,mBAAmB,oBAAoB;AAC7F,QAAM,CAAC,YAAY,YAAY,IAAI,cAAc;AAEjD,YAAU,MAAM;AACd,QAAI,MAAM,QAAS,QAAO,QAAQ;AAAA,EACpC,GAAG,CAAC,MAAM,SAAS,MAAM,CAAC;AAE1B,SACE,iCACE;AAAA,wBAAC,OAAE,WAAU,mDAAkD,qEAE/D;AAAA,IACA,qBAAC,UAAK,QAAQ,YACZ;AAAA,0BAAC,WAAM,WAAU,YAAW,SAAQ,mBAAkB,0BAEtD;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,IAAG;AAAA,UACH,WAAU;AAAA,UACV,MAAK;AAAA,UACL,MAAK;AAAA,UACL,cAAa;AAAA,UACb,WAAW;AAAA,UACX,UAAQ;AAAA,UACR,WAAS;AAAA;AAAA,MACX;AAAA,MACA,oBAAC,WAAM,WAAU,YAAW,SAAQ,uBAAsB,8BAE1D;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,IAAG;AAAA,UACH,WAAU;AAAA,UACV,MAAK;AAAA,UACL,MAAK;AAAA,UACL,cAAa;AAAA,UACb,WAAW;AAAA,UACX,UAAQ;AAAA;AAAA,MACV;AAAA,MACA,oBAAC,OAAE,WAAU,2CAA0C,oCAAsB;AAAA,MAC5E,MAAM,SAAS,oBAAC,SAAI,WAAU,qBAAqB,gBAAM,OAAM;AAAA,MAChE,oBAAC,YAAO,WAAU,4BAA2B,MAAK,UAAS,UAAU,aAAa,YAC/E,sBAAY,iBAAY,gBAC3B;AAAA,OACF;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,UAAU,aAAa;AAAA,QACvB,WAAU;AAAA,QACV,SAAS,MACP,aAAa,YAAY;AACvB,gBAAM,aAAa;AACnB,iBAAO,QAAQ;AAAA,QACjB,CAAC;AAAA,QAEJ;AAAA;AAAA,IAED;AAAA,KACF;AAEJ;AAEA,SAAS,qBAAqB;AAC5B,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAS,EAAE;AACrC,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAsC,MAAM;AACxE,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAwB,IAAI;AAEtD,MAAI,WAAW,QAAQ;AACrB,WACE,qBAAC,OAAE,WAAU,uCAAsC;AAAA;AAAA,MAC9C,oBAAC,YAAQ,gBAAM,KAAK,GAAE;AAAA,MAAS;AAAA,OAEpC;AAAA,EAEJ;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,UAAU,OAAO,MAAM;AACrB,UAAE,eAAe;AACjB,kBAAU,SAAS;AACnB,iBAAS,IAAI;AACb,cAAM,MAAM,MAAM,qBAAqB,KAAK;AAC5C,YAAI,IAAI,QAAS,WAAU,MAAM;AAAA,aAC5B;AACH,mBAAS,IAAI,SAAS,uBAAuB;AAC7C,oBAAU,MAAM;AAAA,QAClB;AAAA,MACF;AAAA,MAEA;AAAA,4BAAC,OAAE,WAAU,mDAAkD,8EAE/D;AAAA,QACA,oBAAC,WAAM,WAAU,YAAW,SAAQ,mBAAkB,mBAEtD;AAAA,QACA;AAAA,UAAC;AAAA;AAAA,YACC,IAAG;AAAA,YACH,WAAU;AAAA,YACV,MAAK;AAAA,YACL,cAAa;AAAA,YACb,aAAY;AAAA,YACZ,OAAO;AAAA,YACP,UAAU,CAAC,MAAM,SAAS,EAAE,OAAO,KAAK;AAAA,YACxC,UAAQ;AAAA,YACR,WAAS;AAAA;AAAA,QACX;AAAA,QACC,SAAS,oBAAC,SAAI,WAAU,qBAAqB,iBAAM;AAAA,QACpD,oBAAC,YAAO,WAAU,4BAA2B,MAAK,UAAS,UAAU,WAAW,WAC7E,qBAAW,YAAY,kBAAa,mBACvC;AAAA;AAAA;AAAA,EACF;AAEJ;","names":[]}
|
|
@@ -9,7 +9,6 @@ import { useGRail } from "../Provider";
|
|
|
9
9
|
import { useAnchorRect } from "../useAnchorRect";
|
|
10
10
|
import { CheckIcon, CloseIcon, OpenIcon, PencilIcon, SpinnerIcon } from "../Icons";
|
|
11
11
|
import { openHref } from "../openHref";
|
|
12
|
-
import { S } from "../styles";
|
|
13
12
|
import { MediaPicker } from "../MediaPicker";
|
|
14
13
|
function GRailClient({
|
|
15
14
|
as,
|
|
@@ -84,17 +83,8 @@ function GRailClient({
|
|
|
84
83
|
...htmlProps,
|
|
85
84
|
ref: elementRef,
|
|
86
85
|
override: renderedValue,
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
// Images are left alone — no outline, position, or z-index — and
|
|
90
|
-
// get a separate ImageMarker instead. Restyling the <img> itself
|
|
91
|
-
// can't work for a background image with content layered over it.
|
|
92
|
-
...isImage ? { cursor: "pointer" } : {
|
|
93
|
-
outline: editing ? "2px solid #2563eb" : "1px dashed #60a5fa",
|
|
94
|
-
outlineOffset: 2,
|
|
95
|
-
cursor: editing ? "text" : "pointer"
|
|
96
|
-
}
|
|
97
|
-
},
|
|
86
|
+
"data-gr-field": isImage ? "image" : "text",
|
|
87
|
+
...editing && !isImage ? { "data-gr-editing": "" } : {},
|
|
98
88
|
onClick: (e) => {
|
|
99
89
|
if (editing) return;
|
|
100
90
|
e.preventDefault();
|
|
@@ -168,13 +158,13 @@ function ImageMarker({
|
|
|
168
158
|
"div",
|
|
169
159
|
{
|
|
170
160
|
"data-guardrail": true,
|
|
161
|
+
className: "gr-marker-outline",
|
|
162
|
+
...editing ? { "data-gr-editing": "" } : {},
|
|
171
163
|
style: {
|
|
172
|
-
...S.markerOutline,
|
|
173
164
|
top: rect.top - 2,
|
|
174
165
|
left: rect.left - 2,
|
|
175
166
|
width: rect.width + 4,
|
|
176
|
-
height: height + 4
|
|
177
|
-
border: editing ? "2px solid #2563eb" : "1px dashed #60a5fa"
|
|
167
|
+
height: height + 4
|
|
178
168
|
}
|
|
179
169
|
}
|
|
180
170
|
),
|
|
@@ -183,7 +173,8 @@ function ImageMarker({
|
|
|
183
173
|
{
|
|
184
174
|
type: "button",
|
|
185
175
|
"data-guardrail": true,
|
|
186
|
-
|
|
176
|
+
className: "gr-marker-handle",
|
|
177
|
+
style: { top: handleTop, left: handleLeft },
|
|
187
178
|
"aria-label": "Edit image",
|
|
188
179
|
title: "Edit image",
|
|
189
180
|
onClick: (e) => {
|
|
@@ -226,7 +217,7 @@ function Chrome({
|
|
|
226
217
|
/* @__PURE__ */ jsx(
|
|
227
218
|
"button",
|
|
228
219
|
{
|
|
229
|
-
|
|
220
|
+
className: "gr-icon-btn-primary",
|
|
230
221
|
onClick: onSave,
|
|
231
222
|
disabled: saving,
|
|
232
223
|
title: "Save (Enter)",
|
|
@@ -237,7 +228,7 @@ function Chrome({
|
|
|
237
228
|
/* @__PURE__ */ jsx(
|
|
238
229
|
"button",
|
|
239
230
|
{
|
|
240
|
-
|
|
231
|
+
className: "gr-icon-btn",
|
|
241
232
|
onClick: onCancel,
|
|
242
233
|
disabled: saving,
|
|
243
234
|
title: "Cancel (Esc)",
|
|
@@ -247,46 +238,47 @@ function Chrome({
|
|
|
247
238
|
)
|
|
248
239
|
] });
|
|
249
240
|
if (isInline) {
|
|
250
|
-
return /* @__PURE__ */ jsxs("div", { "data-guardrail": true,
|
|
241
|
+
return /* @__PURE__ */ jsxs("div", { "data-guardrail": true, className: "gr-anchored-bar", style: position, children: [
|
|
251
242
|
actions,
|
|
252
|
-
error && /* @__PURE__ */ jsx("span", {
|
|
243
|
+
error && /* @__PURE__ */ jsx("span", { className: "gr-error gr-mx-6", children: error })
|
|
253
244
|
] });
|
|
254
245
|
}
|
|
255
246
|
return /* @__PURE__ */ jsxs(
|
|
256
247
|
"div",
|
|
257
248
|
{
|
|
258
249
|
"data-guardrail": true,
|
|
259
|
-
|
|
250
|
+
className: "gr-anchored-panel",
|
|
251
|
+
style: position,
|
|
260
252
|
onClick: (e) => e.stopPropagation(),
|
|
261
253
|
children: [
|
|
262
254
|
row.value.type === "image" && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
263
255
|
row.value.src && // Plain img: previews must not depend on remotePatterns.
|
|
264
|
-
/* @__PURE__ */ jsx("img", { src: row.value.src, alt: "",
|
|
256
|
+
/* @__PURE__ */ jsx("img", { src: row.value.src, alt: "", className: "gr-preview-thumb" }),
|
|
265
257
|
storage && /* @__PURE__ */ jsx(
|
|
266
258
|
"button",
|
|
267
259
|
{
|
|
268
260
|
type: "button",
|
|
269
|
-
|
|
261
|
+
className: "gr-secondary-btn",
|
|
270
262
|
onClick: () => setPickerOpen(true),
|
|
271
263
|
children: row.value.src ? "Replace image" : "Choose image"
|
|
272
264
|
}
|
|
273
265
|
),
|
|
274
|
-
/* @__PURE__ */ jsx("label", {
|
|
266
|
+
/* @__PURE__ */ jsx("label", { className: "gr-label", children: "Alt text" }),
|
|
275
267
|
/* @__PURE__ */ jsx(
|
|
276
268
|
"input",
|
|
277
269
|
{
|
|
278
|
-
|
|
270
|
+
className: "gr-field",
|
|
279
271
|
value: row.value.alt || "",
|
|
280
272
|
placeholder: "Describe the image for screen readers",
|
|
281
273
|
onChange: (e) => onChange("alt", e.target.value)
|
|
282
274
|
}
|
|
283
275
|
),
|
|
284
276
|
!storage && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
285
|
-
/* @__PURE__ */ jsx("label", {
|
|
277
|
+
/* @__PURE__ */ jsx("label", { className: "gr-label", children: "Image URL" }),
|
|
286
278
|
/* @__PURE__ */ jsx(
|
|
287
279
|
"input",
|
|
288
280
|
{
|
|
289
|
-
|
|
281
|
+
className: "gr-field",
|
|
290
282
|
value: row.value.src || "",
|
|
291
283
|
onChange: (e) => onPatch({ src: e.target.value, width: void 0, height: void 0 })
|
|
292
284
|
}
|
|
@@ -302,48 +294,36 @@ function Chrome({
|
|
|
302
294
|
)
|
|
303
295
|
] }),
|
|
304
296
|
row.value.type === "link" && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
305
|
-
/* @__PURE__ */ jsx("label", {
|
|
297
|
+
/* @__PURE__ */ jsx("label", { className: "gr-label", children: "Link text" }),
|
|
306
298
|
/* @__PURE__ */ jsx(
|
|
307
299
|
"input",
|
|
308
300
|
{
|
|
309
|
-
|
|
301
|
+
className: "gr-field",
|
|
310
302
|
value: row.value.text || "",
|
|
311
303
|
onChange: (e) => onChange("text", e.target.value)
|
|
312
304
|
}
|
|
313
305
|
),
|
|
314
|
-
/* @__PURE__ */ jsx("label", {
|
|
306
|
+
/* @__PURE__ */ jsx("label", { className: "gr-label", children: "URL" }),
|
|
315
307
|
/* @__PURE__ */ jsx(
|
|
316
308
|
"input",
|
|
317
309
|
{
|
|
318
|
-
|
|
310
|
+
className: "gr-field",
|
|
319
311
|
value: row.value.href || "",
|
|
320
312
|
onChange: (e) => onChange("href", e.target.value)
|
|
321
313
|
}
|
|
322
314
|
)
|
|
323
315
|
] }),
|
|
324
|
-
error && /* @__PURE__ */ jsx("div", {
|
|
316
|
+
error && /* @__PURE__ */ jsx("div", { className: "gr-error gr-mb-8", children: error }),
|
|
325
317
|
/* @__PURE__ */ jsxs(
|
|
326
318
|
"div",
|
|
327
319
|
{
|
|
328
|
-
|
|
329
|
-
display: "flex",
|
|
330
|
-
gap: 4,
|
|
331
|
-
alignItems: "center",
|
|
332
|
-
justifyContent: row.value.type === "link" ? "space-between" : "flex-end"
|
|
333
|
-
},
|
|
320
|
+
className: row.value.type === "link" ? "gr-flex gr-gap-4 gr-items-center gr-justify-between" : "gr-flex gr-gap-4 gr-items-center gr-justify-end",
|
|
334
321
|
children: [
|
|
335
322
|
row.value.type === "link" && /* @__PURE__ */ jsxs(
|
|
336
323
|
"button",
|
|
337
324
|
{
|
|
338
325
|
type: "button",
|
|
339
|
-
|
|
340
|
-
...S.secondaryBtn,
|
|
341
|
-
width: "auto",
|
|
342
|
-
marginBottom: 0,
|
|
343
|
-
display: "inline-flex",
|
|
344
|
-
alignItems: "center",
|
|
345
|
-
gap: 6
|
|
346
|
-
},
|
|
326
|
+
className: "gr-secondary-btn gr-w-auto gr-mb-0 gr-inline-flex gr-items-center gr-gap-6",
|
|
347
327
|
disabled: !row.value.href?.trim(),
|
|
348
328
|
onClick: () => openHref(row.value.href ?? "", (path) => router.push(path)),
|
|
349
329
|
title: "Open this link",
|
|
@@ -354,7 +334,7 @@ function Chrome({
|
|
|
354
334
|
]
|
|
355
335
|
}
|
|
356
336
|
),
|
|
357
|
-
/* @__PURE__ */ jsx("div", {
|
|
337
|
+
/* @__PURE__ */ jsx("div", { className: "gr-flex gr-gap-4", children: actions })
|
|
358
338
|
]
|
|
359
339
|
}
|
|
360
340
|
)
|