@guard-rail/core 0.10.0 → 0.12.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 +135 -4
- package/dist/adapters/supabase/db.js +33 -0
- package/dist/adapters/supabase/db.js.map +1 -1
- package/dist/cli/doctor.js +175 -6
- package/dist/cli/doctor.js.map +1 -1
- package/dist/core/adapter-types.d.ts +35 -2
- 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 +3 -2
- package/dist/index.client.js +8 -1
- package/dist/index.client.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +8 -1
- 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 +95 -56
- package/dist/react/GRail/client.js.map +1 -1
- package/dist/react/Icons.d.ts +3 -1
- package/dist/react/Icons.js +5 -0
- package/dist/react/Icons.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/dist/server/actions.d.ts +19 -2
- package/dist/server/actions.js +35 -0
- package/dist/server/actions.js.map +1 -1
- package/package.json +11 -10
- package/supabase/admin/check-grants.sql +12 -3
- package/supabase/migrations/20261103000000_content_history.sql +211 -0
- package/dist/react/styles.d.ts +0 -504
- package/dist/react/styles.js +0 -500
- package/dist/react/styles.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/react/MediaPicker.tsx"],"sourcesContent":["\"use client\";\n\nimport React, { useCallback, useEffect, useRef, useState } from \"react\";\nimport { createAssetUpload, listAssets } from \"../server/actions\";\nimport { CloseIcon, SpinnerIcon } from \"./Icons\";\nimport { S, v } from \"./styles\";\nimport { prepareImage } from \"./resizeImage\";\nimport type { GRAsset } from \"../core/types\";\n\nexport interface PickedImage {\n src: string;\n width?: number;\n height?: number;\n}\n\ntype Size = { width?: number; height?: number };\n\n/**\n * Natural size is read in the browser, so no dimensions need storing on the\n * storage side and every adapter gets them for free.\n */\nfunction measure(url: string): Promise<Size> {\n return new Promise((resolve) => {\n const img = new Image();\n img.onload = () => resolve({ width: img.naturalWidth, height: img.naturalHeight });\n img.onerror = () => resolve({});\n img.src = url;\n });\n}\n\nexport function MediaPicker({\n current,\n onSelect,\n onClose,\n}: {\n current?: string;\n onSelect: (image: PickedImage) => void;\n onClose: () => void;\n}) {\n const [assets, setAssets] = useState<GRAsset[] | null>(null);\n const [uploading, setUploading] = useState(false);\n const [dragging, setDragging] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const inputRef = useRef<HTMLInputElement>(null);\n\n useEffect(() => {\n void listAssets().then(setAssets);\n }, []);\n\n useEffect(() => {\n const onKey = (e: KeyboardEvent) => {\n if (e.key !== \"Escape\") return;\n e.stopPropagation();\n onClose();\n };\n window.addEventListener(\"keydown\", onKey, true);\n return () => window.removeEventListener(\"keydown\", onKey, true);\n }, [onClose]);\n\n const choose = useCallback(\n (src: string, size: Size) => {\n onSelect({ src, ...size });\n onClose();\n },\n [onSelect, onClose],\n );\n\n const upload = async (file: File) => {\n if (uploading) return;\n setError(null);\n setUploading(true);\n try {\n // Scaled first, so the size check and the upload both see the file\n // that will actually be stored — an oversized phone photo that shrinks\n // under the cap is accepted.\n const prepared = await prepareImage(file);\n const payload = prepared.file;\n\n const res = await createAssetUpload({\n name: payload.name,\n contentType: payload.type,\n size: payload.size,\n });\n if (!res.success) {\n setError(res.error);\n return;\n }\n const { target } = res;\n\n // Straight to storage — the bytes never touch the Next.js server.\n const put = await fetch(target.url, {\n method: target.method,\n headers: target.headers,\n body: payload,\n });\n if (!put.ok) {\n setError(`Upload failed (${put.status}).`);\n return;\n }\n\n setAssets((prev) => [target.asset, ...(prev ?? [])]);\n choose(target.asset.url, { width: prepared.width, height: prepared.height });\n } catch {\n setError(\"Upload failed. Check your connection and try again.\");\n } finally {\n setUploading(false);\n }\n };\n\n const onFiles = (files: FileList | null | undefined) => {\n const file = files?.[0];\n if (file) void upload(file);\n };\n\n return (\n <>\n <div data-guardrail style={S.pickerScrim} onClick={onClose} />\n <div\n data-guardrail\n role=\"dialog\"\n aria-label=\"Choose an image\"\n style={S.picker}\n onClick={(e) => e.stopPropagation()}\n >\n <header style={S.drawerHead}>\n <h2 style={S.drawerTitle}>Choose an image</h2>\n <button\n style={{ ...S.iconBtn, color: v(\"muted\") }}\n onClick={onClose}\n aria-label=\"Close\"\n >\n <CloseIcon />\n </button>\n </header>\n\n <div style={S.drawerBody}>\n <div\n role=\"button\"\n tabIndex={0}\n style={{ ...S.dropzone, ...(dragging ? S.dropzoneActive : {}) }}\n onClick={() => !uploading && inputRef.current?.click()}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" || e.key === \" \") inputRef.current?.click();\n }}\n onDragOver={(e) => {\n e.preventDefault();\n setDragging(true);\n }}\n onDragLeave={() => setDragging(false)}\n onDrop={(e) => {\n e.preventDefault();\n setDragging(false);\n onFiles(e.dataTransfer.files);\n }}\n >\n {uploading ? (\n <span style={{ display: \"inline-flex\", gap: 8, alignItems: \"center\" }}>\n <SpinnerIcon /> Uploading…\n </span>\n ) : (\n \"Drop an image here, or click to upload\"\n )}\n <input\n ref={inputRef}\n type=\"file\"\n accept=\"image/png,image/jpeg,image/webp,image/gif,image/avif\"\n hidden\n onChange={(e) => {\n onFiles(e.target.files);\n e.target.value = \"\";\n }}\n />\n </div>\n\n {error && <div style={{ ...S.error, marginBottom: 12 }}>{error}</div>}\n\n {assets === null ? (\n <p style={{ fontSize: 13, color: v(\"muted\") }}>Loading…</p>\n ) : assets.length === 0 ? (\n <p style={{ fontSize: 13, color: v(\"muted\") }}>No images uploaded yet.</p>\n ) : (\n <div style={S.pickerGrid}>\n {assets.map((asset) => (\n <button\n key={asset.id}\n type=\"button\"\n title={asset.name}\n style={{\n ...S.pickerTile,\n ...(asset.url === current ? S.pickerTileActive : {}),\n }}\n onClick={async () => choose(asset.url, await measure(asset.url))}\n >\n {/* Plain img: the admin UI must not depend on next/image's\n remotePatterns being configured. */}\n <img src={asset.url} alt=\"\" loading=\"lazy\" style={S.pickerThumb} />\n </button>\n ))}\n </div>\n )}\n </div>\n </div>\n </>\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/react/MediaPicker.tsx"],"sourcesContent":["\"use client\";\n\nimport React, { useCallback, useEffect, useRef, useState } from \"react\";\nimport { createAssetUpload, listAssets } from \"../server/actions\";\nimport { CloseIcon, SpinnerIcon } from \"./Icons\";\nimport { prepareImage } from \"./resizeImage\";\nimport type { GRAsset } from \"../core/types\";\n\nexport interface PickedImage {\n src: string;\n width?: number;\n height?: number;\n}\n\ntype Size = { width?: number; height?: number };\n\n/**\n * Natural size is read in the browser, so no dimensions need storing on the\n * storage side and every adapter gets them for free.\n */\nfunction measure(url: string): Promise<Size> {\n return new Promise((resolve) => {\n const img = new Image();\n img.onload = () => resolve({ width: img.naturalWidth, height: img.naturalHeight });\n img.onerror = () => resolve({});\n img.src = url;\n });\n}\n\nexport function MediaPicker({\n current,\n onSelect,\n onClose,\n}: {\n current?: string;\n onSelect: (image: PickedImage) => void;\n onClose: () => void;\n}) {\n const [assets, setAssets] = useState<GRAsset[] | null>(null);\n const [uploading, setUploading] = useState(false);\n const [dragging, setDragging] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const inputRef = useRef<HTMLInputElement>(null);\n\n useEffect(() => {\n void listAssets().then(setAssets);\n }, []);\n\n useEffect(() => {\n const onKey = (e: KeyboardEvent) => {\n if (e.key !== \"Escape\") return;\n e.stopPropagation();\n onClose();\n };\n window.addEventListener(\"keydown\", onKey, true);\n return () => window.removeEventListener(\"keydown\", onKey, true);\n }, [onClose]);\n\n const choose = useCallback(\n (src: string, size: Size) => {\n onSelect({ src, ...size });\n onClose();\n },\n [onSelect, onClose],\n );\n\n const upload = async (file: File) => {\n if (uploading) return;\n setError(null);\n setUploading(true);\n try {\n // Scaled first, so the size check and the upload both see the file\n // that will actually be stored — an oversized phone photo that shrinks\n // under the cap is accepted.\n const prepared = await prepareImage(file);\n const payload = prepared.file;\n\n const res = await createAssetUpload({\n name: payload.name,\n contentType: payload.type,\n size: payload.size,\n });\n if (!res.success) {\n setError(res.error);\n return;\n }\n const { target } = res;\n\n // Straight to storage — the bytes never touch the Next.js server.\n const put = await fetch(target.url, {\n method: target.method,\n headers: target.headers,\n body: payload,\n });\n if (!put.ok) {\n setError(`Upload failed (${put.status}).`);\n return;\n }\n\n setAssets((prev) => [target.asset, ...(prev ?? [])]);\n choose(target.asset.url, { width: prepared.width, height: prepared.height });\n } catch {\n setError(\"Upload failed. Check your connection and try again.\");\n } finally {\n setUploading(false);\n }\n };\n\n const onFiles = (files: FileList | null | undefined) => {\n const file = files?.[0];\n if (file) void upload(file);\n };\n\n return (\n <>\n <div data-guardrail className=\"gr-picker-scrim\" onClick={onClose} />\n <div\n data-guardrail\n role=\"dialog\"\n aria-label=\"Choose an image\"\n className=\"gr-picker\"\n onClick={(e) => e.stopPropagation()}\n >\n <header className=\"gr-drawer-head\">\n <h2 className=\"gr-drawer-title\">Choose an image</h2>\n <button\n className=\"gr-icon-btn gr-text-muted\"\n onClick={onClose}\n aria-label=\"Close\"\n >\n <CloseIcon />\n </button>\n </header>\n\n <div className=\"gr-drawer-body\">\n <div\n role=\"button\"\n tabIndex={0}\n className={dragging ? \"gr-dropzone gr-dropzone-active\" : \"gr-dropzone\"}\n onClick={() => !uploading && inputRef.current?.click()}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" || e.key === \" \") inputRef.current?.click();\n }}\n onDragOver={(e) => {\n e.preventDefault();\n setDragging(true);\n }}\n onDragLeave={() => setDragging(false)}\n onDrop={(e) => {\n e.preventDefault();\n setDragging(false);\n onFiles(e.dataTransfer.files);\n }}\n >\n {uploading ? (\n <span className=\"gr-inline-flex gr-gap-8 gr-items-center\">\n <SpinnerIcon /> Uploading…\n </span>\n ) : (\n \"Drop an image here, or click to upload\"\n )}\n <input\n ref={inputRef}\n type=\"file\"\n accept=\"image/png,image/jpeg,image/webp,image/gif,image/avif\"\n hidden\n onChange={(e) => {\n onFiles(e.target.files);\n e.target.value = \"\";\n }}\n />\n </div>\n\n {error && <div className=\"gr-error gr-mb-12\">{error}</div>}\n\n {assets === null ? (\n <p className=\"gr-fs-13 gr-text-muted\">Loading…</p>\n ) : assets.length === 0 ? (\n <p className=\"gr-fs-13 gr-text-muted\">No images uploaded yet.</p>\n ) : (\n <div className=\"gr-picker-grid\">\n {assets.map((asset) => (\n <button\n key={asset.id}\n type=\"button\"\n title={asset.name}\n className={asset.url === current ? \"gr-picker-tile gr-picker-tile-active\" : \"gr-picker-tile\"}\n onClick={async () => choose(asset.url, await measure(asset.url))}\n >\n {/* Plain img: the admin UI must not depend on next/image's\n remotePatterns being configured. */}\n <img src={asset.url} alt=\"\" loading=\"lazy\" className=\"gr-picker-thumb\" />\n </button>\n ))}\n </div>\n )}\n </div>\n </div>\n </>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -11,7 +11,6 @@ import {
|
|
|
11
11
|
SpinnerIcon,
|
|
12
12
|
TrashIcon
|
|
13
13
|
} from "../Icons";
|
|
14
|
-
import { S, v } from "../styles";
|
|
15
14
|
function RepeaterClient({
|
|
16
15
|
repeaterId,
|
|
17
16
|
routed,
|
|
@@ -56,15 +55,15 @@ function RepeaterClient({
|
|
|
56
55
|
};
|
|
57
56
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
58
57
|
items.length === 0 && children,
|
|
59
|
-
items.map(({ id, href, node }, index) => /* @__PURE__ */ jsxs("div", {
|
|
60
|
-
/* @__PURE__ */ jsxs("div", {
|
|
58
|
+
items.map(({ id, href, node }, index) => /* @__PURE__ */ jsxs("div", { className: "gr-repeater-item", children: [
|
|
59
|
+
/* @__PURE__ */ jsxs("div", { className: "gr-repeater-toolbar", children: [
|
|
61
60
|
href && // Clicking the item's fields edits them rather than following
|
|
62
61
|
// any link around them, so this is the way to its own page. A
|
|
63
62
|
// same-tab router push: the session keeps edit mode on.
|
|
64
63
|
/* @__PURE__ */ jsx(
|
|
65
64
|
"button",
|
|
66
65
|
{
|
|
67
|
-
|
|
66
|
+
className: "gr-icon-btn",
|
|
68
67
|
onClick: () => router.push(href),
|
|
69
68
|
"aria-label": "Open page",
|
|
70
69
|
title: "Open this item's page",
|
|
@@ -74,7 +73,7 @@ function RepeaterClient({
|
|
|
74
73
|
/* @__PURE__ */ jsx(
|
|
75
74
|
"button",
|
|
76
75
|
{
|
|
77
|
-
|
|
76
|
+
className: "gr-icon-btn",
|
|
78
77
|
disabled: pending || index === 0,
|
|
79
78
|
onClick: () => move(id, -1),
|
|
80
79
|
"aria-label": "Move up",
|
|
@@ -85,7 +84,7 @@ function RepeaterClient({
|
|
|
85
84
|
/* @__PURE__ */ jsx(
|
|
86
85
|
"button",
|
|
87
86
|
{
|
|
88
|
-
|
|
87
|
+
className: "gr-icon-btn",
|
|
89
88
|
disabled: pending || index === items.length - 1,
|
|
90
89
|
onClick: () => move(id, 1),
|
|
91
90
|
"aria-label": "Move down",
|
|
@@ -96,7 +95,7 @@ function RepeaterClient({
|
|
|
96
95
|
/* @__PURE__ */ jsx(
|
|
97
96
|
"button",
|
|
98
97
|
{
|
|
99
|
-
|
|
98
|
+
className: "gr-icon-btn gr-text-danger",
|
|
100
99
|
disabled: pending,
|
|
101
100
|
onClick: () => {
|
|
102
101
|
if (confirm("Remove this item and its content?")) {
|
|
@@ -111,12 +110,12 @@ function RepeaterClient({
|
|
|
111
110
|
] }),
|
|
112
111
|
node
|
|
113
112
|
] }, id)),
|
|
114
|
-
error && /* @__PURE__ */ jsx("div", {
|
|
115
|
-
/* @__PURE__ */ jsx("div", {
|
|
113
|
+
error && /* @__PURE__ */ jsx("div", { className: "gr-error gr-mt-8", children: error }),
|
|
114
|
+
/* @__PURE__ */ jsx("div", { className: "gr-repeater-add-row", children: adding && routed ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
116
115
|
/* @__PURE__ */ jsx(
|
|
117
116
|
"input",
|
|
118
117
|
{
|
|
119
|
-
|
|
118
|
+
className: "gr-repeater-slug-input",
|
|
120
119
|
placeholder: "url-slug",
|
|
121
120
|
value: slug,
|
|
122
121
|
autoFocus: true,
|
|
@@ -127,11 +126,11 @@ function RepeaterClient({
|
|
|
127
126
|
}
|
|
128
127
|
}
|
|
129
128
|
),
|
|
130
|
-
/* @__PURE__ */ jsx("button", {
|
|
129
|
+
/* @__PURE__ */ jsx("button", { className: "gr-primary-btn", disabled: pending, onClick: submitAdd, children: pending ? /* @__PURE__ */ jsx(SpinnerIcon, {}) : "Add" }),
|
|
131
130
|
/* @__PURE__ */ jsx(
|
|
132
131
|
"button",
|
|
133
132
|
{
|
|
134
|
-
|
|
133
|
+
className: "gr-cancel-btn",
|
|
135
134
|
disabled: pending,
|
|
136
135
|
onClick: () => {
|
|
137
136
|
setAdding(false);
|
|
@@ -144,7 +143,7 @@ function RepeaterClient({
|
|
|
144
143
|
"button",
|
|
145
144
|
{
|
|
146
145
|
type: "button",
|
|
147
|
-
|
|
146
|
+
className: "gr-repeater-add-btn",
|
|
148
147
|
disabled: pending,
|
|
149
148
|
onClick: () => routed ? setAdding(true) : submitAdd(),
|
|
150
149
|
children: [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/react/Repeater/client.tsx"],"sourcesContent":["\"use client\";\n\nimport React, { useState, useTransition } from \"react\";\nimport { useRouter } from \"next/navigation\";\nimport { addRepeaterItem, removeRepeaterItem, reorderRepeaterItems } from \"../../server/repeaterActions\";\nimport {\n ArrowDownIcon,\n ArrowUpIcon,\n OpenIcon,\n PlusIcon,\n SpinnerIcon,\n TrashIcon,\n} from \"../Icons\";\
|
|
1
|
+
{"version":3,"sources":["../../../src/react/Repeater/client.tsx"],"sourcesContent":["\"use client\";\n\nimport React, { useState, useTransition } from \"react\";\nimport { useRouter } from \"next/navigation\";\nimport { addRepeaterItem, removeRepeaterItem, reorderRepeaterItems } from \"../../server/repeaterActions\";\nimport {\n ArrowDownIcon,\n ArrowUpIcon,\n OpenIcon,\n PlusIcon,\n SpinnerIcon,\n TrashIcon,\n} from \"../Icons\";\n\ninterface RenderedItem {\n id: string;\n /** The item's own page, when the repeater was told where its route lives. */\n href?: string;\n /** Already rendered server-side — this component only wraps it in chrome. */\n node: React.ReactNode;\n}\n\nexport function RepeaterClient({\n repeaterId,\n routed,\n items,\n children,\n}: {\n repeaterId: string;\n routed?: boolean;\n items: RenderedItem[];\n children?: React.ReactNode;\n}) {\n const router = useRouter();\n const [error, setError] = useState<string | null>(null);\n const [adding, setAdding] = useState(false);\n const [slug, setSlug] = useState(\"\");\n const [pending, startTransition] = useTransition();\n\n /**\n * There is deliberately no local copy of `items`. Each item's node can\n * contain <GRail> fields, which only exist as a resolved server render —\n * they cannot be reproduced by holding data in client state and re-invoking\n * a component function in the browser. router.refresh() re-runs <GRepeater>\n * on the server and delivers freshly rendered nodes as new props, the same\n * pattern used for the login flow in Admin.tsx.\n */\n const run = (fn: () => Promise<{ success: boolean; error?: string }>) =>\n startTransition(async () => {\n setError(null);\n const res = await fn();\n if (!res.success) setError(res.error ?? \"Something went wrong.\");\n router.refresh();\n });\n\n const move = (id: string, dir: -1 | 1) => {\n const ids = items.map((i) => i.id);\n const index = ids.indexOf(id);\n const target = index + dir;\n if (target < 0 || target >= ids.length) return;\n const a = ids[index]!;\n const b = ids[target]!;\n ids[index] = b;\n ids[target] = a;\n run(() => reorderRepeaterItems(repeaterId, ids));\n };\n\n const submitAdd = () => {\n if (routed && !slug.trim()) return;\n run(async () => {\n const res = await addRepeaterItem(\n repeaterId,\n routed ? { slug: slug.trim() } : undefined,\n );\n if (res.success) {\n setAdding(false);\n setSlug(\"\");\n }\n return res;\n });\n };\n\n return (\n <>\n {items.length === 0 && children}\n\n {items.map(({ id, href, node }, index) => (\n <div key={id} className=\"gr-repeater-item\">\n <div className=\"gr-repeater-toolbar\">\n {href && (\n // Clicking the item's fields edits them rather than following\n // any link around them, so this is the way to its own page. A\n // same-tab router push: the session keeps edit mode on.\n <button\n className=\"gr-icon-btn\"\n onClick={() => router.push(href)}\n aria-label=\"Open page\"\n title=\"Open this item's page\"\n >\n <OpenIcon />\n </button>\n )}\n <button\n className=\"gr-icon-btn\"\n disabled={pending || index === 0}\n onClick={() => move(id, -1)}\n aria-label=\"Move up\"\n title=\"Move up\"\n >\n <ArrowUpIcon />\n </button>\n <button\n className=\"gr-icon-btn\"\n disabled={pending || index === items.length - 1}\n onClick={() => move(id, 1)}\n aria-label=\"Move down\"\n title=\"Move down\"\n >\n <ArrowDownIcon />\n </button>\n <button\n className=\"gr-icon-btn gr-text-danger\"\n disabled={pending}\n onClick={() => {\n if (confirm(\"Remove this item and its content?\")) {\n run(() => removeRepeaterItem(repeaterId, id));\n }\n }}\n aria-label=\"Remove\"\n title=\"Remove\"\n >\n <TrashIcon />\n </button>\n </div>\n {node}\n </div>\n ))}\n\n {error && <div className=\"gr-error gr-mt-8\">{error}</div>}\n\n <div className=\"gr-repeater-add-row\">\n {adding && routed ? (\n <>\n <input\n className=\"gr-repeater-slug-input\"\n placeholder=\"url-slug\"\n value={slug}\n autoFocus\n onChange={(e) => setSlug(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\") submitAdd();\n if (e.key === \"Escape\") setAdding(false);\n }}\n />\n <button className=\"gr-primary-btn\" disabled={pending} onClick={submitAdd}>\n {pending ? <SpinnerIcon /> : \"Add\"}\n </button>\n <button\n className=\"gr-cancel-btn\"\n disabled={pending}\n onClick={() => {\n setAdding(false);\n setSlug(\"\");\n }}\n >\n Cancel\n </button>\n </>\n ) : (\n <button\n type=\"button\"\n className=\"gr-repeater-add-btn\"\n disabled={pending}\n onClick={() => (routed ? setAdding(true) : submitAdd())}\n >\n <PlusIcon /> Add {routed ? \"item\" : \"card\"}\n </button>\n )}\n </div>\n </>\n );\n}\n"],"mappings":"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","names":[]}
|
package/dist/react/Team.d.ts
CHANGED
|
@@ -1,13 +1,7 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
* Keyframes cannot be expressed as inline styles, so the drawer ships the few
|
|
5
|
-
* it needs. Scoped to [data-guardrail-team] to stay clear of app styles, and
|
|
6
|
-
* disabled under prefers-reduced-motion.
|
|
7
|
-
*/
|
|
8
|
-
declare const ANIMATION_CSS = "\n@keyframes gr-fade-in { from { opacity: 0 } to { opacity: 1 } }\n@keyframes gr-slide-in {\n from { transform: translateX(100%) }\n to { transform: translateX(0) }\n}\n[data-guardrail-scrim] { animation: gr-fade-in 160ms ease-out both }\n[data-guardrail-team] { animation: gr-slide-in 220ms cubic-bezier(0.32, 0.72, 0, 1) both }\n[data-guardrail-team] [data-gr-row] { animation: gr-fade-in 240ms ease-out both }\n@keyframes gr-fade-out { from { opacity: 1 } to { opacity: 0 } }\n@keyframes gr-slide-out {\n from { transform: translateX(0) }\n to { transform: translateX(100%) }\n}\n[data-guardrail-scrim][data-closing] { animation: gr-fade-out 180ms ease-in both }\n[data-guardrail-team][data-closing] { animation: gr-slide-out 180ms cubic-bezier(0.4, 0, 1, 1) both }\n@media (prefers-reduced-motion: reduce) {\n [data-guardrail-scrim], [data-guardrail-team], [data-guardrail-team] [data-gr-row] {\n animation: none\n }\n}\n";
|
|
9
3
|
declare function TeamDrawer({ onClose }: {
|
|
10
4
|
onClose: () => void;
|
|
11
5
|
}): react_jsx_runtime.JSX.Element;
|
|
12
6
|
|
|
13
|
-
export {
|
|
7
|
+
export { TeamDrawer };
|
package/dist/react/Team.js
CHANGED
|
@@ -10,30 +10,7 @@ import {
|
|
|
10
10
|
} from "../server/actions";
|
|
11
11
|
import { useGRail } from "./Provider";
|
|
12
12
|
import { CloseIcon, KeyIcon } from "./Icons";
|
|
13
|
-
import { S, v } from "./styles";
|
|
14
13
|
const ROLES = ["admin", "editor"];
|
|
15
|
-
const ANIMATION_CSS = `
|
|
16
|
-
@keyframes gr-fade-in { from { opacity: 0 } to { opacity: 1 } }
|
|
17
|
-
@keyframes gr-slide-in {
|
|
18
|
-
from { transform: translateX(100%) }
|
|
19
|
-
to { transform: translateX(0) }
|
|
20
|
-
}
|
|
21
|
-
[data-guardrail-scrim] { animation: gr-fade-in 160ms ease-out both }
|
|
22
|
-
[data-guardrail-team] { animation: gr-slide-in 220ms cubic-bezier(0.32, 0.72, 0, 1) both }
|
|
23
|
-
[data-guardrail-team] [data-gr-row] { animation: gr-fade-in 240ms ease-out both }
|
|
24
|
-
@keyframes gr-fade-out { from { opacity: 1 } to { opacity: 0 } }
|
|
25
|
-
@keyframes gr-slide-out {
|
|
26
|
-
from { transform: translateX(0) }
|
|
27
|
-
to { transform: translateX(100%) }
|
|
28
|
-
}
|
|
29
|
-
[data-guardrail-scrim][data-closing] { animation: gr-fade-out 180ms ease-in both }
|
|
30
|
-
[data-guardrail-team][data-closing] { animation: gr-slide-out 180ms cubic-bezier(0.4, 0, 1, 1) both }
|
|
31
|
-
@media (prefers-reduced-motion: reduce) {
|
|
32
|
-
[data-guardrail-scrim], [data-guardrail-team], [data-guardrail-team] [data-gr-row] {
|
|
33
|
-
animation: none
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
`;
|
|
37
14
|
function TeamDrawer({ onClose }) {
|
|
38
15
|
const { role, email: currentEmail } = useGRail();
|
|
39
16
|
const isAdmin = role === "admin";
|
|
@@ -76,14 +53,13 @@ function TeamDrawer({ onClose }) {
|
|
|
76
53
|
return () => window.removeEventListener("keydown", onKey);
|
|
77
54
|
}, [requestClose]);
|
|
78
55
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
79
|
-
/* @__PURE__ */ jsx("style", { href: "guardrail-team-animation", precedence: "default", children: ANIMATION_CSS }),
|
|
80
56
|
/* @__PURE__ */ jsx(
|
|
81
57
|
"div",
|
|
82
58
|
{
|
|
83
59
|
"data-guardrail": true,
|
|
84
60
|
"data-guardrail-scrim": true,
|
|
85
61
|
...closing ? { "data-closing": "" } : {},
|
|
86
|
-
|
|
62
|
+
className: "gr-scrim",
|
|
87
63
|
onClick: requestClose
|
|
88
64
|
}
|
|
89
65
|
),
|
|
@@ -96,27 +72,27 @@ function TeamDrawer({ onClose }) {
|
|
|
96
72
|
onAnimationEnd: (e) => {
|
|
97
73
|
if (closing && e.target === e.currentTarget) onClose();
|
|
98
74
|
},
|
|
99
|
-
|
|
75
|
+
className: "gr-drawer",
|
|
100
76
|
role: "dialog",
|
|
101
77
|
"aria-label": "Team",
|
|
102
78
|
children: [
|
|
103
|
-
/* @__PURE__ */ jsxs("header", {
|
|
104
|
-
/* @__PURE__ */ jsx("h2", {
|
|
105
|
-
/* @__PURE__ */ jsx("button", {
|
|
79
|
+
/* @__PURE__ */ jsxs("header", { className: "gr-drawer-head", children: [
|
|
80
|
+
/* @__PURE__ */ jsx("h2", { className: "gr-drawer-title", children: "Team" }),
|
|
81
|
+
/* @__PURE__ */ jsx("button", { className: "gr-icon-btn gr-text-muted", onClick: requestClose, "aria-label": "Close", children: /* @__PURE__ */ jsx(CloseIcon, {}) })
|
|
106
82
|
] }),
|
|
107
|
-
/* @__PURE__ */ jsxs("div", {
|
|
108
|
-
error && /* @__PURE__ */ jsx("div", {
|
|
109
|
-
notice && /* @__PURE__ */ jsx("div", {
|
|
110
|
-
loading ? /* @__PURE__ */ jsx("p", {
|
|
111
|
-
/* @__PURE__ */ jsxs("span", {
|
|
83
|
+
/* @__PURE__ */ jsxs("div", { className: "gr-drawer-body", children: [
|
|
84
|
+
error && /* @__PURE__ */ jsx("div", { className: "gr-error gr-mb-12", children: error }),
|
|
85
|
+
notice && /* @__PURE__ */ jsx("div", { className: "gr-fs-12 gr-text-muted gr-mb-12", role: "status", children: notice }),
|
|
86
|
+
loading ? /* @__PURE__ */ jsx("p", { className: "gr-fs-13 gr-text-muted", children: "Loading\u2026" }) : members.map((m) => /* @__PURE__ */ jsxs("div", { "data-gr-row": true, className: "gr-member-row", children: [
|
|
87
|
+
/* @__PURE__ */ jsxs("span", { className: "gr-member-email", title: m.email, children: [
|
|
112
88
|
m.email,
|
|
113
89
|
m.email === currentEmail && " (you)"
|
|
114
90
|
] }),
|
|
115
|
-
m.status === "pending" && /* @__PURE__ */ jsx("span", {
|
|
91
|
+
m.status === "pending" && /* @__PURE__ */ jsx("span", { className: "gr-badge", children: "Invited" }),
|
|
116
92
|
isAdmin ? /* @__PURE__ */ jsx(
|
|
117
93
|
"select",
|
|
118
94
|
{
|
|
119
|
-
|
|
95
|
+
className: "gr-select",
|
|
120
96
|
value: m.role,
|
|
121
97
|
disabled: pending,
|
|
122
98
|
onChange: (e) => run(
|
|
@@ -124,11 +100,11 @@ function TeamDrawer({ onClose }) {
|
|
|
124
100
|
),
|
|
125
101
|
children: ROLES.map((r) => /* @__PURE__ */ jsx("option", { value: r, children: r }, r))
|
|
126
102
|
}
|
|
127
|
-
) : /* @__PURE__ */ jsx("span", {
|
|
103
|
+
) : /* @__PURE__ */ jsx("span", { className: "gr-fs-12 gr-text-muted", children: m.role }),
|
|
128
104
|
isAdmin && /* @__PURE__ */ jsx(
|
|
129
105
|
"button",
|
|
130
106
|
{
|
|
131
|
-
|
|
107
|
+
className: "gr-icon-btn gr-text-muted gr-size-22",
|
|
132
108
|
disabled: pending,
|
|
133
109
|
onClick: () => run(() => sendPasswordResetAction(m.email)),
|
|
134
110
|
"aria-label": `Send password reset to ${m.email}`,
|
|
@@ -139,7 +115,7 @@ function TeamDrawer({ onClose }) {
|
|
|
139
115
|
isAdmin && /* @__PURE__ */ jsx(
|
|
140
116
|
"button",
|
|
141
117
|
{
|
|
142
|
-
|
|
118
|
+
className: "gr-icon-btn gr-text-muted gr-size-22",
|
|
143
119
|
disabled: pending,
|
|
144
120
|
onClick: () => run(() => removeMember(m.email)),
|
|
145
121
|
"aria-label": `Remove ${m.email}`,
|
|
@@ -149,7 +125,7 @@ function TeamDrawer({ onClose }) {
|
|
|
149
125
|
)
|
|
150
126
|
] }, m.email)),
|
|
151
127
|
isAdmin && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
152
|
-
/* @__PURE__ */ jsx("p", {
|
|
128
|
+
/* @__PURE__ */ jsx("p", { className: "gr-section-label", children: "Invite someone" }),
|
|
153
129
|
/* @__PURE__ */ jsxs(
|
|
154
130
|
"form",
|
|
155
131
|
{
|
|
@@ -165,7 +141,7 @@ function TeamDrawer({ onClose }) {
|
|
|
165
141
|
/* @__PURE__ */ jsx(
|
|
166
142
|
"input",
|
|
167
143
|
{
|
|
168
|
-
|
|
144
|
+
className: "gr-field",
|
|
169
145
|
type: "email",
|
|
170
146
|
placeholder: "name@company.com",
|
|
171
147
|
value: inviteEmail,
|
|
@@ -173,22 +149,22 @@ function TeamDrawer({ onClose }) {
|
|
|
173
149
|
required: true
|
|
174
150
|
}
|
|
175
151
|
),
|
|
176
|
-
/* @__PURE__ */ jsxs("div", {
|
|
152
|
+
/* @__PURE__ */ jsxs("div", { className: "gr-flex gr-gap-8", children: [
|
|
177
153
|
/* @__PURE__ */ jsx(
|
|
178
154
|
"select",
|
|
179
155
|
{
|
|
180
|
-
|
|
156
|
+
className: "gr-select gr-flex-1",
|
|
181
157
|
value: inviteRole,
|
|
182
158
|
onChange: (e) => setInviteRole(e.target.value),
|
|
183
159
|
children: ROLES.map((r) => /* @__PURE__ */ jsx("option", { value: r, children: r }, r))
|
|
184
160
|
}
|
|
185
161
|
),
|
|
186
|
-
/* @__PURE__ */ jsx("button", {
|
|
162
|
+
/* @__PURE__ */ jsx("button", { className: "gr-primary-btn", type: "submit", disabled: pending, children: pending ? "Working\u2026" : "Invite" })
|
|
187
163
|
] })
|
|
188
164
|
]
|
|
189
165
|
}
|
|
190
166
|
),
|
|
191
|
-
/* @__PURE__ */ jsxs("p", {
|
|
167
|
+
/* @__PURE__ */ jsxs("p", { className: "gr-fs-11 gr-text-subtle gr-mt-10", children: [
|
|
192
168
|
"New people get an email to set a password. Invites and resets go out through your ",
|
|
193
169
|
/* @__PURE__ */ jsx("code", { children: "onInvite" }),
|
|
194
170
|
" / ",
|
|
@@ -204,7 +180,6 @@ function TeamDrawer({ onClose }) {
|
|
|
204
180
|
] });
|
|
205
181
|
}
|
|
206
182
|
export {
|
|
207
|
-
ANIMATION_CSS,
|
|
208
183
|
TeamDrawer
|
|
209
184
|
};
|
|
210
185
|
//# sourceMappingURL=Team.js.map
|
package/dist/react/Team.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/react/Team.tsx"],"sourcesContent":["\"use client\";\n\nimport React, { useCallback, useEffect, useState, useTransition } from \"react\";\nimport {\n inviteMember,\n listTeam,\n removeMember,\n sendPasswordResetAction,\n updateMemberRole,\n} from \"../server/actions\";\nimport { useGRail } from \"./Provider\";\nimport { CloseIcon, KeyIcon } from \"./Icons\";\nimport { S, v } from \"./styles\";\nimport type { GRMember, GRRole } from \"../core/types\";\n\nconst ROLES: GRRole[] = [\"admin\", \"editor\"];\n\n/**\n * Keyframes cannot be expressed as inline styles, so the drawer ships the few\n * it needs. Scoped to [data-guardrail-team] to stay clear of app styles, and\n * disabled under prefers-reduced-motion.\n */\nexport const ANIMATION_CSS = `\n@keyframes gr-fade-in { from { opacity: 0 } to { opacity: 1 } }\n@keyframes gr-slide-in {\n from { transform: translateX(100%) }\n to { transform: translateX(0) }\n}\n[data-guardrail-scrim] { animation: gr-fade-in 160ms ease-out both }\n[data-guardrail-team] { animation: gr-slide-in 220ms cubic-bezier(0.32, 0.72, 0, 1) both }\n[data-guardrail-team] [data-gr-row] { animation: gr-fade-in 240ms ease-out both }\n@keyframes gr-fade-out { from { opacity: 1 } to { opacity: 0 } }\n@keyframes gr-slide-out {\n from { transform: translateX(0) }\n to { transform: translateX(100%) }\n}\n[data-guardrail-scrim][data-closing] { animation: gr-fade-out 180ms ease-in both }\n[data-guardrail-team][data-closing] { animation: gr-slide-out 180ms cubic-bezier(0.4, 0, 1, 1) both }\n@media (prefers-reduced-motion: reduce) {\n [data-guardrail-scrim], [data-guardrail-team], [data-guardrail-team] [data-gr-row] {\n animation: none\n }\n}\n`;\n\nexport function TeamDrawer({ onClose }: { onClose: () => void }) {\n const { role, email: currentEmail } = useGRail();\n const isAdmin = role === \"admin\";\n\n const [members, setMembers] = useState<GRMember[]>([]);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n const [notice, setNotice] = useState<string | null>(null);\n const [inviteEmail, setInviteEmail] = useState(\"\");\n const [inviteRole, setInviteRole] = useState<GRRole>(\"editor\");\n const [pending, startTransition] = useTransition();\n\n const refresh = () =>\n listTeam().then((rows) => {\n setMembers(rows);\n setLoading(false);\n });\n\n useEffect(() => {\n void refresh();\n }, []);\n\n // Server actions re-check the role; this only decides what to render.\n // Always refresh: a failure can still have changed the roster (an invite\n // that saved but whose email failed, for instance).\n const run = (fn: () => Promise<{ success: boolean; error?: string; notice?: string }>) =>\n startTransition(async () => {\n setError(null);\n setNotice(null);\n const res = await fn();\n if (!res.success) setError(res.error ?? \"Something went wrong.\");\n else if (res.notice) setNotice(res.notice);\n await refresh();\n });\n\n /**\n * Unmounting on close skips the exit animation entirely. Stay mounted, play\n * it, and let the parent unmount once it finishes.\n */\n const [closing, setClosing] = useState(false);\n const requestClose = useCallback(() => {\n const reduced =\n typeof window !== \"undefined\" &&\n window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches;\n if (reduced) return onClose(); // No animation, so no animationend to wait for.\n setClosing(true);\n }, [onClose]);\n\n useEffect(() => {\n if (!closing) return;\n // Safety net if animationend never arrives (tab hidden, styles stripped).\n const t = window.setTimeout(onClose, 400);\n return () => window.clearTimeout(t);\n }, [closing, onClose]);\n\n useEffect(() => {\n const onKey = (e: KeyboardEvent) => e.key === \"Escape\" && requestClose();\n window.addEventListener(\"keydown\", onKey);\n return () => window.removeEventListener(\"keydown\", onKey);\n }, [requestClose]);\n\n return (\n <>\n {/* Hoisted to <head> — see the note on the theme <style> in Admin.tsx. */}\n <style href=\"guardrail-team-animation\" precedence=\"default\">\n {ANIMATION_CSS}\n </style>\n <div\n data-guardrail\n data-guardrail-scrim\n {...(closing ? { \"data-closing\": \"\" } : {})}\n style={S.scrim}\n onClick={requestClose}\n />\n <aside\n data-guardrail\n data-guardrail-team\n {...(closing ? { \"data-closing\": \"\" } : {})}\n onAnimationEnd={(e) => {\n // Row fade-ins bubble up here too; only the drawer's own exit counts.\n if (closing && e.target === e.currentTarget) onClose();\n }}\n style={S.drawer}\n role=\"dialog\"\n aria-label=\"Team\"\n >\n <header style={S.drawerHead}>\n <h2 style={S.drawerTitle}>Team</h2>\n <button style={{ ...S.iconBtn, color: v(\"muted\") }} onClick={requestClose} aria-label=\"Close\">\n <CloseIcon />\n </button>\n </header>\n\n <div style={S.drawerBody}>\n {error && <div style={{ ...S.error, marginBottom: 12 }}>{error}</div>}\n {notice && (\n <div style={{ fontSize: 12, color: v(\"muted\"), marginBottom: 12 }} role=\"status\">\n {notice}\n </div>\n )}\n\n {loading ? (\n <p style={{ fontSize: 13, color: v(\"muted\") }}>Loading…</p>\n ) : (\n members.map((m) => (\n <div key={m.email} data-gr-row style={S.memberRow}>\n <span style={S.memberEmail} title={m.email}>\n {m.email}\n {m.email === currentEmail && \" (you)\"}\n </span>\n\n {m.status === \"pending\" && <span style={S.badge}>Invited</span>}\n\n {isAdmin ? (\n <select\n style={S.select}\n value={m.role}\n disabled={pending}\n onChange={(e) =>\n run(() =>\n updateMemberRole(m.email, e.target.value as GRRole),\n )\n }\n >\n {ROLES.map((r) => (\n <option key={r} value={r}>\n {r}\n </option>\n ))}\n </select>\n ) : (\n <span style={{ fontSize: 12, color: v(\"muted\") }}>{m.role}</span>\n )}\n\n {isAdmin && (\n <button\n style={{ ...S.iconBtn, color: v(\"muted\"), width: 22, height: 22 }}\n disabled={pending}\n onClick={() => run(() => sendPasswordResetAction(m.email))}\n aria-label={`Send password reset to ${m.email}`}\n title=\"Send password reset\"\n >\n <KeyIcon />\n </button>\n )}\n\n {isAdmin && (\n <button\n style={{ ...S.iconBtn, color: v(\"muted\"), width: 22, height: 22 }}\n disabled={pending}\n onClick={() => run(() => removeMember(m.email))}\n aria-label={`Remove ${m.email}`}\n title=\"Remove\"\n >\n <CloseIcon />\n </button>\n )}\n </div>\n ))\n )}\n\n {isAdmin && (\n <>\n <p style={S.sectionLabel}>Invite someone</p>\n <form\n onSubmit={(e) => {\n e.preventDefault();\n run(async () => {\n const res = await inviteMember(inviteEmail, inviteRole);\n if (res.success) setInviteEmail(\"\");\n return res;\n });\n }}\n >\n <input\n style={S.field}\n type=\"email\"\n placeholder=\"name@company.com\"\n value={inviteEmail}\n onChange={(e) => setInviteEmail(e.target.value)}\n required\n />\n <div style={{ display: \"flex\", gap: 8 }}>\n <select\n style={{ ...S.select, flex: 1 }}\n value={inviteRole}\n onChange={(e) => setInviteRole(e.target.value as GRRole)}\n >\n {ROLES.map((r) => (\n <option key={r} value={r}>\n {r}\n </option>\n ))}\n </select>\n <button style={S.primaryBtn} type=\"submit\" disabled={pending}>\n {pending ? \"Working…\" : \"Invite\"}\n </button>\n </div>\n </form>\n <p style={{ fontSize: 11, color: v(\"subtle\"), marginTop: 10 }}>\n New people get an email to set a password. Invites and resets go\n out through your <code>onInvite</code> / <code>onPasswordReset</code>{\" \"}\n handlers, or your auth provider's own emails if you haven't\n set them.\n </p>\n </>\n )}\n </div>\n </aside>\n </>\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/react/Team.tsx"],"sourcesContent":["\"use client\";\n\nimport React, { useCallback, useEffect, useState, useTransition } from \"react\";\nimport {\n inviteMember,\n listTeam,\n removeMember,\n sendPasswordResetAction,\n updateMemberRole,\n} from \"../server/actions\";\nimport { useGRail } from \"./Provider\";\nimport { CloseIcon, KeyIcon } from \"./Icons\";\nimport type { GRMember, GRRole } from \"../core/types\";\n\nconst ROLES: GRRole[] = [\"admin\", \"editor\"];\n\nexport function TeamDrawer({ onClose }: { onClose: () => void }) {\n const { role, email: currentEmail } = useGRail();\n const isAdmin = role === \"admin\";\n\n const [members, setMembers] = useState<GRMember[]>([]);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n const [notice, setNotice] = useState<string | null>(null);\n const [inviteEmail, setInviteEmail] = useState(\"\");\n const [inviteRole, setInviteRole] = useState<GRRole>(\"editor\");\n const [pending, startTransition] = useTransition();\n\n const refresh = () =>\n listTeam().then((rows) => {\n setMembers(rows);\n setLoading(false);\n });\n\n useEffect(() => {\n void refresh();\n }, []);\n\n // Server actions re-check the role; this only decides what to render.\n // Always refresh: a failure can still have changed the roster (an invite\n // that saved but whose email failed, for instance).\n const run = (fn: () => Promise<{ success: boolean; error?: string; notice?: string }>) =>\n startTransition(async () => {\n setError(null);\n setNotice(null);\n const res = await fn();\n if (!res.success) setError(res.error ?? \"Something went wrong.\");\n else if (res.notice) setNotice(res.notice);\n await refresh();\n });\n\n /**\n * Unmounting on close skips the exit animation entirely. Stay mounted, play\n * it, and let the parent unmount once it finishes.\n */\n const [closing, setClosing] = useState(false);\n const requestClose = useCallback(() => {\n const reduced =\n typeof window !== \"undefined\" &&\n window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches;\n if (reduced) return onClose(); // No animation, so no animationend to wait for.\n setClosing(true);\n }, [onClose]);\n\n useEffect(() => {\n if (!closing) return;\n // Safety net if animationend never arrives (tab hidden, styles stripped).\n const t = window.setTimeout(onClose, 400);\n return () => window.clearTimeout(t);\n }, [closing, onClose]);\n\n useEffect(() => {\n const onKey = (e: KeyboardEvent) => e.key === \"Escape\" && requestClose();\n window.addEventListener(\"keydown\", onKey);\n return () => window.removeEventListener(\"keydown\", onKey);\n }, [requestClose]);\n\n return (\n <>\n <div\n data-guardrail\n data-guardrail-scrim\n {...(closing ? { \"data-closing\": \"\" } : {})}\n className=\"gr-scrim\"\n onClick={requestClose}\n />\n <aside\n data-guardrail\n data-guardrail-team\n {...(closing ? { \"data-closing\": \"\" } : {})}\n onAnimationEnd={(e) => {\n // Row fade-ins bubble up here too; only the drawer's own exit counts.\n if (closing && e.target === e.currentTarget) onClose();\n }}\n className=\"gr-drawer\"\n role=\"dialog\"\n aria-label=\"Team\"\n >\n <header className=\"gr-drawer-head\">\n <h2 className=\"gr-drawer-title\">Team</h2>\n <button className=\"gr-icon-btn gr-text-muted\" onClick={requestClose} aria-label=\"Close\">\n <CloseIcon />\n </button>\n </header>\n\n <div className=\"gr-drawer-body\">\n {error && <div className=\"gr-error gr-mb-12\">{error}</div>}\n {notice && (\n <div className=\"gr-fs-12 gr-text-muted gr-mb-12\" role=\"status\">\n {notice}\n </div>\n )}\n\n {loading ? (\n <p className=\"gr-fs-13 gr-text-muted\">Loading…</p>\n ) : (\n members.map((m) => (\n <div key={m.email} data-gr-row className=\"gr-member-row\">\n <span className=\"gr-member-email\" title={m.email}>\n {m.email}\n {m.email === currentEmail && \" (you)\"}\n </span>\n\n {m.status === \"pending\" && <span className=\"gr-badge\">Invited</span>}\n\n {isAdmin ? (\n <select\n className=\"gr-select\"\n value={m.role}\n disabled={pending}\n onChange={(e) =>\n run(() =>\n updateMemberRole(m.email, e.target.value as GRRole),\n )\n }\n >\n {ROLES.map((r) => (\n <option key={r} value={r}>\n {r}\n </option>\n ))}\n </select>\n ) : (\n <span className=\"gr-fs-12 gr-text-muted\">{m.role}</span>\n )}\n\n {isAdmin && (\n <button\n className=\"gr-icon-btn gr-text-muted gr-size-22\"\n disabled={pending}\n onClick={() => run(() => sendPasswordResetAction(m.email))}\n aria-label={`Send password reset to ${m.email}`}\n title=\"Send password reset\"\n >\n <KeyIcon />\n </button>\n )}\n\n {isAdmin && (\n <button\n className=\"gr-icon-btn gr-text-muted gr-size-22\"\n disabled={pending}\n onClick={() => run(() => removeMember(m.email))}\n aria-label={`Remove ${m.email}`}\n title=\"Remove\"\n >\n <CloseIcon />\n </button>\n )}\n </div>\n ))\n )}\n\n {isAdmin && (\n <>\n <p className=\"gr-section-label\">Invite someone</p>\n <form\n onSubmit={(e) => {\n e.preventDefault();\n run(async () => {\n const res = await inviteMember(inviteEmail, inviteRole);\n if (res.success) setInviteEmail(\"\");\n return res;\n });\n }}\n >\n <input\n className=\"gr-field\"\n type=\"email\"\n placeholder=\"name@company.com\"\n value={inviteEmail}\n onChange={(e) => setInviteEmail(e.target.value)}\n required\n />\n <div className=\"gr-flex gr-gap-8\">\n <select\n className=\"gr-select gr-flex-1\"\n value={inviteRole}\n onChange={(e) => setInviteRole(e.target.value as GRRole)}\n >\n {ROLES.map((r) => (\n <option key={r} value={r}>\n {r}\n </option>\n ))}\n </select>\n <button className=\"gr-primary-btn\" type=\"submit\" disabled={pending}>\n {pending ? \"Working…\" : \"Invite\"}\n </button>\n </div>\n </form>\n <p className=\"gr-fs-11 gr-text-subtle gr-mt-10\">\n New people get an email to set a password. Invites and resets go\n out through your <code>onInvite</code> / <code>onPasswordReset</code>{\" \"}\n handlers, or your auth provider's own emails if you haven't\n set them.\n </p>\n </>\n )}\n </div>\n </aside>\n </>\n );\n}\n"],"mappings":"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","names":[]}
|