@guard-rail/core 0.2.1 → 0.5.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 +132 -31
- package/dist/adapters/supabase/auth.js +50 -0
- package/dist/adapters/supabase/auth.js.map +1 -1
- package/dist/adapters/supabase/db.js +11 -0
- package/dist/adapters/supabase/db.js.map +1 -1
- package/dist/cli/init-db.d.ts +1 -0
- package/dist/cli/init-db.js +121 -0
- package/dist/cli/init-db.js.map +1 -0
- package/dist/core/adapter-types.d.ts +77 -1
- package/dist/core/config.d.ts +7 -1
- package/dist/core/config.js +11 -0
- package/dist/core/config.js.map +1 -1
- package/dist/core/defs.d.ts +43 -2
- package/dist/core/defs.js +29 -2
- package/dist/core/defs.js.map +1 -1
- package/dist/core/scaffold.d.ts +11 -0
- package/dist/core/scaffold.js +19 -0
- package/dist/core/scaffold.js.map +1 -0
- package/dist/core/types.d.ts +21 -1
- package/dist/index.client.d.ts +18 -0
- package/dist/index.client.js +70 -0
- package/dist/index.client.js.map +1 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/react/Admin.d.ts +2 -2
- package/dist/react/Admin.js +84 -8
- package/dist/react/Admin.js.map +1 -1
- package/dist/react/AuthRedirectSync.d.ts +20 -0
- package/dist/react/AuthRedirectSync.js +38 -0
- package/dist/react/AuthRedirectSync.js.map +1 -0
- package/dist/react/GRail/client.js +5 -1
- package/dist/react/GRail/client.js.map +1 -1
- package/dist/react/GRail/editor.d.ts +24 -0
- package/dist/react/GRail/editor.js +9 -0
- package/dist/react/GRail/editor.js.map +1 -0
- package/dist/react/GRail/index.js +2 -2
- package/dist/react/GRail/index.js.map +1 -1
- package/dist/react/GRail/render.d.ts +3 -3
- package/dist/react/GRail/universal.d.ts +22 -0
- package/dist/react/GRail/universal.js +29 -0
- package/dist/react/GRail/universal.js.map +1 -0
- package/dist/react/Provider.d.ts +9 -5
- package/dist/react/Provider.js +15 -5
- package/dist/react/Provider.js.map +1 -1
- package/dist/react/Repeater/client.d.ts +3 -3
- package/dist/react/Repeater/index.d.ts +3 -3
- package/dist/react/Syncer.d.ts +10 -8
- package/dist/react/Syncer.js +8 -6
- package/dist/react/Syncer.js.map +1 -1
- package/dist/react/Team.js +1 -1
- package/dist/react/Team.js.map +1 -1
- package/dist/react/useAnchorRect.d.ts +17 -1
- package/dist/react/useAnchorRect.js +37 -9
- package/dist/react/useAnchorRect.js.map +1 -1
- package/dist/server/actions.d.ts +31 -3
- package/dist/server/actions.js +45 -1
- package/dist/server/actions.js.map +1 -1
- package/dist/server/data.js +1 -15
- package/dist/server/data.js.map +1 -1
- package/dist/server/loader.d.ts +9 -2
- package/dist/server/loader.js +74 -11
- package/dist/server/loader.js.map +1 -1
- package/dist/server/wrapper.d.ts +2 -2
- package/dist/server/wrapper.js +13 -4
- package/dist/server/wrapper.js.map +1 -1
- package/package.json +12 -2
- package/scripts/postinstall.mjs +4 -4
package/dist/core/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/core/config.ts"],"sourcesContent":["import type {\n GRConfig,\n GRAuthAdapter,\n GRDBAdapter,\n GRMembershipAdapter,\n GRRepeaterAdapter,\n GRStorageAdapter,\n} from \"./adapter-types\";\n\nlet _config: GRConfig | null = null;\n\n/**\n * Registers the adapters Guardrail should use.\n *\n * Idempotent, and re-applied on every render by withGuardrail: Next.js does not\n * guarantee one shared module instance across the SSR graph, the server-action\n * graph, and the workers used for static generation, so config registered once\n * at startup is simply absent in some of them.\n *\n * Core has no knowledge of any particular backend — there is no default and no\n * fallback. An app names its adapters explicitly or Guardrail refuses to run.\n */\nexport function configureGuardrail(config: GRConfig): void {\n _config = config;\n}\n\nfunction required<K extends keyof GRConfig>(key: K): GRConfig[K] {\n const value = _config?.[key];\n if (!value) {\n throw new Error(\n `Guardrail: no ${String(key)} adapter configured. Pass one to ` +\n `withGuardrail(Layout, { db, auth, members }).`,\n );\n }\n return value;\n}\n\nexport function getDB(): GRDBAdapter {\n return required(\"db\") as GRDBAdapter;\n}\n\nexport function getAuth(): GRAuthAdapter {\n return required(\"auth\") as GRAuthAdapter;\n}\n\nexport function getMembers(): GRMembershipAdapter {\n return required(\"members\") as GRMembershipAdapter;\n}\n\n/** Optional — sites without editable images need no storage. */\nexport function getStorage(): GRStorageAdapter | undefined {\n return _config?.storage;\n}\n\n/** Throws with a clear message if no page on this site uses <GRepeater>. */\nexport function getRepeaters(): GRRepeaterAdapter {\n return required(\"repeaters\") as GRRepeaterAdapter;\n}\n\nexport function getInviteHandler(): GRConfig[\"onInvite\"] {\n return _config?.onInvite;\n}\n\nexport async function resolveSiteId(): Promise<string> {\n const sid = _config?.siteId ?? \"default\";\n return typeof sid === \"function\" ? await sid() : sid || \"default\";\n}\n"],"mappings":"AASA,IAAI,UAA2B;AAaxB,SAAS,mBAAmB,QAAwB;AACzD,YAAU;AACZ;AAEA,SAAS,SAAmC,KAAqB;AAC/D,QAAM,QAAQ,UAAU,GAAG;AAC3B,MAAI,CAAC,OAAO;AACV,UAAM,IAAI;AAAA,MACR,iBAAiB,OAAO,GAAG,CAAC;AAAA,IAE9B;AAAA,EACF;AACA,SAAO;AACT;AAEO,SAAS,QAAqB;AACnC,SAAO,SAAS,IAAI;AACtB;AAEO,SAAS,UAAyB;AACvC,SAAO,SAAS,MAAM;AACxB;AAEO,SAAS,aAAkC;AAChD,SAAO,SAAS,SAAS;AAC3B;AAGO,SAAS,aAA2C;AACzD,SAAO,SAAS;AAClB;AAGO,SAAS,eAAkC;AAChD,SAAO,SAAS,WAAW;AAC7B;AAEO,SAAS,mBAAyC;AACvD,SAAO,SAAS;AAClB;AAEA,eAAsB,gBAAiC;AACrD,QAAM,MAAM,SAAS,UAAU;AAC/B,SAAO,OAAO,QAAQ,aAAa,MAAM,IAAI,IAAI,OAAO;AAC1D;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/core/config.ts"],"sourcesContent":["import type {\n GRConfig,\n GRAuthAdapter,\n GRDBAdapter,\n GRMembershipAdapter,\n GRRepeaterAdapter,\n GRStorageAdapter,\n} from \"./adapter-types\";\n\nlet _config: GRConfig | null = null;\n\n/**\n * Registers the adapters Guardrail should use.\n *\n * Idempotent, and re-applied on every render by withGuardrail: Next.js does not\n * guarantee one shared module instance across the SSR graph, the server-action\n * graph, and the workers used for static generation, so config registered once\n * at startup is simply absent in some of them.\n *\n * Core has no knowledge of any particular backend — there is no default and no\n * fallback. An app names its adapters explicitly or Guardrail refuses to run.\n */\nexport function configureGuardrail(config: GRConfig): void {\n _config = config;\n}\n\nfunction required<K extends keyof GRConfig>(key: K): GRConfig[K] {\n const value = _config?.[key];\n if (!value) {\n throw new Error(\n `Guardrail: no ${String(key)} adapter configured. Pass one to ` +\n `withGuardrail(Layout, { db, auth, members }).`,\n );\n }\n return value;\n}\n\nexport function getDB(): GRDBAdapter {\n return required(\"db\") as GRDBAdapter;\n}\n\nexport function getAuth(): GRAuthAdapter {\n return required(\"auth\") as GRAuthAdapter;\n}\n\nexport function getMembers(): GRMembershipAdapter {\n return required(\"members\") as GRMembershipAdapter;\n}\n\n/** Optional — sites without editable images need no storage. */\nexport function getStorage(): GRStorageAdapter | undefined {\n return _config?.storage;\n}\n\n/** Throws with a clear message if no page on this site uses <GRepeater>. */\nexport function getRepeaters(): GRRepeaterAdapter {\n return required(\"repeaters\") as GRRepeaterAdapter;\n}\n\nexport const DEFAULT_PREFETCH_LIMIT = 500;\n\n/** Normalized preload settings, or null when preloading is off. */\nexport function getPrefetch(): { prefix?: string[]; limit: number } | null {\n const p = _config?.prefetch;\n if (p === false) return null;\n return {\n ...(p?.prefix?.length ? { prefix: p.prefix } : {}),\n limit: p?.limit ?? DEFAULT_PREFETCH_LIMIT,\n };\n}\n\nexport function getInviteHandler(): GRConfig[\"onInvite\"] {\n return _config?.onInvite;\n}\n\nexport async function resolveSiteId(): Promise<string> {\n const sid = _config?.siteId ?? \"default\";\n return typeof sid === \"function\" ? await sid() : sid || \"default\";\n}\n"],"mappings":"AASA,IAAI,UAA2B;AAaxB,SAAS,mBAAmB,QAAwB;AACzD,YAAU;AACZ;AAEA,SAAS,SAAmC,KAAqB;AAC/D,QAAM,QAAQ,UAAU,GAAG;AAC3B,MAAI,CAAC,OAAO;AACV,UAAM,IAAI;AAAA,MACR,iBAAiB,OAAO,GAAG,CAAC;AAAA,IAE9B;AAAA,EACF;AACA,SAAO;AACT;AAEO,SAAS,QAAqB;AACnC,SAAO,SAAS,IAAI;AACtB;AAEO,SAAS,UAAyB;AACvC,SAAO,SAAS,MAAM;AACxB;AAEO,SAAS,aAAkC;AAChD,SAAO,SAAS,SAAS;AAC3B;AAGO,SAAS,aAA2C;AACzD,SAAO,SAAS;AAClB;AAGO,SAAS,eAAkC;AAChD,SAAO,SAAS,WAAW;AAC7B;AAEO,MAAM,yBAAyB;AAG/B,SAAS,cAA2D;AACzE,QAAM,IAAI,SAAS;AACnB,MAAI,MAAM,MAAO,QAAO;AACxB,SAAO;AAAA,IACL,GAAI,GAAG,QAAQ,SAAS,EAAE,QAAQ,EAAE,OAAO,IAAI,CAAC;AAAA,IAChD,OAAO,GAAG,SAAS;AAAA,EACrB;AACF;AAEO,SAAS,mBAAyC;AACvD,SAAO,SAAS;AAClB;AAEA,eAAsB,gBAAiC;AACrD,QAAM,MAAM,SAAS,UAAU;AAC/B,SAAO,OAAO,QAAQ,aAAa,MAAM,IAAI,IAAI,OAAO;AAC1D;","names":[]}
|
package/dist/core/defs.d.ts
CHANGED
|
@@ -1,7 +1,48 @@
|
|
|
1
1
|
declare const GR_COOKIES: {
|
|
2
|
-
/**
|
|
2
|
+
/**
|
|
3
|
+
* Signals intent to edit. Carries no authority on its own — every write
|
|
4
|
+
* still goes through a real, server-verified role check, independent of
|
|
5
|
+
* this — but it does decide whether the admin chrome (the sign-in bar, or
|
|
6
|
+
* the toolbar) renders at all, so it is not harmless either: a stale copy
|
|
7
|
+
* of it is a real, if low-stakes, exposure ("there's a CMS here") to
|
|
8
|
+
* whoever's browser it's sitting in next, not just its original visitor.
|
|
9
|
+
*
|
|
10
|
+
* Values: "true" (show chrome — the short-lived grace window below, for
|
|
11
|
+
* someone not yet authenticated to reach the sign-in form), "false"
|
|
12
|
+
* (explicitly suppressed, e.g. ?edit=false previewing without chrome while
|
|
13
|
+
* still signed in), or absent. Absent defers entirely to whether a real
|
|
14
|
+
* session exists (see withGuardrail) — an authenticated editor's chrome
|
|
15
|
+
* persists for as long as their actual session does, with no separate
|
|
16
|
+
* timer of its own to fall out of sync with it.
|
|
17
|
+
*/
|
|
3
18
|
readonly EDIT_MODE: "gr_edit_mode";
|
|
19
|
+
/**
|
|
20
|
+
* Set server-side the moment a redirect-carried session (an invite or
|
|
21
|
+
* password-recovery link) completes, so the next render — which has no
|
|
22
|
+
* other way to know that just happened — prompts to set a password
|
|
23
|
+
* instead of showing the normal toolbar. Cleared once they do, or once
|
|
24
|
+
* they explicitly skip.
|
|
25
|
+
*/
|
|
26
|
+
readonly NEEDS_PASSWORD: "gr_needs_password";
|
|
4
27
|
};
|
|
28
|
+
/**
|
|
29
|
+
* How long the edit-mode cookie's "true"/"false" values last before
|
|
30
|
+
* reverting to the session-derived default. Short on purpose: it only ever
|
|
31
|
+
* needs to outlive the handful of clicks between landing on ?edit=true and
|
|
32
|
+
* actually typing a password (or, for ?edit=false, a quick chrome-free
|
|
33
|
+
* preview) — not a whole working session, which persists through real auth
|
|
34
|
+
* instead. Bounds how long a stray copy of this cookie can show anything to
|
|
35
|
+
* whoever's browser it's left in, self-healing without needing an explicit
|
|
36
|
+
* logout.
|
|
37
|
+
*/
|
|
38
|
+
declare const GR_EDIT_MODE_TTL_SECONDS: number;
|
|
39
|
+
/**
|
|
40
|
+
* How long the set-a-password prompt stays up after an invite/recovery link
|
|
41
|
+
* completes. Longer than the edit-mode window above — picking a password
|
|
42
|
+
* takes more than one click — but still bounded, rather than nagging
|
|
43
|
+
* indefinitely if it's skipped and the cookie is somehow never cleared.
|
|
44
|
+
*/
|
|
45
|
+
declare const GR_NEEDS_PASSWORD_TTL_SECONDS: number;
|
|
5
46
|
/**
|
|
6
47
|
* Dedicated Postgres schema, not `public`. A name prefix alone only guards
|
|
7
48
|
* against accidental collisions; it does nothing against a `supabase db
|
|
@@ -19,4 +60,4 @@ declare const GR_TABLE = "gr_content";
|
|
|
19
60
|
declare const GR_MEMBERS_TABLE = "gr_members";
|
|
20
61
|
declare const GR_REPEATER_TABLE = "gr_repeater_items";
|
|
21
62
|
|
|
22
|
-
export { GR_COOKIES, GR_MEMBERS_TABLE, GR_REPEATER_TABLE, GR_SCHEMA, GR_TABLE };
|
|
63
|
+
export { GR_COOKIES, GR_EDIT_MODE_TTL_SECONDS, GR_MEMBERS_TABLE, GR_NEEDS_PASSWORD_TTL_SECONDS, GR_REPEATER_TABLE, GR_SCHEMA, GR_TABLE };
|
package/dist/core/defs.js
CHANGED
|
@@ -1,14 +1,41 @@
|
|
|
1
1
|
const GR_COOKIES = {
|
|
2
|
-
/**
|
|
3
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Signals intent to edit. Carries no authority on its own — every write
|
|
4
|
+
* still goes through a real, server-verified role check, independent of
|
|
5
|
+
* this — but it does decide whether the admin chrome (the sign-in bar, or
|
|
6
|
+
* the toolbar) renders at all, so it is not harmless either: a stale copy
|
|
7
|
+
* of it is a real, if low-stakes, exposure ("there's a CMS here") to
|
|
8
|
+
* whoever's browser it's sitting in next, not just its original visitor.
|
|
9
|
+
*
|
|
10
|
+
* Values: "true" (show chrome — the short-lived grace window below, for
|
|
11
|
+
* someone not yet authenticated to reach the sign-in form), "false"
|
|
12
|
+
* (explicitly suppressed, e.g. ?edit=false previewing without chrome while
|
|
13
|
+
* still signed in), or absent. Absent defers entirely to whether a real
|
|
14
|
+
* session exists (see withGuardrail) — an authenticated editor's chrome
|
|
15
|
+
* persists for as long as their actual session does, with no separate
|
|
16
|
+
* timer of its own to fall out of sync with it.
|
|
17
|
+
*/
|
|
18
|
+
EDIT_MODE: "gr_edit_mode",
|
|
19
|
+
/**
|
|
20
|
+
* Set server-side the moment a redirect-carried session (an invite or
|
|
21
|
+
* password-recovery link) completes, so the next render — which has no
|
|
22
|
+
* other way to know that just happened — prompts to set a password
|
|
23
|
+
* instead of showing the normal toolbar. Cleared once they do, or once
|
|
24
|
+
* they explicitly skip.
|
|
25
|
+
*/
|
|
26
|
+
NEEDS_PASSWORD: "gr_needs_password"
|
|
4
27
|
};
|
|
28
|
+
const GR_EDIT_MODE_TTL_SECONDS = 10 * 60;
|
|
29
|
+
const GR_NEEDS_PASSWORD_TTL_SECONDS = 30 * 60;
|
|
5
30
|
const GR_SCHEMA = "guardrail";
|
|
6
31
|
const GR_TABLE = "gr_content";
|
|
7
32
|
const GR_MEMBERS_TABLE = "gr_members";
|
|
8
33
|
const GR_REPEATER_TABLE = "gr_repeater_items";
|
|
9
34
|
export {
|
|
10
35
|
GR_COOKIES,
|
|
36
|
+
GR_EDIT_MODE_TTL_SECONDS,
|
|
11
37
|
GR_MEMBERS_TABLE,
|
|
38
|
+
GR_NEEDS_PASSWORD_TTL_SECONDS,
|
|
12
39
|
GR_REPEATER_TABLE,
|
|
13
40
|
GR_SCHEMA,
|
|
14
41
|
GR_TABLE
|
package/dist/core/defs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/core/defs.ts"],"sourcesContent":["export const GR_COOKIES = {\n
|
|
1
|
+
{"version":3,"sources":["../../src/core/defs.ts"],"sourcesContent":["export const GR_COOKIES = {\n /**\n * Signals intent to edit. Carries no authority on its own — every write\n * still goes through a real, server-verified role check, independent of\n * this — but it does decide whether the admin chrome (the sign-in bar, or\n * the toolbar) renders at all, so it is not harmless either: a stale copy\n * of it is a real, if low-stakes, exposure (\"there's a CMS here\") to\n * whoever's browser it's sitting in next, not just its original visitor.\n *\n * Values: \"true\" (show chrome — the short-lived grace window below, for\n * someone not yet authenticated to reach the sign-in form), \"false\"\n * (explicitly suppressed, e.g. ?edit=false previewing without chrome while\n * still signed in), or absent. Absent defers entirely to whether a real\n * session exists (see withGuardrail) — an authenticated editor's chrome\n * persists for as long as their actual session does, with no separate\n * timer of its own to fall out of sync with it.\n */\n EDIT_MODE: \"gr_edit_mode\",\n /**\n * Set server-side the moment a redirect-carried session (an invite or\n * password-recovery link) completes, so the next render — which has no\n * other way to know that just happened — prompts to set a password\n * instead of showing the normal toolbar. Cleared once they do, or once\n * they explicitly skip.\n */\n NEEDS_PASSWORD: \"gr_needs_password\",\n} as const;\n\n/**\n * How long the edit-mode cookie's \"true\"/\"false\" values last before\n * reverting to the session-derived default. Short on purpose: it only ever\n * needs to outlive the handful of clicks between landing on ?edit=true and\n * actually typing a password (or, for ?edit=false, a quick chrome-free\n * preview) — not a whole working session, which persists through real auth\n * instead. Bounds how long a stray copy of this cookie can show anything to\n * whoever's browser it's left in, self-healing without needing an explicit\n * logout.\n */\nexport const GR_EDIT_MODE_TTL_SECONDS = 10 * 60;\n\n/**\n * How long the set-a-password prompt stays up after an invite/recovery link\n * completes. Longer than the edit-mode window above — picking a password\n * takes more than one click — but still bounded, rather than nagging\n * indefinitely if it's skipped and the cookie is somehow never cleared.\n */\nexport const GR_NEEDS_PASSWORD_TTL_SECONDS = 30 * 60;\n\n/**\n * Dedicated Postgres schema, not `public`. A name prefix alone only guards\n * against accidental collisions; it does nothing against a `supabase db\n * reset`, a broad `DROP ... CASCADE`, or a migration tool that diffs and\n * \"reconciles\" everything in `public` as if it owns that namespace. A\n * separate schema keeps Guardrail's tables outside that blast radius —\n * the same reason auth.* and storage.* are their own schemas, not public.*.\n *\n * Requires \"guardrail\" to be added under Settings > API > Exposed schemas in\n * the Supabase dashboard — PostgREST only serves schemas on that list, and no\n * migration can add itself to it.\n */\nexport const GR_SCHEMA = \"guardrail\";\n\nexport const GR_TABLE = \"gr_content\";\nexport const GR_MEMBERS_TABLE = \"gr_members\";\nexport const GR_REPEATER_TABLE = \"gr_repeater_items\";\n"],"mappings":"AAAO,MAAM,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBxB,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQX,gBAAgB;AAClB;AAYO,MAAM,2BAA2B,KAAK;AAQtC,MAAM,gCAAgC,KAAK;AAc3C,MAAM,YAAY;AAElB,MAAM,WAAW;AACjB,MAAM,mBAAmB;AACzB,MAAM,oBAAoB;","names":[]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { OverrideRow } from './types.js';
|
|
2
|
+
import 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Builds a default row from the JSX the developer already wrote, so an
|
|
6
|
+
* un-edited field renders its hardcoded content. Deliberately does not write
|
|
7
|
+
* to the database — nothing is persisted until an editor saves.
|
|
8
|
+
*/
|
|
9
|
+
declare function generateScaffold(pathKey: string, props: Record<string, unknown>): OverrideRow;
|
|
10
|
+
|
|
11
|
+
export { generateScaffold };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
function generateScaffold(pathKey, props) {
|
|
2
|
+
const { children, type, src, alt, href } = props;
|
|
3
|
+
const defaultText = typeof children === "string" || typeof children === "number" ? String(children) : "";
|
|
4
|
+
let value;
|
|
5
|
+
if (type === "image" || src) {
|
|
6
|
+
value = { type: "image", src: src ?? "", alt: alt ?? "" };
|
|
7
|
+
} else if (type === "link" || href) {
|
|
8
|
+
value = { type: "link", href: href ?? "", text: defaultText };
|
|
9
|
+
} else if (type === "button") {
|
|
10
|
+
value = { type: "button", text: defaultText };
|
|
11
|
+
} else {
|
|
12
|
+
value = { type: "text", text: defaultText };
|
|
13
|
+
}
|
|
14
|
+
return { id: `scaffold-${pathKey}`, pathKey, value };
|
|
15
|
+
}
|
|
16
|
+
export {
|
|
17
|
+
generateScaffold
|
|
18
|
+
};
|
|
19
|
+
//# sourceMappingURL=scaffold.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/core/scaffold.ts"],"sourcesContent":["import type { OverrideRow, OverrideValue } from \"./types\";\n\n/**\n * Builds a default row from the JSX the developer already wrote, so an\n * un-edited field renders its hardcoded content. Deliberately does not write\n * to the database — nothing is persisted until an editor saves.\n */\nexport function generateScaffold(\n pathKey: string,\n props: Record<string, unknown>,\n): OverrideRow {\n const { children, type, src, alt, href } = props as {\n children?: unknown;\n type?: string;\n src?: string;\n alt?: string;\n href?: string;\n };\n\n const defaultText =\n typeof children === \"string\" || typeof children === \"number\"\n ? String(children)\n : \"\";\n\n let value: OverrideValue;\n\n if (type === \"image\" || src) {\n value = { type: \"image\", src: src ?? \"\", alt: alt ?? \"\" };\n } else if (type === \"link\" || href) {\n value = { type: \"link\", href: href ?? \"\", text: defaultText };\n } else if (type === \"button\") {\n value = { type: \"button\", text: defaultText };\n } else {\n value = { type: \"text\", text: defaultText };\n }\n\n return { id: `scaffold-${pathKey}`, pathKey, value };\n}\n"],"mappings":"AAOO,SAAS,iBACd,SACA,OACa;AACb,QAAM,EAAE,UAAU,MAAM,KAAK,KAAK,KAAK,IAAI;AAQ3C,QAAM,cACJ,OAAO,aAAa,YAAY,OAAO,aAAa,WAChD,OAAO,QAAQ,IACf;AAEN,MAAI;AAEJ,MAAI,SAAS,WAAW,KAAK;AAC3B,YAAQ,EAAE,MAAM,SAAS,KAAK,OAAO,IAAI,KAAK,OAAO,GAAG;AAAA,EAC1D,WAAW,SAAS,UAAU,MAAM;AAClC,YAAQ,EAAE,MAAM,QAAQ,MAAM,QAAQ,IAAI,MAAM,YAAY;AAAA,EAC9D,WAAW,SAAS,UAAU;AAC5B,YAAQ,EAAE,MAAM,UAAU,MAAM,YAAY;AAAA,EAC9C,OAAO;AACL,YAAQ,EAAE,MAAM,QAAQ,MAAM,YAAY;AAAA,EAC5C;AAEA,SAAO,EAAE,IAAI,YAAY,OAAO,IAAI,SAAS,MAAM;AACrD;","names":[]}
|
package/dist/core/types.d.ts
CHANGED
|
@@ -33,6 +33,19 @@ interface OverrideRow {
|
|
|
33
33
|
pathKey: string;
|
|
34
34
|
value: OverrideValue;
|
|
35
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* Field values preloaded for <GRail>s rendered inside Client Components, which
|
|
38
|
+
* cannot await a query themselves. Lean on purpose — it is serialized into the
|
|
39
|
+
* page — so it carries values only, not rows.
|
|
40
|
+
*/
|
|
41
|
+
interface GRContent {
|
|
42
|
+
values: Record<string, OverrideValue>;
|
|
43
|
+
/**
|
|
44
|
+
* True when `values` is every row this site has. A key absent from a
|
|
45
|
+
* complete map is known to be unedited, with no further lookup needed.
|
|
46
|
+
*/
|
|
47
|
+
complete: boolean;
|
|
48
|
+
}
|
|
36
49
|
/**
|
|
37
50
|
* next/image props that have no intrinsic-element equivalent. A field rendered
|
|
38
51
|
* with type="image" goes through next/image, so these have to be accepted even
|
|
@@ -100,6 +113,9 @@ interface GRAuthCapabilities {
|
|
|
100
113
|
interface GRState {
|
|
101
114
|
isAuth: boolean;
|
|
102
115
|
isEditMode: boolean;
|
|
116
|
+
/** A redirect-carried session (an invite or recovery link) just completed
|
|
117
|
+
* and has no password yet — see completeAuthRedirect. */
|
|
118
|
+
needsPassword: boolean;
|
|
103
119
|
siteId: string;
|
|
104
120
|
/** Null when the signed-in user holds no role on this site. */
|
|
105
121
|
role: GRRole | null;
|
|
@@ -132,5 +148,9 @@ type ActionState = {
|
|
|
132
148
|
email?: string;
|
|
133
149
|
password?: string;
|
|
134
150
|
};
|
|
151
|
+
type SetPasswordState = {
|
|
152
|
+
success?: boolean;
|
|
153
|
+
error?: string;
|
|
154
|
+
};
|
|
135
155
|
|
|
136
|
-
export type { ActionState, GRAsset, GRAuthCapabilities, GRMember, GRRepeaterItem, GRRole, GRState, GRViewer, HTMLKeys, NextImageExtras, OverrideRow, OverrideType, OverrideValue, PolymorphicProps };
|
|
156
|
+
export type { ActionState, GRAsset, GRAuthCapabilities, GRContent, GRMember, GRRepeaterItem, GRRole, GRState, GRViewer, HTMLKeys, NextImageExtras, OverrideRow, OverrideType, OverrideValue, PolymorphicProps, SetPasswordState };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export { GRail } from './react/GRail/universal.js';
|
|
2
|
+
export { useGRail } from './react/Provider.js';
|
|
3
|
+
export { createAssetUpload, inviteMember, listAssets, listTeam, removeMember, setPasswordAction, skipSetPasswordAction, updateMemberRole } from './server/actions.js';
|
|
4
|
+
export { addRepeaterItem, removeRepeaterItem, reorderRepeaterItems } from './server/repeaterActions.js';
|
|
5
|
+
export { GRepeaterProps, RepeaterItemProps } from './react/Repeater/index.js';
|
|
6
|
+
export { ActionState, GRAsset, GRMember, GRRepeaterItem, GRRole, GRState, GRViewer, HTMLKeys, OverrideRow, OverrideType, OverrideValue, PolymorphicProps, SetPasswordState } from './core/types.js';
|
|
7
|
+
export { AuthRedirectParams, AuthResult, GRAuthAdapter, GRConfig, GRDBAdapter, GRInviteEvent, GRMembershipAdapter, GRPrefetch, GRRepeaterAdapter, GRSession, GRStorageAdapter, GRStorageRow, GRUploadTarget } from './core/adapter-types.js';
|
|
8
|
+
import 'react/jsx-runtime';
|
|
9
|
+
import 'react';
|
|
10
|
+
|
|
11
|
+
declare const withGuardrail: (..._args: unknown[]) => never;
|
|
12
|
+
declare const configureGuardrail: (..._args: unknown[]) => never;
|
|
13
|
+
declare const GRepeater: (..._args: unknown[]) => never;
|
|
14
|
+
declare const listRepeaterItems: (..._args: unknown[]) => never;
|
|
15
|
+
declare const getRepeaterItemBySlug: (..._args: unknown[]) => never;
|
|
16
|
+
declare const getFieldValue: (..._args: unknown[]) => never;
|
|
17
|
+
|
|
18
|
+
export { GRepeater, configureGuardrail, getFieldValue, getRepeaterItemBySlug, listRepeaterItems, withGuardrail };
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { GRail } from "./react/GRail/universal";
|
|
2
|
+
import { useGRail } from "./react/Provider";
|
|
3
|
+
import {
|
|
4
|
+
listTeam,
|
|
5
|
+
inviteMember,
|
|
6
|
+
updateMemberRole,
|
|
7
|
+
removeMember,
|
|
8
|
+
createAssetUpload,
|
|
9
|
+
listAssets,
|
|
10
|
+
setPasswordAction,
|
|
11
|
+
skipSetPasswordAction
|
|
12
|
+
} from "./server/actions";
|
|
13
|
+
import {
|
|
14
|
+
addRepeaterItem,
|
|
15
|
+
removeRepeaterItem,
|
|
16
|
+
reorderRepeaterItems
|
|
17
|
+
} from "./server/repeaterActions";
|
|
18
|
+
function serverOnly(name, hint) {
|
|
19
|
+
return (..._args) => {
|
|
20
|
+
throw new Error(
|
|
21
|
+
`Guardrail: ${name} only works in a Server Component. ${hint} (If this isn't a Client Component, your tooling may not set the "react-server" condition \u2014 import from "@guard-rail/core/server" instead.)`
|
|
22
|
+
);
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
const withGuardrail = serverOnly(
|
|
26
|
+
"withGuardrail()",
|
|
27
|
+
"Call it in your root layout, which is a Server Component."
|
|
28
|
+
);
|
|
29
|
+
const configureGuardrail = serverOnly(
|
|
30
|
+
"configureGuardrail()",
|
|
31
|
+
"Configuration is applied by withGuardrail() in your root layout."
|
|
32
|
+
);
|
|
33
|
+
const GRepeater = serverOnly(
|
|
34
|
+
"<GRepeater>",
|
|
35
|
+
"Render it in a Server Component and pass it into this one as children or a prop."
|
|
36
|
+
);
|
|
37
|
+
const listRepeaterItems = serverOnly(
|
|
38
|
+
"listRepeaterItems()",
|
|
39
|
+
"Read repeater items in a Server Component and pass them down."
|
|
40
|
+
);
|
|
41
|
+
const getRepeaterItemBySlug = serverOnly(
|
|
42
|
+
"getRepeaterItemBySlug()",
|
|
43
|
+
"Resolve the item in a Server Component (your [slug]/page.tsx) and pass it down."
|
|
44
|
+
);
|
|
45
|
+
const getFieldValue = serverOnly(
|
|
46
|
+
"getFieldValue()",
|
|
47
|
+
"Read the value in a Server Component and pass it down as a prop."
|
|
48
|
+
);
|
|
49
|
+
export {
|
|
50
|
+
GRail,
|
|
51
|
+
GRepeater,
|
|
52
|
+
addRepeaterItem,
|
|
53
|
+
configureGuardrail,
|
|
54
|
+
createAssetUpload,
|
|
55
|
+
getFieldValue,
|
|
56
|
+
getRepeaterItemBySlug,
|
|
57
|
+
inviteMember,
|
|
58
|
+
listAssets,
|
|
59
|
+
listRepeaterItems,
|
|
60
|
+
listTeam,
|
|
61
|
+
removeMember,
|
|
62
|
+
removeRepeaterItem,
|
|
63
|
+
reorderRepeaterItems,
|
|
64
|
+
setPasswordAction,
|
|
65
|
+
skipSetPasswordAction,
|
|
66
|
+
updateMemberRole,
|
|
67
|
+
useGRail,
|
|
68
|
+
withGuardrail
|
|
69
|
+
};
|
|
70
|
+
//# sourceMappingURL=index.client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.client.ts"],"sourcesContent":["// Client-graph entry. `import ... from \"@guard-rail/core\"` resolves here from a\n// Client Component (see \"exports\" in package.json) and to ./index from a\n// Server Component, so an app uses one import path everywhere.\n//\n// Nothing server-only may be imported here — this is bundled for the browser.\n// Server Actions are fine: a \"use server\" module is replaced by references in\n// the client bundle, which is what lets an app build its own team/repeater UI\n// in a Client Component.\n\nexport { GRail } from \"./react/GRail/universal\";\nexport { useGRail } from \"./react/Provider\";\n\nexport {\n listTeam,\n inviteMember,\n updateMemberRole,\n removeMember,\n createAssetUpload,\n listAssets,\n setPasswordAction,\n skipSetPasswordAction,\n} from \"./server/actions\";\nexport {\n addRepeaterItem,\n removeRepeaterItem,\n reorderRepeaterItems,\n} from \"./server/repeaterActions\";\n\n/**\n * Stand-ins for the server-only exports, so importing one from a Client\n * Component fails with an explanation instead of an undefined-is-not-a-function\n * deep in a render. Also what a tool that doesn't set the \"react-server\"\n * condition (a test runner, a plain Node script) gets from the package root —\n * `@guard-rail/core/server` is the explicit path to the real ones there.\n */\nfunction serverOnly(name: string, hint: string) {\n return (..._args: unknown[]): never => {\n throw new Error(\n `Guardrail: ${name} only works in a Server Component. ${hint} ` +\n `(If this isn't a Client Component, your tooling may not set the ` +\n `\"react-server\" condition — import from \"@guard-rail/core/server\" instead.)`,\n );\n };\n}\n\nexport const withGuardrail = serverOnly(\n \"withGuardrail()\",\n \"Call it in your root layout, which is a Server Component.\",\n);\nexport const configureGuardrail = serverOnly(\n \"configureGuardrail()\",\n \"Configuration is applied by withGuardrail() in your root layout.\",\n);\nexport const GRepeater = serverOnly(\n \"<GRepeater>\",\n \"Render it in a Server Component and pass it into this one as children or a prop.\",\n);\nexport const listRepeaterItems = serverOnly(\n \"listRepeaterItems()\",\n \"Read repeater items in a Server Component and pass them down.\",\n);\nexport const getRepeaterItemBySlug = serverOnly(\n \"getRepeaterItemBySlug()\",\n \"Resolve the item in a Server Component (your [slug]/page.tsx) and pass it down.\",\n);\nexport const getFieldValue = serverOnly(\n \"getFieldValue()\",\n \"Read the value in a Server Component and pass it down as a prop.\",\n);\n\nexport type { GRepeaterProps, RepeaterItemProps } from \"./react/Repeater\";\nexport type {\n GRState,\n GRRole,\n GRMember,\n GRViewer,\n GRAsset,\n GRRepeaterItem,\n ActionState,\n SetPasswordState,\n OverrideRow,\n OverrideValue,\n OverrideType,\n PolymorphicProps,\n HTMLKeys,\n} from \"./core/types\";\nexport type {\n GRConfig,\n GRPrefetch,\n GRMembershipAdapter,\n GRInviteEvent,\n GRStorageAdapter,\n GRUploadTarget,\n GRRepeaterAdapter,\n GRDBAdapter,\n GRAuthAdapter,\n GRSession,\n GRStorageRow,\n AuthResult,\n AuthRedirectParams,\n} from \"./core/adapter-types\";\n"],"mappings":"AASA,SAAS,aAAa;AACtB,SAAS,gBAAgB;AAEzB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AASP,SAAS,WAAW,MAAc,MAAc;AAC9C,SAAO,IAAI,UAA4B;AACrC,UAAM,IAAI;AAAA,MACR,cAAc,IAAI,sCAAsC,IAAI;AAAA,IAG9D;AAAA,EACF;AACF;AAEO,MAAM,gBAAgB;AAAA,EAC3B;AAAA,EACA;AACF;AACO,MAAM,qBAAqB;AAAA,EAChC;AAAA,EACA;AACF;AACO,MAAM,YAAY;AAAA,EACvB;AAAA,EACA;AACF;AACO,MAAM,oBAAoB;AAAA,EAC/B;AAAA,EACA;AACF;AACO,MAAM,wBAAwB;AAAA,EACnC;AAAA,EACA;AACF;AACO,MAAM,gBAAgB;AAAA,EAC3B;AAAA,EACA;AACF;","names":[]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { withGuardrail } from './server/wrapper.js';
|
|
2
|
-
export { createAssetUpload, inviteMember, listAssets, listTeam, removeMember, updateMemberRole } from './server/actions.js';
|
|
2
|
+
export { createAssetUpload, inviteMember, listAssets, listTeam, removeMember, setPasswordAction, skipSetPasswordAction, updateMemberRole } from './server/actions.js';
|
|
3
3
|
export { GRail } from './react/GRail/index.js';
|
|
4
4
|
export { GRepeater, GRepeaterProps, RepeaterItemProps } from './react/Repeater/index.js';
|
|
5
5
|
export { configureGuardrail } from './core/config.js';
|
|
@@ -7,7 +7,7 @@ export { getRepeaterItemBySlug, listRepeaterItems } from './server/repeaterData.
|
|
|
7
7
|
export { getFieldValue } from './server/fieldData.js';
|
|
8
8
|
export { addRepeaterItem, removeRepeaterItem, reorderRepeaterItems } from './server/repeaterActions.js';
|
|
9
9
|
export { useGRail } from './react/Provider.js';
|
|
10
|
-
export { GRAsset, GRMember, GRRepeaterItem, GRRole, GRState, GRViewer, HTMLKeys, OverrideRow, OverrideType, OverrideValue, PolymorphicProps } from './core/types.js';
|
|
11
|
-
export { AuthResult, GRAuthAdapter, GRConfig, GRDBAdapter, GRInviteEvent, GRMembershipAdapter, GRRepeaterAdapter, GRSession, GRStorageAdapter, GRStorageRow, GRUploadTarget } from './core/adapter-types.js';
|
|
10
|
+
export { ActionState, GRAsset, GRMember, GRRepeaterItem, GRRole, GRState, GRViewer, HTMLKeys, OverrideRow, OverrideType, OverrideValue, PolymorphicProps, SetPasswordState } from './core/types.js';
|
|
11
|
+
export { AuthRedirectParams, AuthResult, GRAuthAdapter, GRConfig, GRDBAdapter, GRInviteEvent, GRMembershipAdapter, GRPrefetch, GRRepeaterAdapter, GRSession, GRStorageAdapter, GRStorageRow, GRUploadTarget } from './core/adapter-types.js';
|
|
12
12
|
import 'react/jsx-runtime';
|
|
13
13
|
import 'react';
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,9 @@ import {
|
|
|
5
5
|
updateMemberRole,
|
|
6
6
|
removeMember,
|
|
7
7
|
createAssetUpload,
|
|
8
|
-
listAssets
|
|
8
|
+
listAssets,
|
|
9
|
+
setPasswordAction,
|
|
10
|
+
skipSetPasswordAction
|
|
9
11
|
} from "./server/actions";
|
|
10
12
|
import { GRail } from "./react/GRail";
|
|
11
13
|
import { GRepeater } from "./react/Repeater";
|
|
@@ -36,6 +38,8 @@ export {
|
|
|
36
38
|
removeMember,
|
|
37
39
|
removeRepeaterItem,
|
|
38
40
|
reorderRepeaterItems,
|
|
41
|
+
setPasswordAction,
|
|
42
|
+
skipSetPasswordAction,
|
|
39
43
|
updateMemberRole,
|
|
40
44
|
useGRail,
|
|
41
45
|
withGuardrail
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["// Server-safe entry. Client components are reached through the files that\n// carry their own \"use client\" directive, so importing this from a Server\n// Component does not pull the editor into the client bundle.\nexport { withGuardrail } from \"./server/wrapper\";\n\n// Team management, exported so an app can build its own UI instead of using
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["// Server-safe entry. Client components are reached through the files that\n// carry their own \"use client\" directive, so importing this from a Server\n// Component does not pull the editor into the client bundle.\nexport { withGuardrail } from \"./server/wrapper\";\n\n// Team management and the set-password step, exported so an app can build\n// its own UI instead of using the bundled drawer / sign-in bar.\nexport {\n listTeam,\n inviteMember,\n updateMemberRole,\n removeMember,\n createAssetUpload,\n listAssets,\n setPasswordAction,\n skipSetPasswordAction,\n} from \"./server/actions\";\nexport { GRail } from \"./react/GRail\";\nexport { GRepeater } from \"./react/Repeater\";\nexport type { GRepeaterProps, RepeaterItemProps } from \"./react/Repeater\";\nexport { configureGuardrail } from \"./core/config\";\nexport {\n listRepeaterItems,\n getRepeaterItemBySlug,\n} from \"./server/repeaterData\";\nexport { getFieldValue } from \"./server/fieldData\";\n// Mutations, exported so an app can build its own add/remove/reorder UI\n// instead of the bundled chrome — same rationale as the team actions above.\nexport {\n addRepeaterItem,\n removeRepeaterItem,\n reorderRepeaterItems,\n} from \"./server/repeaterActions\";\nexport { useGRail } from \"./react/Provider\";\n\nexport type {\n GRState,\n GRRole,\n GRMember,\n GRViewer,\n GRAsset,\n GRRepeaterItem,\n ActionState,\n SetPasswordState,\n OverrideRow,\n OverrideValue,\n OverrideType,\n PolymorphicProps,\n HTMLKeys,\n} from \"./core/types\";\nexport type {\n GRConfig,\n GRPrefetch,\n GRMembershipAdapter,\n GRInviteEvent,\n GRStorageAdapter,\n GRUploadTarget,\n GRRepeaterAdapter,\n GRDBAdapter,\n GRAuthAdapter,\n GRSession,\n GRStorageRow,\n AuthResult,\n AuthRedirectParams,\n} from \"./core/adapter-types\";\n"],"mappings":"AAGA,SAAS,qBAAqB;AAI9B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,aAAa;AACtB,SAAS,iBAAiB;AAE1B,SAAS,0BAA0B;AACnC;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP,SAAS,qBAAqB;AAG9B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,gBAAgB;","names":[]}
|
package/dist/react/Admin.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
-
import
|
|
2
|
+
import React__default from 'react';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Owns the theme stylesheet so every piece of chrome — the sign-in bar, the
|
|
@@ -7,7 +7,7 @@ import React from 'react';
|
|
|
7
7
|
* light/dark variables from one place.
|
|
8
8
|
*/
|
|
9
9
|
declare function GRailAdmin(props: {
|
|
10
|
-
toolbarExtra?:
|
|
10
|
+
toolbarExtra?: React__default.ReactNode;
|
|
11
11
|
}): react_jsx_runtime.JSX.Element | null;
|
|
12
12
|
|
|
13
13
|
export { GRailAdmin };
|
package/dist/react/Admin.js
CHANGED
|
@@ -2,36 +2,59 @@
|
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import React, { useActionState, useEffect, useState } from "react";
|
|
4
4
|
import { usePathname, useRouter } from "next/navigation";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
loginAction,
|
|
7
|
+
logoutAction,
|
|
8
|
+
setPasswordAction,
|
|
9
|
+
skipSetPasswordAction
|
|
10
|
+
} from "../server/actions";
|
|
6
11
|
import { useGRail } from "./Provider";
|
|
7
12
|
import { TeamDrawer } from "./Team";
|
|
8
|
-
import { LogOutIcon, PeopleIcon } from "./Icons";
|
|
13
|
+
import { CloseIcon, LogOutIcon, PeopleIcon } from "./Icons";
|
|
9
14
|
import { S, THEME_CSS, v } from "./styles";
|
|
15
|
+
import { GR_COOKIES, GR_EDIT_MODE_TTL_SECONDS } from "../core/defs";
|
|
10
16
|
const initialState = { email: "", password: "" };
|
|
17
|
+
const initialPasswordState = {};
|
|
11
18
|
function GRailAdmin(props) {
|
|
12
19
|
const { isEditMode } = useGRail();
|
|
13
20
|
if (!isEditMode) return null;
|
|
14
21
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
15
|
-
/* @__PURE__ */ jsx("style", { children: THEME_CSS }),
|
|
22
|
+
/* @__PURE__ */ jsx("style", { href: "guardrail-theme", precedence: "default", children: THEME_CSS }),
|
|
16
23
|
/* @__PURE__ */ jsx(AdminBody, { ...props })
|
|
17
24
|
] });
|
|
18
25
|
}
|
|
19
26
|
function AdminBody({ toolbarExtra }) {
|
|
20
|
-
const { isAuth, isEditMode, role, auth } = useGRail();
|
|
27
|
+
const { isAuth, isEditMode, needsPassword, role, auth } = useGRail();
|
|
21
28
|
const [teamOpen, setTeamOpen] = useState(false);
|
|
22
29
|
const [formState, formAction, isPending] = useActionState(
|
|
23
30
|
loginAction,
|
|
24
31
|
initialState
|
|
25
32
|
);
|
|
33
|
+
const [passwordState, passwordFormAction, isSettingPassword] = useActionState(
|
|
34
|
+
setPasswordAction,
|
|
35
|
+
initialPasswordState
|
|
36
|
+
);
|
|
26
37
|
const router = useRouter();
|
|
27
38
|
const pathname = usePathname();
|
|
28
39
|
useEffect(() => {
|
|
29
40
|
if (formState.success) router.refresh();
|
|
30
41
|
}, [formState.success, router]);
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
if (passwordState.success) router.refresh();
|
|
44
|
+
}, [passwordState.success, router]);
|
|
45
|
+
const skipSetPassword = async () => {
|
|
46
|
+
await skipSetPasswordAction();
|
|
47
|
+
router.refresh();
|
|
48
|
+
};
|
|
31
49
|
const signOut = async () => {
|
|
32
50
|
await logoutAction();
|
|
33
|
-
|
|
34
|
-
|
|
51
|
+
router.replace(pathname);
|
|
52
|
+
router.refresh();
|
|
53
|
+
};
|
|
54
|
+
const dismissEditMode = () => {
|
|
55
|
+
document.cookie = `${GR_COOKIES.EDIT_MODE}=false; path=/; max-age=${GR_EDIT_MODE_TTL_SECONDS}; SameSite=Lax`;
|
|
56
|
+
router.replace(pathname);
|
|
57
|
+
router.refresh();
|
|
35
58
|
};
|
|
36
59
|
if (!isEditMode) return null;
|
|
37
60
|
if (!isAuth) {
|
|
@@ -41,7 +64,18 @@ function AdminBody({ toolbarExtra }) {
|
|
|
41
64
|
/* @__PURE__ */ jsx("span", { style: S.brand, children: "Guardrail" }),
|
|
42
65
|
!auth.signInUrl && /* @__PURE__ */ jsx("span", { style: { fontSize: 12, color: v("muted") }, children: "No sign-in method is configured for this site." })
|
|
43
66
|
] }),
|
|
44
|
-
auth.signInUrl && /* @__PURE__ */ jsx("a", { href: auth.signInUrl, style: { ...S.primaryBtn, textDecoration: "none" }, children: "Sign in" })
|
|
67
|
+
auth.signInUrl && /* @__PURE__ */ jsx("a", { href: auth.signInUrl, style: { ...S.primaryBtn, textDecoration: "none" }, children: "Sign in" }),
|
|
68
|
+
/* @__PURE__ */ jsx(
|
|
69
|
+
"button",
|
|
70
|
+
{
|
|
71
|
+
type: "button",
|
|
72
|
+
style: { ...S.iconBtn, color: v("muted") },
|
|
73
|
+
onClick: dismissEditMode,
|
|
74
|
+
title: "Not editing? Dismiss",
|
|
75
|
+
"aria-label": "Dismiss",
|
|
76
|
+
children: /* @__PURE__ */ jsx(CloseIcon, {})
|
|
77
|
+
}
|
|
78
|
+
)
|
|
45
79
|
] }) });
|
|
46
80
|
}
|
|
47
81
|
return /* @__PURE__ */ jsx("div", { "data-guardrail": true, style: S.banner, children: /* @__PURE__ */ jsxs("form", { action: formAction, style: S.bannerForm, children: [
|
|
@@ -70,7 +104,49 @@ function AdminBody({ toolbarExtra }) {
|
|
|
70
104
|
style: S.input
|
|
71
105
|
}
|
|
72
106
|
),
|
|
73
|
-
/* @__PURE__ */ jsx("button", { type: "submit", disabled: isPending, style: S.primaryBtn, children: isPending ? "Authenticating\u2026" : "Sign In" })
|
|
107
|
+
/* @__PURE__ */ jsx("button", { type: "submit", disabled: isPending, style: S.primaryBtn, children: isPending ? "Authenticating\u2026" : "Sign In" }),
|
|
108
|
+
/* @__PURE__ */ jsx(
|
|
109
|
+
"button",
|
|
110
|
+
{
|
|
111
|
+
type: "button",
|
|
112
|
+
style: { ...S.iconBtn, color: v("muted") },
|
|
113
|
+
onClick: dismissEditMode,
|
|
114
|
+
title: "Not editing? Dismiss",
|
|
115
|
+
"aria-label": "Dismiss",
|
|
116
|
+
children: /* @__PURE__ */ jsx(CloseIcon, {})
|
|
117
|
+
}
|
|
118
|
+
)
|
|
119
|
+
] }) });
|
|
120
|
+
}
|
|
121
|
+
if (needsPassword) {
|
|
122
|
+
return /* @__PURE__ */ jsx("div", { "data-guardrail": true, style: S.banner, children: /* @__PURE__ */ jsxs("form", { action: passwordFormAction, style: S.bannerForm, children: [
|
|
123
|
+
/* @__PURE__ */ jsxs("div", { style: S.brandStack, children: [
|
|
124
|
+
/* @__PURE__ */ jsx("span", { style: S.brand, children: "Guardrail" }),
|
|
125
|
+
passwordState.error ? /* @__PURE__ */ jsx("span", { style: S.error, children: passwordState.error }) : /* @__PURE__ */ jsx("span", { style: { fontSize: 12, color: v("muted") }, children: "Set a password to sign in next time" })
|
|
126
|
+
] }),
|
|
127
|
+
/* @__PURE__ */ jsx(
|
|
128
|
+
"input",
|
|
129
|
+
{
|
|
130
|
+
name: "password",
|
|
131
|
+
type: "password",
|
|
132
|
+
placeholder: "New password",
|
|
133
|
+
required: true,
|
|
134
|
+
minLength: 6,
|
|
135
|
+
style: S.input
|
|
136
|
+
}
|
|
137
|
+
),
|
|
138
|
+
/* @__PURE__ */ jsx("button", { type: "submit", disabled: isSettingPassword, style: S.primaryBtn, children: isSettingPassword ? "Saving\u2026" : "Set Password" }),
|
|
139
|
+
/* @__PURE__ */ jsx(
|
|
140
|
+
"button",
|
|
141
|
+
{
|
|
142
|
+
type: "button",
|
|
143
|
+
style: { ...S.iconBtn, color: v("muted") },
|
|
144
|
+
onClick: skipSetPassword,
|
|
145
|
+
title: "Skip for now",
|
|
146
|
+
"aria-label": "Skip",
|
|
147
|
+
children: /* @__PURE__ */ jsx(CloseIcon, {})
|
|
148
|
+
}
|
|
149
|
+
)
|
|
74
150
|
] }) });
|
|
75
151
|
}
|
|
76
152
|
if (!role) {
|
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 { LogOutIcon, PeopleIcon } from \"./Icons\";\nimport { S, THEME_CSS, v } from \"./styles\";\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 <style>{THEME_CSS}</style>\n <AdminBody {...props} />\n </>\n );\n}\n\nfunction AdminBody({ toolbarExtra }: { toolbarExtra?: React.ReactNode }) {\n const { isAuth, isEditMode, role, auth } = useGRail();\n const [teamOpen, setTeamOpen] = 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 const signOut = async () => {\n await logoutAction();\n document.cookie =\n \"gr_edit_mode=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT\";\n window.location.href = pathname;\n };\n\n // No intent signalled — render nothing at all.\n if (!isEditMode) return null;\n\n if (!isAuth) {\n // The adapter cannot take a password here, so hand sign-in off rather than\n // showing a form that could never succeed.\n if (!auth.password) {\n return (\n <div data-guardrail style={S.banner}>\n <div style={S.bannerForm}>\n <div style={S.brandStack}>\n <span style={S.brand}>Guardrail</span>\n {!auth.signInUrl && (\n <span style={{ fontSize: 12, color: v(\"muted\") }}>\n No sign-in method is configured for this site.\n </span>\n )}\n </div>\n {auth.signInUrl && (\n <a href={auth.signInUrl} style={{ ...S.primaryBtn, textDecoration: \"none\" }}>\n Sign in\n </a>\n )}\n </div>\n </div>\n );\n }\n\n return (\n <div data-guardrail style={S.banner}>\n <form action={formAction} style={S.bannerForm}>\n <div style={S.brandStack}>\n <span style={S.brand}>Guardrail</span>\n {formState.error && <span style={S.error}>{formState.error}</span>}\n </div>\n <input\n name=\"email\"\n type=\"email\"\n placeholder=\"Email\"\n defaultValue={formState.email}\n required\n style={S.input}\n />\n <input\n name=\"password\"\n type=\"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 </form>\n </div>\n );\n }\n\n // Signed in, but holds no role on this site.\n if (!role) {\n return (\n <div data-guardrail style={S.toolbar}>\n <span style={S.toolbarLabel}>No access to this site</span>\n <span style={S.divider} />\n <button\n style={{ ...S.iconBtn, color: v(\"muted\") }}\n onClick={signOut}\n title=\"Log out\"\n aria-label=\"Log out\"\n >\n <LogOutIcon />\n </button>\n </div>\n );\n }\n\n return (\n <>\n {teamOpen && <TeamDrawer onClose={() => setTeamOpen(false)} />}\n <div data-guardrail style={S.toolbar}>\n <span style={S.dot} />\n <span style={S.toolbarLabel}>Edit Mode</span>\n <span style={S.divider} />\n <button\n style={{ ...S.iconBtn, color: v(\"muted\"), width: 24, height: 24 }}\n onClick={() => setTeamOpen(true)}\n title=\"Team\"\n aria-label=\"Team\"\n >\n <PeopleIcon />\n </button>\n\n {toolbarExtra && (\n <>\n <span style={S.divider} />\n {toolbarExtra}\n </>\n )}\n\n <span style={S.divider} />\n <button\n style={{ ...S.iconBtn, color: v(\"muted\") }}\n onClick={signOut}\n title=\"Log out\"\n aria-label=\"Log out\"\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 {\n loginAction,\n logoutAction,\n setPasswordAction,\n skipSetPasswordAction,\n} from \"../server/actions\";\nimport { useGRail } from \"./Provider\";\nimport { TeamDrawer } from \"./Team\";\nimport { CloseIcon, LogOutIcon, PeopleIcon } from \"./Icons\";\nimport { S, THEME_CSS, v } from \"./styles\";\nimport { GR_COOKIES, GR_EDIT_MODE_TTL_SECONDS } from \"../core/defs\";\nimport type { ActionState, SetPasswordState } from \"../core/types\";\n\nconst initialState: ActionState = { email: \"\", password: \"\" };\nconst initialPasswordState: SetPasswordState = {};\n\n/**\n * Owns the theme stylesheet so every piece of chrome — the sign-in bar, the\n * toolbar, and the field popups portaled to <body> — picks up the same\n * light/dark variables from one place.\n */\nexport function GRailAdmin(props: { toolbarExtra?: React.ReactNode }) {\n const { isEditMode } = useGRail();\n if (!isEditMode) return null;\n return (\n <>\n {/*\n precedence + href make React hoist this into <head> and dedupe it.\n Required, not cosmetic: this whole component renders as a sibling of\n <html> (withGuardrail can't reach inside the app's own layout), and\n React 19 rejects a bare <style> anywhere outside the main document.\n */}\n <style href=\"guardrail-theme\" precedence=\"default\">\n {THEME_CSS}\n </style>\n <AdminBody {...props} />\n </>\n );\n}\n\nfunction AdminBody({ toolbarExtra }: { toolbarExtra?: React.ReactNode }) {\n const { isAuth, isEditMode, needsPassword, role, auth } = useGRail();\n const [teamOpen, setTeamOpen] = useState(false);\n const [formState, formAction, isPending] = useActionState(\n loginAction,\n initialState,\n );\n const [passwordState, passwordFormAction, isSettingPassword] = useActionState(\n setPasswordAction,\n initialPasswordState,\n );\n const router = useRouter();\n const pathname = usePathname();\n\n useEffect(() => {\n if (formState.success) router.refresh();\n }, [formState.success, router]);\n\n useEffect(() => {\n if (passwordState.success) router.refresh();\n }, [passwordState.success, router]);\n\n const skipSetPassword = async () => {\n await skipSetPasswordAction();\n router.refresh();\n };\n\n const signOut = async () => {\n // logoutAction() now clears the edit-mode cookie itself — see the\n // comment there for why a client-side hard navigation right after it\n // used to race Next's own automatic post-action route refresh and lose.\n // router.replace + refresh works with that refresh instead of against\n // it: replace drops ?edit=true from the URL (a bare reload would leave\n // it there, since the cookie, not the query, is what withGuardrail\n // reads), and refresh re-renders the tree against the now-cleared\n // cookies without a full page reload.\n await logoutAction();\n router.replace(pathname);\n router.refresh();\n };\n\n // For someone who was never authenticated and doesn't want to be here at\n // all — a stray or expired-but-not-yet-cleared cookie, say. Explicit\n // \"false\" rather than deleting: a plain delete would do nothing for an\n // authenticated visitor (their session alone is enough to keep chrome\n // visible), but this button only ever renders in the not-yet-authenticated\n // branches below, where the two are equivalent — \"false\" is used anyway\n // for consistency with the one cookie-writing convention (see Syncer.tsx).\n const dismissEditMode = () => {\n document.cookie = `${GR_COOKIES.EDIT_MODE}=false; path=/; max-age=${GR_EDIT_MODE_TTL_SECONDS}; SameSite=Lax`;\n router.replace(pathname);\n router.refresh();\n };\n\n // No intent signalled — render nothing at all.\n if (!isEditMode) return null;\n\n if (!isAuth) {\n // The adapter cannot take a password here, so hand sign-in off rather than\n // showing a form that could never succeed.\n if (!auth.password) {\n return (\n <div data-guardrail style={S.banner}>\n <div style={S.bannerForm}>\n <div style={S.brandStack}>\n <span style={S.brand}>Guardrail</span>\n {!auth.signInUrl && (\n <span style={{ fontSize: 12, color: v(\"muted\") }}>\n No sign-in method is configured for this site.\n </span>\n )}\n </div>\n {auth.signInUrl && (\n <a href={auth.signInUrl} style={{ ...S.primaryBtn, textDecoration: \"none\" }}>\n Sign in\n </a>\n )}\n <button\n type=\"button\"\n style={{ ...S.iconBtn, color: v(\"muted\") }}\n onClick={dismissEditMode}\n title=\"Not editing? Dismiss\"\n aria-label=\"Dismiss\"\n >\n <CloseIcon />\n </button>\n </div>\n </div>\n );\n }\n\n return (\n <div data-guardrail style={S.banner}>\n <form action={formAction} style={S.bannerForm}>\n <div style={S.brandStack}>\n <span style={S.brand}>Guardrail</span>\n {formState.error && <span style={S.error}>{formState.error}</span>}\n </div>\n <input\n name=\"email\"\n type=\"email\"\n placeholder=\"Email\"\n defaultValue={formState.email}\n required\n style={S.input}\n />\n <input\n name=\"password\"\n type=\"password\"\n placeholder=\"Password\"\n required\n style={S.input}\n />\n <button type=\"submit\" disabled={isPending} style={S.primaryBtn}>\n {isPending ? \"Authenticating…\" : \"Sign In\"}\n </button>\n <button\n type=\"button\"\n style={{ ...S.iconBtn, color: v(\"muted\") }}\n onClick={dismissEditMode}\n title=\"Not editing? Dismiss\"\n aria-label=\"Dismiss\"\n >\n <CloseIcon />\n </button>\n </form>\n </div>\n );\n }\n\n // A redirect-carried session (an invite or password-recovery link) just\n // completed — they're genuinely signed in, but have no password yet.\n // Ahead of the role check below: getCachedViewer() already claims a\n // pending invite as a side effect of resolving them, so role is normally\n // already set by the time this renders — this is purely about the\n // password, which is orthogonal to that. Skippable rather than a hard\n // gate: magic-link sign-in already works with no password at all, so\n // someone can dismiss this and keep using invite/magic links indefinitely.\n if (needsPassword) {\n return (\n <div data-guardrail style={S.banner}>\n <form action={passwordFormAction} style={S.bannerForm}>\n <div style={S.brandStack}>\n <span style={S.brand}>Guardrail</span>\n {passwordState.error ? (\n <span style={S.error}>{passwordState.error}</span>\n ) : (\n <span style={{ fontSize: 12, color: v(\"muted\") }}>\n Set a password to sign in next time\n </span>\n )}\n </div>\n <input\n name=\"password\"\n type=\"password\"\n placeholder=\"New password\"\n required\n minLength={6}\n style={S.input}\n />\n <button type=\"submit\" disabled={isSettingPassword} style={S.primaryBtn}>\n {isSettingPassword ? \"Saving…\" : \"Set Password\"}\n </button>\n <button\n type=\"button\"\n style={{ ...S.iconBtn, color: v(\"muted\") }}\n onClick={skipSetPassword}\n title=\"Skip for now\"\n aria-label=\"Skip\"\n >\n <CloseIcon />\n </button>\n </form>\n </div>\n );\n }\n\n // Signed in, but holds no role on this site.\n if (!role) {\n return (\n <div data-guardrail style={S.toolbar}>\n <span style={S.toolbarLabel}>No access to this site</span>\n <span style={S.divider} />\n <button\n style={{ ...S.iconBtn, color: v(\"muted\") }}\n onClick={signOut}\n title=\"Log out\"\n aria-label=\"Log out\"\n >\n <LogOutIcon />\n </button>\n </div>\n );\n }\n\n return (\n <>\n {teamOpen && <TeamDrawer onClose={() => setTeamOpen(false)} />}\n <div data-guardrail style={S.toolbar}>\n <span style={S.dot} />\n <span style={S.toolbarLabel}>Edit Mode</span>\n <span style={S.divider} />\n <button\n style={{ ...S.iconBtn, color: v(\"muted\"), width: 24, height: 24 }}\n onClick={() => setTeamOpen(true)}\n title=\"Team\"\n aria-label=\"Team\"\n >\n <PeopleIcon />\n </button>\n\n {toolbarExtra && (\n <>\n <span style={S.divider} />\n {toolbarExtra}\n </>\n )}\n\n <span style={S.divider} />\n <button\n style={{ ...S.iconBtn, color: v(\"muted\") }}\n onClick={signOut}\n title=\"Log out\"\n aria-label=\"Log out\"\n >\n <LogOutIcon />\n </button>\n </div>\n </>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Notices a session that arrived via a redirect link — an invite, magic
|
|
3
|
+
* link, or password-recovery email — and hands whatever it found to
|
|
4
|
+
* completeAuthRedirectAction, which defers to the auth adapter's own
|
|
5
|
+
* completeAuthRedirect for the actual exchange. Deliberately generic: this
|
|
6
|
+
* file has no idea what a Supabase token looks like, only that a hash
|
|
7
|
+
* carrying `access_token=` or a `code`/`token_hash` query param might be one.
|
|
8
|
+
*
|
|
9
|
+
* Has to run client-side and unconditionally (not gated behind edit mode,
|
|
10
|
+
* unlike EditSyncer) — an invite link redirects to the site's plain URL, not
|
|
11
|
+
* necessarily one with ?edit=true, and hash-based tokens never reach the
|
|
12
|
+
* server on their own; only the browser ever sees window.location.hash.
|
|
13
|
+
*
|
|
14
|
+
* Mirrors the community-documented pattern for this exact problem, just
|
|
15
|
+
* generalized across whichever redirect style a provider's own email
|
|
16
|
+
* templates happen to produce, rather than requiring one specific route.
|
|
17
|
+
*/
|
|
18
|
+
declare function AuthRedirectSync(): null;
|
|
19
|
+
|
|
20
|
+
export { AuthRedirectSync };
|