@dalgoridim/headless-cms 0.3.1 → 0.4.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.
@@ -1,4 +1,4 @@
1
- import * as React from 'react';
1
+ import * as react from 'react';
2
2
  import { ReactNode } from 'react';
3
3
  import { Auth, GoogleAuthProvider, User } from 'firebase/auth';
4
4
 
@@ -22,7 +22,7 @@ interface FirebaseAuthContextValue {
22
22
  loginWithEmail: (email: string, password: string) => Promise<void>;
23
23
  logout: () => Promise<void>;
24
24
  }
25
- declare function FirebaseAuthProvider({ children, auth, googleProvider, cookieName, onLogout, }: FirebaseAuthProviderProps): React.JSX.Element;
25
+ declare function FirebaseAuthProvider({ children, auth, googleProvider, cookieName, onLogout, }: FirebaseAuthProviderProps): react.JSX.Element;
26
26
  /** Extended Firebase auth API (login/logout/user) for login pages and toolbars. */
27
27
  declare function useFirebaseAuth(): FirebaseAuthContextValue;
28
28
 
@@ -1,4 +1,4 @@
1
- import * as React from 'react';
1
+ import * as react from 'react';
2
2
  import { ReactNode } from 'react';
3
3
  import { Auth, GoogleAuthProvider, User } from 'firebase/auth';
4
4
 
@@ -22,7 +22,7 @@ interface FirebaseAuthContextValue {
22
22
  loginWithEmail: (email: string, password: string) => Promise<void>;
23
23
  logout: () => Promise<void>;
24
24
  }
25
- declare function FirebaseAuthProvider({ children, auth, googleProvider, cookieName, onLogout, }: FirebaseAuthProviderProps): React.JSX.Element;
25
+ declare function FirebaseAuthProvider({ children, auth, googleProvider, cookieName, onLogout, }: FirebaseAuthProviderProps): react.JSX.Element;
26
26
  /** Extended Firebase auth API (login/logout/user) for login pages and toolbars. */
27
27
  declare function useFirebaseAuth(): FirebaseAuthContextValue;
28
28
 
@@ -1,9 +1,38 @@
1
1
  "use strict";
2
2
  "use client";
3
3
  var __defProp = Object.defineProperty;
4
+ var __defProps = Object.defineProperties;
4
5
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
5
7
  var __getOwnPropNames = Object.getOwnPropertyNames;
8
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
6
9
  var __hasOwnProp = Object.prototype.hasOwnProperty;
10
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
11
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
12
+ var __spreadValues = (a, b) => {
13
+ for (var prop in b || (b = {}))
14
+ if (__hasOwnProp.call(b, prop))
15
+ __defNormalProp(a, prop, b[prop]);
16
+ if (__getOwnPropSymbols)
17
+ for (var prop of __getOwnPropSymbols(b)) {
18
+ if (__propIsEnum.call(b, prop))
19
+ __defNormalProp(a, prop, b[prop]);
20
+ }
21
+ return a;
22
+ };
23
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
24
+ var __objRest = (source, exclude) => {
25
+ var target = {};
26
+ for (var prop in source)
27
+ if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
28
+ target[prop] = source[prop];
29
+ if (source != null && __getOwnPropSymbols)
30
+ for (var prop of __getOwnPropSymbols(source)) {
31
+ if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
32
+ target[prop] = source[prop];
33
+ }
34
+ return target;
35
+ };
7
36
  var __export = (target, all) => {
8
37
  for (var name in all)
9
38
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -22,13 +51,14 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
22
51
  var client_exports = {};
23
52
  __export(client_exports, {
24
53
  GoogleAuthProvider: () => GoogleAuthProvider,
54
+ GoogleSignInButton: () => GoogleSignInButton,
25
55
  useGoogleAuth: () => useGoogleAuth
26
56
  });
27
57
  module.exports = __toCommonJS(client_exports);
28
58
  var import_react = require("react");
59
+ var import_google = require("@react-oauth/google");
29
60
  var import_client = require("@dalgoridim/headless-cms/client");
30
61
  var import_jsx_runtime = require("react/jsx-runtime");
31
- var GSI_SRC = "https://accounts.google.com/gsi/client";
32
62
  var GoogleAuthContext = (0, import_react.createContext)(
33
63
  void 0
34
64
  );
@@ -49,15 +79,13 @@ function decodeJwt(token) {
49
79
  try {
50
80
  const payload = token.split(".")[1];
51
81
  const json = atob(payload.replace(/-/g, "+").replace(/_/g, "/"));
52
- const data = JSON.parse(json);
53
- return data;
82
+ return JSON.parse(json);
54
83
  } catch (e) {
55
84
  return null;
56
85
  }
57
86
  }
58
- function GoogleAuthProvider({
87
+ function AuthState({
59
88
  children,
60
- clientId,
61
89
  adminEmails,
62
90
  cookieName = "adminToken",
63
91
  onLogout
@@ -65,12 +93,10 @@ function GoogleAuthProvider({
65
93
  const [user, setUser] = (0, import_react.useState)(null);
66
94
  const [isAdmin, setIsAdmin] = (0, import_react.useState)(false);
67
95
  const [isEditing, setIsEditing] = (0, import_react.useState)(false);
68
- const [ready, setReady] = (0, import_react.useState)(false);
69
- const [failed, setFailed] = (0, import_react.useState)(false);
70
96
  const allowed = (0, import_react.useRef)(
71
97
  (adminEmails != null ? adminEmails : []).map((e) => e.trim().toLowerCase())
72
98
  );
73
- const applyToken = (0, import_react.useCallback)(
99
+ const applyCredential = (0, import_react.useCallback)(
74
100
  (token) => {
75
101
  var _a;
76
102
  const claims = decodeJwt(token);
@@ -94,44 +120,16 @@ function GoogleAuthProvider({
94
120
  );
95
121
  (0, import_react.useEffect)(() => {
96
122
  const existing = readCookie(cookieName);
97
- if (existing) applyToken(existing);
98
- }, [cookieName, applyToken]);
99
- (0, import_react.useEffect)(() => {
100
- let cancelled = false;
101
- function tryInit() {
102
- var _a, _b;
103
- if (cancelled || !((_b = (_a = window.google) == null ? void 0 : _a.accounts) == null ? void 0 : _b.id)) return false;
104
- window.google.accounts.id.initialize({
105
- client_id: clientId,
106
- callback: (res) => applyToken(res.credential),
107
- auto_select: false,
108
- cancel_on_tap_outside: true
109
- });
110
- setReady(true);
111
- return true;
112
- }
113
- if (tryInit()) return;
114
- if (!document.querySelector(`script[src="${GSI_SRC}"]`)) {
115
- const script = document.createElement("script");
116
- script.src = GSI_SRC;
117
- script.async = true;
118
- script.defer = true;
119
- document.head.appendChild(script);
120
- }
121
- const startedAt = Date.now();
122
- const timer = setInterval(() => {
123
- if (tryInit()) {
124
- clearInterval(timer);
125
- } else if (Date.now() - startedAt > 1e4) {
126
- clearInterval(timer);
127
- if (!cancelled) setFailed(true);
128
- }
129
- }, 120);
130
- return () => {
131
- cancelled = true;
132
- clearInterval(timer);
133
- };
134
- }, [clientId, applyToken]);
123
+ if (existing) applyCredential(existing);
124
+ }, [cookieName, applyCredential]);
125
+ const logout = (0, import_react.useCallback)(() => {
126
+ (0, import_google.googleLogout)();
127
+ deleteCookie(cookieName);
128
+ setUser(null);
129
+ setIsAdmin(false);
130
+ setIsEditing(false);
131
+ onLogout == null ? void 0 : onLogout();
132
+ }, [cookieName, onLogout]);
135
133
  (0, import_react.useEffect)(() => {
136
134
  const originalFetch = window.fetch;
137
135
  window.fetch = async (...args) => {
@@ -139,7 +137,7 @@ function GoogleAuthProvider({
139
137
  if (response.status === 401) {
140
138
  try {
141
139
  const data = await response.clone().json();
142
- if (data == null ? void 0 : data.logout) doLogout();
140
+ if (data == null ? void 0 : data.logout) logout();
143
141
  } catch (e) {
144
142
  }
145
143
  }
@@ -148,48 +146,48 @@ function GoogleAuthProvider({
148
146
  return () => {
149
147
  window.fetch = originalFetch;
150
148
  };
151
- }, []);
152
- function doLogout() {
153
- var _a;
154
- (_a = window.google) == null ? void 0 : _a.accounts.id.disableAutoSelect();
155
- deleteCookie(cookieName);
156
- setUser(null);
157
- setIsAdmin(false);
158
- setIsEditing(false);
159
- onLogout == null ? void 0 : onLogout();
160
- }
161
- const promptSignIn = (0, import_react.useCallback)(() => {
162
- var _a;
163
- if (!ready) return;
164
- (_a = window.google) == null ? void 0 : _a.accounts.id.prompt();
165
- }, [ready]);
166
- const renderButton = (0, import_react.useCallback)(
167
- (el, options) => {
168
- var _a;
169
- if (!ready) return;
170
- (_a = window.google) == null ? void 0 : _a.accounts.id.renderButton(el, options);
171
- },
172
- [ready]
173
- );
149
+ }, [logout]);
174
150
  const toggleEdit = (0, import_react.useCallback)(() => setIsEditing((p) => !p), []);
175
151
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
176
152
  GoogleAuthContext.Provider,
177
153
  {
178
- value: {
179
- user,
180
- isAdmin,
181
- isEditing,
182
- toggleEdit,
183
- ready,
184
- failed,
185
- promptSignIn,
186
- renderButton,
187
- logout: doLogout
188
- },
154
+ value: { user, isAdmin, isEditing, toggleEdit, logout, applyCredential },
189
155
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_client.CmsAuthProvider, { value: { isAdmin, isEditing, toggleEdit }, children })
190
156
  }
191
157
  );
192
158
  }
159
+ function GoogleAuthProvider(_a) {
160
+ var _b = _a, {
161
+ children,
162
+ clientId
163
+ } = _b, rest = __objRest(_b, [
164
+ "children",
165
+ "clientId"
166
+ ]);
167
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_google.GoogleOAuthProvider, { clientId, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AuthState, __spreadProps(__spreadValues({}, rest), { children })) });
168
+ }
169
+ function GoogleSignInButton({
170
+ theme = "filled_black",
171
+ shape = "pill",
172
+ size = "large",
173
+ text = "continue_with",
174
+ onError
175
+ }) {
176
+ const { applyCredential } = useGoogleAuth();
177
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
178
+ import_google.GoogleLogin,
179
+ {
180
+ onSuccess: (res) => {
181
+ if (res.credential) applyCredential(res.credential);
182
+ },
183
+ onError,
184
+ theme,
185
+ shape,
186
+ size,
187
+ text
188
+ }
189
+ );
190
+ }
193
191
  function useGoogleAuth() {
194
192
  const ctx = (0, import_react.useContext)(GoogleAuthContext);
195
193
  if (!ctx) {
@@ -200,6 +198,7 @@ function useGoogleAuth() {
200
198
  // Annotate the CommonJS export names for ESM import in node:
201
199
  0 && (module.exports = {
202
200
  GoogleAuthProvider,
201
+ GoogleSignInButton,
203
202
  useGoogleAuth
204
203
  });
205
204
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/auth/google/client/index.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useRef,\n useState,\n type ReactNode,\n} from \"react\";\n// Imported via the public specifier so we share the SAME context instance as\n// the consumer's edit primitives at runtime (see tsup `external` + tsconfig paths).\nimport { CmsAuthProvider } from \"@dalgoridim/headless-cms/client\";\n\n/**\n * Client provider for **Google Identity Services** sign-in — the Firebase-free\n * counterpart to `FirebaseAuthProvider`. It loads the GSI script, lets the user\n * sign in with Google, stashes the resulting ID token in a cookie for the\n * server `googleAuth` adapter to verify, and feeds the shared CMS auth context\n * so the edit primitives light up. Admin status is optimistic on the client\n * (via `adminEmails`); the server gate remains authoritative.\n */\n\nconst GSI_SRC = \"https://accounts.google.com/gsi/client\";\n\n/** Minimal slice of the Google Identity Services API we use. */\ninterface GsiCredentialResponse {\n credential: string;\n}\ninterface GsiButtonOptions {\n type?: \"standard\" | \"icon\";\n theme?: \"outline\" | \"filled_blue\" | \"filled_black\";\n size?: \"small\" | \"medium\" | \"large\";\n text?: \"signin_with\" | \"signup_with\" | \"continue_with\" | \"signin\";\n shape?: \"rectangular\" | \"pill\" | \"circle\" | \"square\";\n width?: number;\n}\ninterface GsiClient {\n accounts: {\n id: {\n initialize: (config: {\n client_id: string;\n callback: (res: GsiCredentialResponse) => void;\n auto_select?: boolean;\n cancel_on_tap_outside?: boolean;\n }) => void;\n prompt: () => void;\n renderButton: (el: HTMLElement, options?: GsiButtonOptions) => void;\n disableAutoSelect: () => void;\n };\n };\n}\n\ndeclare global {\n interface Window {\n google?: GsiClient;\n }\n}\n\nexport interface GoogleUser {\n sub: string;\n email?: string;\n name?: string;\n picture?: string;\n}\n\nexport interface GoogleAuthProviderProps {\n children: ReactNode;\n /** OAuth 2.0 Web client ID (from Google Cloud Console → Credentials). */\n clientId: string;\n /**\n * Optional admin allowlist for *optimistic* client-side `isAdmin`. The server\n * `googleAuth` adapter still enforces the real gate. Omit to treat any\n * successful sign-in as optimistically admin (server will correct via 401).\n */\n adminEmails?: string[];\n /** Cookie name for the ID token. Default `adminToken`. */\n cookieName?: string;\n /** Called when a 401 `{ logout: true }` response is intercepted. */\n onLogout?: () => void;\n}\n\nexport interface GoogleAuthContextValue {\n user: GoogleUser | null;\n isAdmin: boolean;\n isEditing: boolean;\n toggleEdit: () => void;\n /** True once the GSI script has loaded and the client is initialized. */\n ready: boolean;\n /** True if GSI failed to load within the timeout (blocked/offline). */\n failed: boolean;\n /** Trigger Google One Tap / sign-in prompt. */\n promptSignIn: () => void;\n /** Render the official Google button into `el` (most reliable trigger). */\n renderButton: (el: HTMLElement, options?: GsiButtonOptions) => void;\n logout: () => void;\n}\n\nconst GoogleAuthContext = createContext<GoogleAuthContextValue | undefined>(\n undefined,\n);\n\nfunction setCookie(name: string, value: string) {\n document.cookie = `${name}=${encodeURIComponent(value)}; path=/; samesite=lax`;\n}\nfunction deleteCookie(name: string) {\n document.cookie = `${name}=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT`;\n}\nfunction readCookie(name: string): string | null {\n for (const part of document.cookie.split(\";\")) {\n const [k, ...v] = part.trim().split(\"=\");\n if (k === name) return decodeURIComponent(v.join(\"=\"));\n }\n return null;\n}\n\n/** Decode a JWT payload client-side (no verification — the server does that). */\nfunction decodeJwt(token: string): (GoogleUser & { exp?: number }) | null {\n try {\n const payload = token.split(\".\")[1];\n const json = atob(payload.replace(/-/g, \"+\").replace(/_/g, \"/\"));\n const data = JSON.parse(json) as {\n sub: string;\n email?: string;\n name?: string;\n picture?: string;\n exp?: number;\n };\n return data;\n } catch {\n return null;\n }\n}\n\nexport function GoogleAuthProvider({\n children,\n clientId,\n adminEmails,\n cookieName = \"adminToken\",\n onLogout,\n}: GoogleAuthProviderProps) {\n const [user, setUser] = useState<GoogleUser | null>(null);\n const [isAdmin, setIsAdmin] = useState(false);\n const [isEditing, setIsEditing] = useState(false);\n const [ready, setReady] = useState(false);\n const [failed, setFailed] = useState(false);\n const allowed = useRef(\n (adminEmails ?? []).map((e) => e.trim().toLowerCase()),\n );\n\n const applyToken = useCallback(\n (token: string) => {\n const claims = decodeJwt(token);\n if (!claims) return;\n if (claims.exp && claims.exp * 1000 <= Date.now()) {\n deleteCookie(cookieName);\n return;\n }\n const email = claims.email?.toLowerCase();\n // Optimistic: if no allowlist supplied, trust the sign-in and let the\n // server correct a non-admin via the 401 interceptor below.\n const admin =\n allowed.current.length === 0\n ? true\n : !!email && allowed.current.includes(email);\n setCookie(cookieName, token);\n setUser({\n sub: claims.sub,\n email: claims.email,\n name: claims.name,\n picture: claims.picture,\n });\n setIsAdmin(admin);\n },\n [cookieName],\n );\n\n // Restore an existing session from the cookie on mount.\n useEffect(() => {\n const existing = readCookie(cookieName);\n if (existing) applyToken(existing);\n }, [cookieName, applyToken]);\n\n // Load the GSI script and initialize the client. Polling (rather than relying\n // on a `load` event) makes this robust against React's dev double-mount, a\n // cached/already-loaded script, and missed load events — any of which could\n // otherwise leave `ready` stuck false and the UI spinning forever.\n useEffect(() => {\n let cancelled = false;\n\n function tryInit(): boolean {\n if (cancelled || !window.google?.accounts?.id) return false;\n window.google.accounts.id.initialize({\n client_id: clientId,\n callback: (res) => applyToken(res.credential),\n auto_select: false,\n cancel_on_tap_outside: true,\n });\n setReady(true);\n return true;\n }\n\n if (tryInit()) return;\n\n // Ensure the script tag exists exactly once across mounts.\n if (!document.querySelector(`script[src=\"${GSI_SRC}\"]`)) {\n const script = document.createElement(\"script\");\n script.src = GSI_SRC;\n script.async = true;\n script.defer = true;\n document.head.appendChild(script);\n }\n\n const startedAt = Date.now();\n const timer = setInterval(() => {\n if (tryInit()) {\n clearInterval(timer);\n } else if (Date.now() - startedAt > 10_000) {\n // Blocked (ad/privacy extension) or offline — surface a failure so the\n // UI can show a fallback instead of an endless spinner.\n clearInterval(timer);\n if (!cancelled) setFailed(true);\n }\n }, 120);\n\n return () => {\n cancelled = true;\n clearInterval(timer);\n };\n }, [clientId, applyToken]);\n\n // Intercept admin 401s so an expired/forbidden session forces sign-out.\n useEffect(() => {\n const originalFetch = window.fetch;\n window.fetch = async (...args: Parameters<typeof fetch>) => {\n const response = await originalFetch(...args);\n if (response.status === 401) {\n try {\n const data = await response.clone().json();\n if (data?.logout) doLogout();\n } catch {\n /* not a JSON body — ignore */\n }\n }\n return response;\n };\n return () => {\n window.fetch = originalFetch;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n function doLogout() {\n window.google?.accounts.id.disableAutoSelect();\n deleteCookie(cookieName);\n setUser(null);\n setIsAdmin(false);\n setIsEditing(false);\n onLogout?.();\n }\n\n const promptSignIn = useCallback(() => {\n if (!ready) return;\n window.google?.accounts.id.prompt();\n }, [ready]);\n\n const renderButton = useCallback(\n (el: HTMLElement, options?: GsiButtonOptions) => {\n if (!ready) return;\n window.google?.accounts.id.renderButton(el, options);\n },\n [ready],\n );\n\n const toggleEdit = useCallback(() => setIsEditing((p) => !p), []);\n\n return (\n <GoogleAuthContext.Provider\n value={{\n user,\n isAdmin,\n isEditing,\n toggleEdit,\n ready,\n failed,\n promptSignIn,\n renderButton,\n logout: doLogout,\n }}\n >\n <CmsAuthProvider value={{ isAdmin, isEditing, toggleEdit }}>\n {children}\n </CmsAuthProvider>\n </GoogleAuthContext.Provider>\n );\n}\n\n/** Google auth API (user/login/logout) for login pages and toolbars. */\nexport function useGoogleAuth(): GoogleAuthContextValue {\n const ctx = useContext(GoogleAuthContext);\n if (!ctx) {\n throw new Error(\"useGoogleAuth must be used within a GoogleAuthProvider\");\n }\n return ctx;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../../src/auth/google/client/index.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useRef,\n useState,\n type ReactNode,\n} from \"react\";\nimport {\n GoogleOAuthProvider,\n GoogleLogin,\n googleLogout,\n type CredentialResponse,\n} from \"@react-oauth/google\";\n// Imported via the public specifier so we share the SAME context instance as\n// the consumer's edit primitives at runtime (see tsup `external` + tsconfig paths).\nimport { CmsAuthProvider } from \"@dalgoridim/headless-cms/client\";\n\n/**\n * Client provider for **Google sign-in**, built on `@react-oauth/google` (the\n * official React wrapper for Google Identity Services). It handles the GSI\n * script + button rendering robustly — including remounts, so the button shows\n * reliably every time a sign-in dialog opens. The resulting ID token is stashed\n * in a cookie for the server `googleAuth` adapter to verify; admin status is\n * optimistic on the client (via `adminEmails`), with the server gate\n * authoritative.\n *\n * Render `<GoogleSignInButton />` anywhere inside the provider to show the\n * official Google button.\n */\n\nexport interface GoogleUser {\n sub: string;\n email?: string;\n name?: string;\n picture?: string;\n}\n\nexport interface GoogleAuthProviderProps {\n children: ReactNode;\n /** OAuth 2.0 Web client ID (from Google Cloud Console → Credentials). */\n clientId: string;\n /**\n * Optional admin allowlist for *optimistic* client-side `isAdmin`. The server\n * `googleAuth` adapter still enforces the real gate. Omit to treat any\n * successful sign-in as optimistically admin (server will correct via 401).\n */\n adminEmails?: string[];\n /** Cookie name for the ID token. Default `adminToken`. */\n cookieName?: string;\n /** Called when a 401 `{ logout: true }` response is intercepted. */\n onLogout?: () => void;\n}\n\nexport interface GoogleAuthContextValue {\n user: GoogleUser | null;\n isAdmin: boolean;\n isEditing: boolean;\n toggleEdit: () => void;\n logout: () => void;\n /** Feed a Google ID token credential (used by `GoogleSignInButton`). */\n applyCredential: (credential: string) => void;\n}\n\nconst GoogleAuthContext = createContext<GoogleAuthContextValue | undefined>(\n undefined,\n);\n\nfunction setCookie(name: string, value: string) {\n document.cookie = `${name}=${encodeURIComponent(value)}; path=/; samesite=lax`;\n}\nfunction deleteCookie(name: string) {\n document.cookie = `${name}=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT`;\n}\nfunction readCookie(name: string): string | null {\n for (const part of document.cookie.split(\";\")) {\n const [k, ...v] = part.trim().split(\"=\");\n if (k === name) return decodeURIComponent(v.join(\"=\"));\n }\n return null;\n}\n\n/** Decode a JWT payload client-side (no verification — the server does that). */\nfunction decodeJwt(token: string): (GoogleUser & { exp?: number }) | null {\n try {\n const payload = token.split(\".\")[1];\n const json = atob(payload.replace(/-/g, \"+\").replace(/_/g, \"/\"));\n return JSON.parse(json);\n } catch {\n return null;\n }\n}\n\nfunction AuthState({\n children,\n adminEmails,\n cookieName = \"adminToken\",\n onLogout,\n}: Omit<GoogleAuthProviderProps, \"clientId\">) {\n const [user, setUser] = useState<GoogleUser | null>(null);\n const [isAdmin, setIsAdmin] = useState(false);\n const [isEditing, setIsEditing] = useState(false);\n const allowed = useRef(\n (adminEmails ?? []).map((e) => e.trim().toLowerCase()),\n );\n\n const applyCredential = useCallback(\n (token: string) => {\n const claims = decodeJwt(token);\n if (!claims) return;\n if (claims.exp && claims.exp * 1000 <= Date.now()) {\n deleteCookie(cookieName);\n return;\n }\n const email = claims.email?.toLowerCase();\n // Optimistic: if no allowlist supplied, trust the sign-in and let the\n // server correct a non-admin via the 401 interceptor below.\n const admin =\n allowed.current.length === 0\n ? true\n : !!email && allowed.current.includes(email);\n setCookie(cookieName, token);\n setUser({\n sub: claims.sub,\n email: claims.email,\n name: claims.name,\n picture: claims.picture,\n });\n setIsAdmin(admin);\n },\n [cookieName],\n );\n\n // Restore an existing session from the cookie on mount.\n useEffect(() => {\n const existing = readCookie(cookieName);\n if (existing) applyCredential(existing);\n }, [cookieName, applyCredential]);\n\n const logout = useCallback(() => {\n googleLogout();\n deleteCookie(cookieName);\n setUser(null);\n setIsAdmin(false);\n setIsEditing(false);\n onLogout?.();\n }, [cookieName, onLogout]);\n\n // Intercept admin 401s so an expired/forbidden session forces sign-out.\n useEffect(() => {\n const originalFetch = window.fetch;\n window.fetch = async (...args: Parameters<typeof fetch>) => {\n const response = await originalFetch(...args);\n if (response.status === 401) {\n try {\n const data = await response.clone().json();\n if (data?.logout) logout();\n } catch {\n /* not a JSON body — ignore */\n }\n }\n return response;\n };\n return () => {\n window.fetch = originalFetch;\n };\n }, [logout]);\n\n const toggleEdit = useCallback(() => setIsEditing((p) => !p), []);\n\n return (\n <GoogleAuthContext.Provider\n value={{ user, isAdmin, isEditing, toggleEdit, logout, applyCredential }}\n >\n <CmsAuthProvider value={{ isAdmin, isEditing, toggleEdit }}>\n {children}\n </CmsAuthProvider>\n </GoogleAuthContext.Provider>\n );\n}\n\nexport function GoogleAuthProvider({\n children,\n clientId,\n ...rest\n}: GoogleAuthProviderProps) {\n return (\n <GoogleOAuthProvider clientId={clientId}>\n <AuthState {...rest}>{children}</AuthState>\n </GoogleOAuthProvider>\n );\n}\n\nexport interface GoogleSignInButtonProps {\n /** Forwarded to the underlying `<GoogleLogin>` (theme, shape, size, text…). */\n theme?: \"outline\" | \"filled_blue\" | \"filled_black\";\n shape?: \"rectangular\" | \"pill\" | \"circle\" | \"square\";\n size?: \"small\" | \"medium\" | \"large\";\n text?: \"signin_with\" | \"signup_with\" | \"continue_with\" | \"signin\";\n onError?: () => void;\n}\n\n/** The official Google sign-in button. Must be rendered inside a provider. */\nexport function GoogleSignInButton({\n theme = \"filled_black\",\n shape = \"pill\",\n size = \"large\",\n text = \"continue_with\",\n onError,\n}: GoogleSignInButtonProps) {\n const { applyCredential } = useGoogleAuth();\n return (\n <GoogleLogin\n onSuccess={(res: CredentialResponse) => {\n if (res.credential) applyCredential(res.credential);\n }}\n onError={onError}\n theme={theme}\n shape={shape}\n size={size}\n text={text}\n />\n );\n}\n\n/** Google auth API (user/logout) for login pages and toolbars. */\nexport function useGoogleAuth(): GoogleAuthContextValue {\n const ctx = useContext(GoogleAuthContext);\n if (!ctx) {\n throw new Error(\"useGoogleAuth must be used within a GoogleAuthProvider\");\n }\n return ctx;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAEA,mBAQO;AACP,oBAKO;AAGP,oBAAgC;AA8J1B;AA9GN,IAAM,wBAAoB;AAAA,EACxB;AACF;AAEA,SAAS,UAAU,MAAc,OAAe;AAC9C,WAAS,SAAS,GAAG,IAAI,IAAI,mBAAmB,KAAK,CAAC;AACxD;AACA,SAAS,aAAa,MAAc;AAClC,WAAS,SAAS,GAAG,IAAI;AAC3B;AACA,SAAS,WAAW,MAA6B;AAC/C,aAAW,QAAQ,SAAS,OAAO,MAAM,GAAG,GAAG;AAC7C,UAAM,CAAC,GAAG,GAAG,CAAC,IAAI,KAAK,KAAK,EAAE,MAAM,GAAG;AACvC,QAAI,MAAM,KAAM,QAAO,mBAAmB,EAAE,KAAK,GAAG,CAAC;AAAA,EACvD;AACA,SAAO;AACT;AAGA,SAAS,UAAU,OAAuD;AACxE,MAAI;AACF,UAAM,UAAU,MAAM,MAAM,GAAG,EAAE,CAAC;AAClC,UAAM,OAAO,KAAK,QAAQ,QAAQ,MAAM,GAAG,EAAE,QAAQ,MAAM,GAAG,CAAC;AAC/D,WAAO,KAAK,MAAM,IAAI;AAAA,EACxB,SAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,UAAU;AAAA,EACjB;AAAA,EACA;AAAA,EACA,aAAa;AAAA,EACb;AACF,GAA8C;AAC5C,QAAM,CAAC,MAAM,OAAO,QAAI,uBAA4B,IAAI;AACxD,QAAM,CAAC,SAAS,UAAU,QAAI,uBAAS,KAAK;AAC5C,QAAM,CAAC,WAAW,YAAY,QAAI,uBAAS,KAAK;AAChD,QAAM,cAAU;AAAA,KACb,oCAAe,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,YAAY,CAAC;AAAA,EACvD;AAEA,QAAM,sBAAkB;AAAA,IACtB,CAAC,UAAkB;AA9GvB;AA+GM,YAAM,SAAS,UAAU,KAAK;AAC9B,UAAI,CAAC,OAAQ;AACb,UAAI,OAAO,OAAO,OAAO,MAAM,OAAQ,KAAK,IAAI,GAAG;AACjD,qBAAa,UAAU;AACvB;AAAA,MACF;AACA,YAAM,SAAQ,YAAO,UAAP,mBAAc;AAG5B,YAAM,QACJ,QAAQ,QAAQ,WAAW,IACvB,OACA,CAAC,CAAC,SAAS,QAAQ,QAAQ,SAAS,KAAK;AAC/C,gBAAU,YAAY,KAAK;AAC3B,cAAQ;AAAA,QACN,KAAK,OAAO;AAAA,QACZ,OAAO,OAAO;AAAA,QACd,MAAM,OAAO;AAAA,QACb,SAAS,OAAO;AAAA,MAClB,CAAC;AACD,iBAAW,KAAK;AAAA,IAClB;AAAA,IACA,CAAC,UAAU;AAAA,EACb;AAGA,8BAAU,MAAM;AACd,UAAM,WAAW,WAAW,UAAU;AACtC,QAAI,SAAU,iBAAgB,QAAQ;AAAA,EACxC,GAAG,CAAC,YAAY,eAAe,CAAC;AAEhC,QAAM,aAAS,0BAAY,MAAM;AAC/B,oCAAa;AACb,iBAAa,UAAU;AACvB,YAAQ,IAAI;AACZ,eAAW,KAAK;AAChB,iBAAa,KAAK;AAClB;AAAA,EACF,GAAG,CAAC,YAAY,QAAQ,CAAC;AAGzB,8BAAU,MAAM;AACd,UAAM,gBAAgB,OAAO;AAC7B,WAAO,QAAQ,UAAU,SAAmC;AAC1D,YAAM,WAAW,MAAM,cAAc,GAAG,IAAI;AAC5C,UAAI,SAAS,WAAW,KAAK;AAC3B,YAAI;AACF,gBAAM,OAAO,MAAM,SAAS,MAAM,EAAE,KAAK;AACzC,cAAI,6BAAM,OAAQ,QAAO;AAAA,QAC3B,SAAQ;AAAA,QAER;AAAA,MACF;AACA,aAAO;AAAA,IACT;AACA,WAAO,MAAM;AACX,aAAO,QAAQ;AAAA,IACjB;AAAA,EACF,GAAG,CAAC,MAAM,CAAC;AAEX,QAAM,iBAAa,0BAAY,MAAM,aAAa,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC;AAEhE,SACE;AAAA,IAAC,kBAAkB;AAAA,IAAlB;AAAA,MACC,OAAO,EAAE,MAAM,SAAS,WAAW,YAAY,QAAQ,gBAAgB;AAAA,MAEvE,sDAAC,iCAAgB,OAAO,EAAE,SAAS,WAAW,WAAW,GACtD,UACH;AAAA;AAAA,EACF;AAEJ;AAEO,SAAS,mBAAmB,IAIP;AAJO,eACjC;AAAA;AAAA,IACA;AAAA,EA1LF,IAwLmC,IAG9B,iBAH8B,IAG9B;AAAA,IAFH;AAAA,IACA;AAAA;AAGA,SACE,4CAAC,qCAAoB,UACnB,sDAAC,4CAAc,OAAd,EAAqB,WAAS,GACjC;AAEJ;AAYO,SAAS,mBAAmB;AAAA,EACjC,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,OAAO;AAAA,EACP;AACF,GAA4B;AAC1B,QAAM,EAAE,gBAAgB,IAAI,cAAc;AAC1C,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW,CAAC,QAA4B;AACtC,YAAI,IAAI,WAAY,iBAAgB,IAAI,UAAU;AAAA,MACpD;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,EACF;AAEJ;AAGO,SAAS,gBAAwC;AACtD,QAAM,UAAM,yBAAW,iBAAiB;AACxC,MAAI,CAAC,KAAK;AACR,UAAM,IAAI,MAAM,wDAAwD;AAAA,EAC1E;AACA,SAAO;AACT;","names":[]}
@@ -1,38 +1,18 @@
1
- import * as React from 'react';
1
+ import * as react from 'react';
2
2
  import { ReactNode } from 'react';
3
3
 
4
- /** Minimal slice of the Google Identity Services API we use. */
5
- interface GsiCredentialResponse {
6
- credential: string;
7
- }
8
- interface GsiButtonOptions {
9
- type?: "standard" | "icon";
10
- theme?: "outline" | "filled_blue" | "filled_black";
11
- size?: "small" | "medium" | "large";
12
- text?: "signin_with" | "signup_with" | "continue_with" | "signin";
13
- shape?: "rectangular" | "pill" | "circle" | "square";
14
- width?: number;
15
- }
16
- interface GsiClient {
17
- accounts: {
18
- id: {
19
- initialize: (config: {
20
- client_id: string;
21
- callback: (res: GsiCredentialResponse) => void;
22
- auto_select?: boolean;
23
- cancel_on_tap_outside?: boolean;
24
- }) => void;
25
- prompt: () => void;
26
- renderButton: (el: HTMLElement, options?: GsiButtonOptions) => void;
27
- disableAutoSelect: () => void;
28
- };
29
- };
30
- }
31
- declare global {
32
- interface Window {
33
- google?: GsiClient;
34
- }
35
- }
4
+ /**
5
+ * Client provider for **Google sign-in**, built on `@react-oauth/google` (the
6
+ * official React wrapper for Google Identity Services). It handles the GSI
7
+ * script + button rendering robustly — including remounts, so the button shows
8
+ * reliably every time a sign-in dialog opens. The resulting ID token is stashed
9
+ * in a cookie for the server `googleAuth` adapter to verify; admin status is
10
+ * optimistic on the client (via `adminEmails`), with the server gate
11
+ * authoritative.
12
+ *
13
+ * Render `<GoogleSignInButton />` anywhere inside the provider to show the
14
+ * official Google button.
15
+ */
36
16
  interface GoogleUser {
37
17
  sub: string;
38
18
  email?: string;
@@ -59,18 +39,22 @@ interface GoogleAuthContextValue {
59
39
  isAdmin: boolean;
60
40
  isEditing: boolean;
61
41
  toggleEdit: () => void;
62
- /** True once the GSI script has loaded and the client is initialized. */
63
- ready: boolean;
64
- /** True if GSI failed to load within the timeout (blocked/offline). */
65
- failed: boolean;
66
- /** Trigger Google One Tap / sign-in prompt. */
67
- promptSignIn: () => void;
68
- /** Render the official Google button into `el` (most reliable trigger). */
69
- renderButton: (el: HTMLElement, options?: GsiButtonOptions) => void;
70
42
  logout: () => void;
43
+ /** Feed a Google ID token credential (used by `GoogleSignInButton`). */
44
+ applyCredential: (credential: string) => void;
45
+ }
46
+ declare function GoogleAuthProvider({ children, clientId, ...rest }: GoogleAuthProviderProps): react.JSX.Element;
47
+ interface GoogleSignInButtonProps {
48
+ /** Forwarded to the underlying `<GoogleLogin>` (theme, shape, size, text…). */
49
+ theme?: "outline" | "filled_blue" | "filled_black";
50
+ shape?: "rectangular" | "pill" | "circle" | "square";
51
+ size?: "small" | "medium" | "large";
52
+ text?: "signin_with" | "signup_with" | "continue_with" | "signin";
53
+ onError?: () => void;
71
54
  }
72
- declare function GoogleAuthProvider({ children, clientId, adminEmails, cookieName, onLogout, }: GoogleAuthProviderProps): React.JSX.Element;
73
- /** Google auth API (user/login/logout) for login pages and toolbars. */
55
+ /** The official Google sign-in button. Must be rendered inside a provider. */
56
+ declare function GoogleSignInButton({ theme, shape, size, text, onError, }: GoogleSignInButtonProps): react.JSX.Element;
57
+ /** Google auth API (user/logout) for login pages and toolbars. */
74
58
  declare function useGoogleAuth(): GoogleAuthContextValue;
75
59
 
76
- export { type GoogleAuthContextValue, GoogleAuthProvider, type GoogleAuthProviderProps, type GoogleUser, useGoogleAuth };
60
+ export { type GoogleAuthContextValue, GoogleAuthProvider, type GoogleAuthProviderProps, GoogleSignInButton, type GoogleSignInButtonProps, type GoogleUser, useGoogleAuth };
@@ -1,38 +1,18 @@
1
- import * as React from 'react';
1
+ import * as react from 'react';
2
2
  import { ReactNode } from 'react';
3
3
 
4
- /** Minimal slice of the Google Identity Services API we use. */
5
- interface GsiCredentialResponse {
6
- credential: string;
7
- }
8
- interface GsiButtonOptions {
9
- type?: "standard" | "icon";
10
- theme?: "outline" | "filled_blue" | "filled_black";
11
- size?: "small" | "medium" | "large";
12
- text?: "signin_with" | "signup_with" | "continue_with" | "signin";
13
- shape?: "rectangular" | "pill" | "circle" | "square";
14
- width?: number;
15
- }
16
- interface GsiClient {
17
- accounts: {
18
- id: {
19
- initialize: (config: {
20
- client_id: string;
21
- callback: (res: GsiCredentialResponse) => void;
22
- auto_select?: boolean;
23
- cancel_on_tap_outside?: boolean;
24
- }) => void;
25
- prompt: () => void;
26
- renderButton: (el: HTMLElement, options?: GsiButtonOptions) => void;
27
- disableAutoSelect: () => void;
28
- };
29
- };
30
- }
31
- declare global {
32
- interface Window {
33
- google?: GsiClient;
34
- }
35
- }
4
+ /**
5
+ * Client provider for **Google sign-in**, built on `@react-oauth/google` (the
6
+ * official React wrapper for Google Identity Services). It handles the GSI
7
+ * script + button rendering robustly — including remounts, so the button shows
8
+ * reliably every time a sign-in dialog opens. The resulting ID token is stashed
9
+ * in a cookie for the server `googleAuth` adapter to verify; admin status is
10
+ * optimistic on the client (via `adminEmails`), with the server gate
11
+ * authoritative.
12
+ *
13
+ * Render `<GoogleSignInButton />` anywhere inside the provider to show the
14
+ * official Google button.
15
+ */
36
16
  interface GoogleUser {
37
17
  sub: string;
38
18
  email?: string;
@@ -59,18 +39,22 @@ interface GoogleAuthContextValue {
59
39
  isAdmin: boolean;
60
40
  isEditing: boolean;
61
41
  toggleEdit: () => void;
62
- /** True once the GSI script has loaded and the client is initialized. */
63
- ready: boolean;
64
- /** True if GSI failed to load within the timeout (blocked/offline). */
65
- failed: boolean;
66
- /** Trigger Google One Tap / sign-in prompt. */
67
- promptSignIn: () => void;
68
- /** Render the official Google button into `el` (most reliable trigger). */
69
- renderButton: (el: HTMLElement, options?: GsiButtonOptions) => void;
70
42
  logout: () => void;
43
+ /** Feed a Google ID token credential (used by `GoogleSignInButton`). */
44
+ applyCredential: (credential: string) => void;
45
+ }
46
+ declare function GoogleAuthProvider({ children, clientId, ...rest }: GoogleAuthProviderProps): react.JSX.Element;
47
+ interface GoogleSignInButtonProps {
48
+ /** Forwarded to the underlying `<GoogleLogin>` (theme, shape, size, text…). */
49
+ theme?: "outline" | "filled_blue" | "filled_black";
50
+ shape?: "rectangular" | "pill" | "circle" | "square";
51
+ size?: "small" | "medium" | "large";
52
+ text?: "signin_with" | "signup_with" | "continue_with" | "signin";
53
+ onError?: () => void;
71
54
  }
72
- declare function GoogleAuthProvider({ children, clientId, adminEmails, cookieName, onLogout, }: GoogleAuthProviderProps): React.JSX.Element;
73
- /** Google auth API (user/login/logout) for login pages and toolbars. */
55
+ /** The official Google sign-in button. Must be rendered inside a provider. */
56
+ declare function GoogleSignInButton({ theme, shape, size, text, onError, }: GoogleSignInButtonProps): react.JSX.Element;
57
+ /** Google auth API (user/logout) for login pages and toolbars. */
74
58
  declare function useGoogleAuth(): GoogleAuthContextValue;
75
59
 
76
- export { type GoogleAuthContextValue, GoogleAuthProvider, type GoogleAuthProviderProps, type GoogleUser, useGoogleAuth };
60
+ export { type GoogleAuthContextValue, GoogleAuthProvider, type GoogleAuthProviderProps, GoogleSignInButton, type GoogleSignInButtonProps, type GoogleUser, useGoogleAuth };
@@ -1,4 +1,35 @@
1
1
  "use client";
2
+ var __defProp = Object.defineProperty;
3
+ var __defProps = Object.defineProperties;
4
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
5
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
8
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
9
+ var __spreadValues = (a, b) => {
10
+ for (var prop in b || (b = {}))
11
+ if (__hasOwnProp.call(b, prop))
12
+ __defNormalProp(a, prop, b[prop]);
13
+ if (__getOwnPropSymbols)
14
+ for (var prop of __getOwnPropSymbols(b)) {
15
+ if (__propIsEnum.call(b, prop))
16
+ __defNormalProp(a, prop, b[prop]);
17
+ }
18
+ return a;
19
+ };
20
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
21
+ var __objRest = (source, exclude) => {
22
+ var target = {};
23
+ for (var prop in source)
24
+ if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
25
+ target[prop] = source[prop];
26
+ if (source != null && __getOwnPropSymbols)
27
+ for (var prop of __getOwnPropSymbols(source)) {
28
+ if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
29
+ target[prop] = source[prop];
30
+ }
31
+ return target;
32
+ };
2
33
 
3
34
  // src/auth/google/client/index.tsx
4
35
  import {
@@ -9,9 +40,13 @@ import {
9
40
  useRef,
10
41
  useState
11
42
  } from "react";
43
+ import {
44
+ GoogleOAuthProvider,
45
+ GoogleLogin,
46
+ googleLogout
47
+ } from "@react-oauth/google";
12
48
  import { CmsAuthProvider } from "@dalgoridim/headless-cms/client";
13
49
  import { jsx } from "react/jsx-runtime";
14
- var GSI_SRC = "https://accounts.google.com/gsi/client";
15
50
  var GoogleAuthContext = createContext(
16
51
  void 0
17
52
  );
@@ -32,15 +67,13 @@ function decodeJwt(token) {
32
67
  try {
33
68
  const payload = token.split(".")[1];
34
69
  const json = atob(payload.replace(/-/g, "+").replace(/_/g, "/"));
35
- const data = JSON.parse(json);
36
- return data;
70
+ return JSON.parse(json);
37
71
  } catch (e) {
38
72
  return null;
39
73
  }
40
74
  }
41
- function GoogleAuthProvider({
75
+ function AuthState({
42
76
  children,
43
- clientId,
44
77
  adminEmails,
45
78
  cookieName = "adminToken",
46
79
  onLogout
@@ -48,12 +81,10 @@ function GoogleAuthProvider({
48
81
  const [user, setUser] = useState(null);
49
82
  const [isAdmin, setIsAdmin] = useState(false);
50
83
  const [isEditing, setIsEditing] = useState(false);
51
- const [ready, setReady] = useState(false);
52
- const [failed, setFailed] = useState(false);
53
84
  const allowed = useRef(
54
85
  (adminEmails != null ? adminEmails : []).map((e) => e.trim().toLowerCase())
55
86
  );
56
- const applyToken = useCallback(
87
+ const applyCredential = useCallback(
57
88
  (token) => {
58
89
  var _a;
59
90
  const claims = decodeJwt(token);
@@ -77,44 +108,16 @@ function GoogleAuthProvider({
77
108
  );
78
109
  useEffect(() => {
79
110
  const existing = readCookie(cookieName);
80
- if (existing) applyToken(existing);
81
- }, [cookieName, applyToken]);
82
- useEffect(() => {
83
- let cancelled = false;
84
- function tryInit() {
85
- var _a, _b;
86
- if (cancelled || !((_b = (_a = window.google) == null ? void 0 : _a.accounts) == null ? void 0 : _b.id)) return false;
87
- window.google.accounts.id.initialize({
88
- client_id: clientId,
89
- callback: (res) => applyToken(res.credential),
90
- auto_select: false,
91
- cancel_on_tap_outside: true
92
- });
93
- setReady(true);
94
- return true;
95
- }
96
- if (tryInit()) return;
97
- if (!document.querySelector(`script[src="${GSI_SRC}"]`)) {
98
- const script = document.createElement("script");
99
- script.src = GSI_SRC;
100
- script.async = true;
101
- script.defer = true;
102
- document.head.appendChild(script);
103
- }
104
- const startedAt = Date.now();
105
- const timer = setInterval(() => {
106
- if (tryInit()) {
107
- clearInterval(timer);
108
- } else if (Date.now() - startedAt > 1e4) {
109
- clearInterval(timer);
110
- if (!cancelled) setFailed(true);
111
- }
112
- }, 120);
113
- return () => {
114
- cancelled = true;
115
- clearInterval(timer);
116
- };
117
- }, [clientId, applyToken]);
111
+ if (existing) applyCredential(existing);
112
+ }, [cookieName, applyCredential]);
113
+ const logout = useCallback(() => {
114
+ googleLogout();
115
+ deleteCookie(cookieName);
116
+ setUser(null);
117
+ setIsAdmin(false);
118
+ setIsEditing(false);
119
+ onLogout == null ? void 0 : onLogout();
120
+ }, [cookieName, onLogout]);
118
121
  useEffect(() => {
119
122
  const originalFetch = window.fetch;
120
123
  window.fetch = async (...args) => {
@@ -122,7 +125,7 @@ function GoogleAuthProvider({
122
125
  if (response.status === 401) {
123
126
  try {
124
127
  const data = await response.clone().json();
125
- if (data == null ? void 0 : data.logout) doLogout();
128
+ if (data == null ? void 0 : data.logout) logout();
126
129
  } catch (e) {
127
130
  }
128
131
  }
@@ -131,48 +134,48 @@ function GoogleAuthProvider({
131
134
  return () => {
132
135
  window.fetch = originalFetch;
133
136
  };
134
- }, []);
135
- function doLogout() {
136
- var _a;
137
- (_a = window.google) == null ? void 0 : _a.accounts.id.disableAutoSelect();
138
- deleteCookie(cookieName);
139
- setUser(null);
140
- setIsAdmin(false);
141
- setIsEditing(false);
142
- onLogout == null ? void 0 : onLogout();
143
- }
144
- const promptSignIn = useCallback(() => {
145
- var _a;
146
- if (!ready) return;
147
- (_a = window.google) == null ? void 0 : _a.accounts.id.prompt();
148
- }, [ready]);
149
- const renderButton = useCallback(
150
- (el, options) => {
151
- var _a;
152
- if (!ready) return;
153
- (_a = window.google) == null ? void 0 : _a.accounts.id.renderButton(el, options);
154
- },
155
- [ready]
156
- );
137
+ }, [logout]);
157
138
  const toggleEdit = useCallback(() => setIsEditing((p) => !p), []);
158
139
  return /* @__PURE__ */ jsx(
159
140
  GoogleAuthContext.Provider,
160
141
  {
161
- value: {
162
- user,
163
- isAdmin,
164
- isEditing,
165
- toggleEdit,
166
- ready,
167
- failed,
168
- promptSignIn,
169
- renderButton,
170
- logout: doLogout
171
- },
142
+ value: { user, isAdmin, isEditing, toggleEdit, logout, applyCredential },
172
143
  children: /* @__PURE__ */ jsx(CmsAuthProvider, { value: { isAdmin, isEditing, toggleEdit }, children })
173
144
  }
174
145
  );
175
146
  }
147
+ function GoogleAuthProvider(_a) {
148
+ var _b = _a, {
149
+ children,
150
+ clientId
151
+ } = _b, rest = __objRest(_b, [
152
+ "children",
153
+ "clientId"
154
+ ]);
155
+ return /* @__PURE__ */ jsx(GoogleOAuthProvider, { clientId, children: /* @__PURE__ */ jsx(AuthState, __spreadProps(__spreadValues({}, rest), { children })) });
156
+ }
157
+ function GoogleSignInButton({
158
+ theme = "filled_black",
159
+ shape = "pill",
160
+ size = "large",
161
+ text = "continue_with",
162
+ onError
163
+ }) {
164
+ const { applyCredential } = useGoogleAuth();
165
+ return /* @__PURE__ */ jsx(
166
+ GoogleLogin,
167
+ {
168
+ onSuccess: (res) => {
169
+ if (res.credential) applyCredential(res.credential);
170
+ },
171
+ onError,
172
+ theme,
173
+ shape,
174
+ size,
175
+ text
176
+ }
177
+ );
178
+ }
176
179
  function useGoogleAuth() {
177
180
  const ctx = useContext(GoogleAuthContext);
178
181
  if (!ctx) {
@@ -182,6 +185,7 @@ function useGoogleAuth() {
182
185
  }
183
186
  export {
184
187
  GoogleAuthProvider,
188
+ GoogleSignInButton,
185
189
  useGoogleAuth
186
190
  };
187
191
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/auth/google/client/index.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useRef,\n useState,\n type ReactNode,\n} from \"react\";\n// Imported via the public specifier so we share the SAME context instance as\n// the consumer's edit primitives at runtime (see tsup `external` + tsconfig paths).\nimport { CmsAuthProvider } from \"@dalgoridim/headless-cms/client\";\n\n/**\n * Client provider for **Google Identity Services** sign-in — the Firebase-free\n * counterpart to `FirebaseAuthProvider`. It loads the GSI script, lets the user\n * sign in with Google, stashes the resulting ID token in a cookie for the\n * server `googleAuth` adapter to verify, and feeds the shared CMS auth context\n * so the edit primitives light up. Admin status is optimistic on the client\n * (via `adminEmails`); the server gate remains authoritative.\n */\n\nconst GSI_SRC = \"https://accounts.google.com/gsi/client\";\n\n/** Minimal slice of the Google Identity Services API we use. */\ninterface GsiCredentialResponse {\n credential: string;\n}\ninterface GsiButtonOptions {\n type?: \"standard\" | \"icon\";\n theme?: \"outline\" | \"filled_blue\" | \"filled_black\";\n size?: \"small\" | \"medium\" | \"large\";\n text?: \"signin_with\" | \"signup_with\" | \"continue_with\" | \"signin\";\n shape?: \"rectangular\" | \"pill\" | \"circle\" | \"square\";\n width?: number;\n}\ninterface GsiClient {\n accounts: {\n id: {\n initialize: (config: {\n client_id: string;\n callback: (res: GsiCredentialResponse) => void;\n auto_select?: boolean;\n cancel_on_tap_outside?: boolean;\n }) => void;\n prompt: () => void;\n renderButton: (el: HTMLElement, options?: GsiButtonOptions) => void;\n disableAutoSelect: () => void;\n };\n };\n}\n\ndeclare global {\n interface Window {\n google?: GsiClient;\n }\n}\n\nexport interface GoogleUser {\n sub: string;\n email?: string;\n name?: string;\n picture?: string;\n}\n\nexport interface GoogleAuthProviderProps {\n children: ReactNode;\n /** OAuth 2.0 Web client ID (from Google Cloud Console → Credentials). */\n clientId: string;\n /**\n * Optional admin allowlist for *optimistic* client-side `isAdmin`. The server\n * `googleAuth` adapter still enforces the real gate. Omit to treat any\n * successful sign-in as optimistically admin (server will correct via 401).\n */\n adminEmails?: string[];\n /** Cookie name for the ID token. Default `adminToken`. */\n cookieName?: string;\n /** Called when a 401 `{ logout: true }` response is intercepted. */\n onLogout?: () => void;\n}\n\nexport interface GoogleAuthContextValue {\n user: GoogleUser | null;\n isAdmin: boolean;\n isEditing: boolean;\n toggleEdit: () => void;\n /** True once the GSI script has loaded and the client is initialized. */\n ready: boolean;\n /** True if GSI failed to load within the timeout (blocked/offline). */\n failed: boolean;\n /** Trigger Google One Tap / sign-in prompt. */\n promptSignIn: () => void;\n /** Render the official Google button into `el` (most reliable trigger). */\n renderButton: (el: HTMLElement, options?: GsiButtonOptions) => void;\n logout: () => void;\n}\n\nconst GoogleAuthContext = createContext<GoogleAuthContextValue | undefined>(\n undefined,\n);\n\nfunction setCookie(name: string, value: string) {\n document.cookie = `${name}=${encodeURIComponent(value)}; path=/; samesite=lax`;\n}\nfunction deleteCookie(name: string) {\n document.cookie = `${name}=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT`;\n}\nfunction readCookie(name: string): string | null {\n for (const part of document.cookie.split(\";\")) {\n const [k, ...v] = part.trim().split(\"=\");\n if (k === name) return decodeURIComponent(v.join(\"=\"));\n }\n return null;\n}\n\n/** Decode a JWT payload client-side (no verification — the server does that). */\nfunction decodeJwt(token: string): (GoogleUser & { exp?: number }) | null {\n try {\n const payload = token.split(\".\")[1];\n const json = atob(payload.replace(/-/g, \"+\").replace(/_/g, \"/\"));\n const data = JSON.parse(json) as {\n sub: string;\n email?: string;\n name?: string;\n picture?: string;\n exp?: number;\n };\n return data;\n } catch {\n return null;\n }\n}\n\nexport function GoogleAuthProvider({\n children,\n clientId,\n adminEmails,\n cookieName = \"adminToken\",\n onLogout,\n}: GoogleAuthProviderProps) {\n const [user, setUser] = useState<GoogleUser | null>(null);\n const [isAdmin, setIsAdmin] = useState(false);\n const [isEditing, setIsEditing] = useState(false);\n const [ready, setReady] = useState(false);\n const [failed, setFailed] = useState(false);\n const allowed = useRef(\n (adminEmails ?? []).map((e) => e.trim().toLowerCase()),\n );\n\n const applyToken = useCallback(\n (token: string) => {\n const claims = decodeJwt(token);\n if (!claims) return;\n if (claims.exp && claims.exp * 1000 <= Date.now()) {\n deleteCookie(cookieName);\n return;\n }\n const email = claims.email?.toLowerCase();\n // Optimistic: if no allowlist supplied, trust the sign-in and let the\n // server correct a non-admin via the 401 interceptor below.\n const admin =\n allowed.current.length === 0\n ? true\n : !!email && allowed.current.includes(email);\n setCookie(cookieName, token);\n setUser({\n sub: claims.sub,\n email: claims.email,\n name: claims.name,\n picture: claims.picture,\n });\n setIsAdmin(admin);\n },\n [cookieName],\n );\n\n // Restore an existing session from the cookie on mount.\n useEffect(() => {\n const existing = readCookie(cookieName);\n if (existing) applyToken(existing);\n }, [cookieName, applyToken]);\n\n // Load the GSI script and initialize the client. Polling (rather than relying\n // on a `load` event) makes this robust against React's dev double-mount, a\n // cached/already-loaded script, and missed load events — any of which could\n // otherwise leave `ready` stuck false and the UI spinning forever.\n useEffect(() => {\n let cancelled = false;\n\n function tryInit(): boolean {\n if (cancelled || !window.google?.accounts?.id) return false;\n window.google.accounts.id.initialize({\n client_id: clientId,\n callback: (res) => applyToken(res.credential),\n auto_select: false,\n cancel_on_tap_outside: true,\n });\n setReady(true);\n return true;\n }\n\n if (tryInit()) return;\n\n // Ensure the script tag exists exactly once across mounts.\n if (!document.querySelector(`script[src=\"${GSI_SRC}\"]`)) {\n const script = document.createElement(\"script\");\n script.src = GSI_SRC;\n script.async = true;\n script.defer = true;\n document.head.appendChild(script);\n }\n\n const startedAt = Date.now();\n const timer = setInterval(() => {\n if (tryInit()) {\n clearInterval(timer);\n } else if (Date.now() - startedAt > 10_000) {\n // Blocked (ad/privacy extension) or offline — surface a failure so the\n // UI can show a fallback instead of an endless spinner.\n clearInterval(timer);\n if (!cancelled) setFailed(true);\n }\n }, 120);\n\n return () => {\n cancelled = true;\n clearInterval(timer);\n };\n }, [clientId, applyToken]);\n\n // Intercept admin 401s so an expired/forbidden session forces sign-out.\n useEffect(() => {\n const originalFetch = window.fetch;\n window.fetch = async (...args: Parameters<typeof fetch>) => {\n const response = await originalFetch(...args);\n if (response.status === 401) {\n try {\n const data = await response.clone().json();\n if (data?.logout) doLogout();\n } catch {\n /* not a JSON body — ignore */\n }\n }\n return response;\n };\n return () => {\n window.fetch = originalFetch;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n function doLogout() {\n window.google?.accounts.id.disableAutoSelect();\n deleteCookie(cookieName);\n setUser(null);\n setIsAdmin(false);\n setIsEditing(false);\n onLogout?.();\n }\n\n const promptSignIn = useCallback(() => {\n if (!ready) return;\n window.google?.accounts.id.prompt();\n }, [ready]);\n\n const renderButton = useCallback(\n (el: HTMLElement, options?: GsiButtonOptions) => {\n if (!ready) return;\n window.google?.accounts.id.renderButton(el, options);\n },\n [ready],\n );\n\n const toggleEdit = useCallback(() => setIsEditing((p) => !p), []);\n\n return (\n <GoogleAuthContext.Provider\n value={{\n user,\n isAdmin,\n isEditing,\n toggleEdit,\n ready,\n failed,\n promptSignIn,\n renderButton,\n logout: doLogout,\n }}\n >\n <CmsAuthProvider value={{ isAdmin, isEditing, toggleEdit }}>\n {children}\n </CmsAuthProvider>\n </GoogleAuthContext.Provider>\n );\n}\n\n/** Google auth API (user/login/logout) for login pages and toolbars. */\nexport function useGoogleAuth(): GoogleAuthContextValue {\n const ctx = useContext(GoogleAuthContext);\n if (!ctx) {\n throw new Error(\"useGoogleAuth must be used within a GoogleAuthProvider\");\n }\n return ctx;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../../src/auth/google/client/index.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useRef,\n useState,\n type ReactNode,\n} from \"react\";\nimport {\n GoogleOAuthProvider,\n GoogleLogin,\n googleLogout,\n type CredentialResponse,\n} from \"@react-oauth/google\";\n// Imported via the public specifier so we share the SAME context instance as\n// the consumer's edit primitives at runtime (see tsup `external` + tsconfig paths).\nimport { CmsAuthProvider } from \"@dalgoridim/headless-cms/client\";\n\n/**\n * Client provider for **Google sign-in**, built on `@react-oauth/google` (the\n * official React wrapper for Google Identity Services). It handles the GSI\n * script + button rendering robustly — including remounts, so the button shows\n * reliably every time a sign-in dialog opens. The resulting ID token is stashed\n * in a cookie for the server `googleAuth` adapter to verify; admin status is\n * optimistic on the client (via `adminEmails`), with the server gate\n * authoritative.\n *\n * Render `<GoogleSignInButton />` anywhere inside the provider to show the\n * official Google button.\n */\n\nexport interface GoogleUser {\n sub: string;\n email?: string;\n name?: string;\n picture?: string;\n}\n\nexport interface GoogleAuthProviderProps {\n children: ReactNode;\n /** OAuth 2.0 Web client ID (from Google Cloud Console → Credentials). */\n clientId: string;\n /**\n * Optional admin allowlist for *optimistic* client-side `isAdmin`. The server\n * `googleAuth` adapter still enforces the real gate. Omit to treat any\n * successful sign-in as optimistically admin (server will correct via 401).\n */\n adminEmails?: string[];\n /** Cookie name for the ID token. Default `adminToken`. */\n cookieName?: string;\n /** Called when a 401 `{ logout: true }` response is intercepted. */\n onLogout?: () => void;\n}\n\nexport interface GoogleAuthContextValue {\n user: GoogleUser | null;\n isAdmin: boolean;\n isEditing: boolean;\n toggleEdit: () => void;\n logout: () => void;\n /** Feed a Google ID token credential (used by `GoogleSignInButton`). */\n applyCredential: (credential: string) => void;\n}\n\nconst GoogleAuthContext = createContext<GoogleAuthContextValue | undefined>(\n undefined,\n);\n\nfunction setCookie(name: string, value: string) {\n document.cookie = `${name}=${encodeURIComponent(value)}; path=/; samesite=lax`;\n}\nfunction deleteCookie(name: string) {\n document.cookie = `${name}=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT`;\n}\nfunction readCookie(name: string): string | null {\n for (const part of document.cookie.split(\";\")) {\n const [k, ...v] = part.trim().split(\"=\");\n if (k === name) return decodeURIComponent(v.join(\"=\"));\n }\n return null;\n}\n\n/** Decode a JWT payload client-side (no verification — the server does that). */\nfunction decodeJwt(token: string): (GoogleUser & { exp?: number }) | null {\n try {\n const payload = token.split(\".\")[1];\n const json = atob(payload.replace(/-/g, \"+\").replace(/_/g, \"/\"));\n return JSON.parse(json);\n } catch {\n return null;\n }\n}\n\nfunction AuthState({\n children,\n adminEmails,\n cookieName = \"adminToken\",\n onLogout,\n}: Omit<GoogleAuthProviderProps, \"clientId\">) {\n const [user, setUser] = useState<GoogleUser | null>(null);\n const [isAdmin, setIsAdmin] = useState(false);\n const [isEditing, setIsEditing] = useState(false);\n const allowed = useRef(\n (adminEmails ?? []).map((e) => e.trim().toLowerCase()),\n );\n\n const applyCredential = useCallback(\n (token: string) => {\n const claims = decodeJwt(token);\n if (!claims) return;\n if (claims.exp && claims.exp * 1000 <= Date.now()) {\n deleteCookie(cookieName);\n return;\n }\n const email = claims.email?.toLowerCase();\n // Optimistic: if no allowlist supplied, trust the sign-in and let the\n // server correct a non-admin via the 401 interceptor below.\n const admin =\n allowed.current.length === 0\n ? true\n : !!email && allowed.current.includes(email);\n setCookie(cookieName, token);\n setUser({\n sub: claims.sub,\n email: claims.email,\n name: claims.name,\n picture: claims.picture,\n });\n setIsAdmin(admin);\n },\n [cookieName],\n );\n\n // Restore an existing session from the cookie on mount.\n useEffect(() => {\n const existing = readCookie(cookieName);\n if (existing) applyCredential(existing);\n }, [cookieName, applyCredential]);\n\n const logout = useCallback(() => {\n googleLogout();\n deleteCookie(cookieName);\n setUser(null);\n setIsAdmin(false);\n setIsEditing(false);\n onLogout?.();\n }, [cookieName, onLogout]);\n\n // Intercept admin 401s so an expired/forbidden session forces sign-out.\n useEffect(() => {\n const originalFetch = window.fetch;\n window.fetch = async (...args: Parameters<typeof fetch>) => {\n const response = await originalFetch(...args);\n if (response.status === 401) {\n try {\n const data = await response.clone().json();\n if (data?.logout) logout();\n } catch {\n /* not a JSON body — ignore */\n }\n }\n return response;\n };\n return () => {\n window.fetch = originalFetch;\n };\n }, [logout]);\n\n const toggleEdit = useCallback(() => setIsEditing((p) => !p), []);\n\n return (\n <GoogleAuthContext.Provider\n value={{ user, isAdmin, isEditing, toggleEdit, logout, applyCredential }}\n >\n <CmsAuthProvider value={{ isAdmin, isEditing, toggleEdit }}>\n {children}\n </CmsAuthProvider>\n </GoogleAuthContext.Provider>\n );\n}\n\nexport function GoogleAuthProvider({\n children,\n clientId,\n ...rest\n}: GoogleAuthProviderProps) {\n return (\n <GoogleOAuthProvider clientId={clientId}>\n <AuthState {...rest}>{children}</AuthState>\n </GoogleOAuthProvider>\n );\n}\n\nexport interface GoogleSignInButtonProps {\n /** Forwarded to the underlying `<GoogleLogin>` (theme, shape, size, text…). */\n theme?: \"outline\" | \"filled_blue\" | \"filled_black\";\n shape?: \"rectangular\" | \"pill\" | \"circle\" | \"square\";\n size?: \"small\" | \"medium\" | \"large\";\n text?: \"signin_with\" | \"signup_with\" | \"continue_with\" | \"signin\";\n onError?: () => void;\n}\n\n/** The official Google sign-in button. Must be rendered inside a provider. */\nexport function GoogleSignInButton({\n theme = \"filled_black\",\n shape = \"pill\",\n size = \"large\",\n text = \"continue_with\",\n onError,\n}: GoogleSignInButtonProps) {\n const { applyCredential } = useGoogleAuth();\n return (\n <GoogleLogin\n onSuccess={(res: CredentialResponse) => {\n if (res.credential) applyCredential(res.credential);\n }}\n onError={onError}\n theme={theme}\n shape={shape}\n size={size}\n text={text}\n />\n );\n}\n\n/** Google auth API (user/logout) for login pages and toolbars. */\nexport function useGoogleAuth(): GoogleAuthContextValue {\n const ctx = useContext(GoogleAuthContext);\n if (!ctx) {\n throw new Error(\"useGoogleAuth must be used within a GoogleAuthProvider\");\n }\n return ctx;\n}\n"],"mappings":"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","names":[]}
@@ -1,5 +1,5 @@
1
- import * as React$1 from 'react';
2
- import React__default, { ReactNode } from 'react';
1
+ import * as react from 'react';
2
+ import react__default, { ReactNode } from 'react';
3
3
  import { NestedSections, ClientStorageAdapter, PendingImage, Section, CmsAuthState } from '../index.cjs';
4
4
  export { AuthAdapter, AuthIdentity, DataAdapter, Editable, EntityAddress, Query, QueryCondition, QueryFilter, QueryFilterGroup, QueryFilterOp, Ref, RelationConfig, SectionMap, ServerStorageAdapter, StorageAdapter } from '../index.cjs';
5
5
 
@@ -37,7 +37,7 @@ interface PageProviderProps {
37
37
  /** Notification sink. Defaults to `sonner` toasts. */
38
38
  notify?: Notifier;
39
39
  }
40
- declare const PageProvider: ({ children, initialSections, apiBasePath, storage, notify, }: PageProviderProps) => React$1.JSX.Element;
40
+ declare const PageProvider: ({ children, initialSections, apiBasePath, storage, notify, }: PageProviderProps) => react.JSX.Element;
41
41
  declare const usePageContext: () => PageContextType;
42
42
 
43
43
  /**
@@ -49,7 +49,7 @@ declare const usePageContext: () => PageContextType;
49
49
  * this context through the package's public `…/client` specifier so they share
50
50
  * the *same* context instance as the consumer's primitives at runtime.
51
51
  */
52
- declare const CmsAuthContext: React$1.Context<CmsAuthState | undefined>;
52
+ declare const CmsAuthContext: react.Context<CmsAuthState | undefined>;
53
53
  declare function useCmsAuth(): CmsAuthState;
54
54
  /**
55
55
  * Controlled provider for consumers wiring their own auth. Pass the resolved
@@ -58,7 +58,7 @@ declare function useCmsAuth(): CmsAuthState;
58
58
  declare function CmsAuthProvider({ value, children, }: {
59
59
  value: CmsAuthState;
60
60
  children: ReactNode;
61
- }): React$1.JSX.Element;
61
+ }): react.JSX.Element;
62
62
 
63
63
  /**
64
64
  * Inline-editable text primitive. Headless: it wires `contentEditable`, reads
@@ -75,13 +75,13 @@ interface ContentEditSpanProps {
75
75
  sectionKey: string;
76
76
  fieldKey: string;
77
77
  className?: string;
78
- children?: React__default.ReactNode;
78
+ children?: react__default.ReactNode;
79
79
  /** Element/tag to render. Defaults to `span`. */
80
- as?: React__default.ElementType;
80
+ as?: react__default.ElementType;
81
81
  /** Render the stored raw string into nodes. Defaults to plain text. */
82
- renderValue?: (raw: string) => React__default.ReactNode;
82
+ renderValue?: (raw: string) => react__default.ReactNode;
83
83
  }
84
- declare function ContentEditSpan({ collection, sectionKey, fieldKey, className, children, as, renderValue, }: ContentEditSpanProps): React__default.JSX.Element;
84
+ declare function ContentEditSpan({ collection, sectionKey, fieldKey, className, children, as, renderValue, }: ContentEditSpanProps): react__default.JSX.Element;
85
85
 
86
86
  /** State + actions handed to the {@link EditableImage} render-prop. */
87
87
  interface EditableImageRenderState {
@@ -116,9 +116,9 @@ interface EditableImageProps {
116
116
  * package ships no visual look — bring your own. When omitted, a bare
117
117
  * unstyled `<img>` is rendered.
118
118
  */
119
- children?: (state: EditableImageRenderState) => React__default.ReactNode;
119
+ children?: (state: EditableImageRenderState) => react__default.ReactNode;
120
120
  }
121
- declare function EditableImage({ sectionKey, fieldKey, src, collection, docId, className, children, }: EditableImageProps): React__default.JSX.Element;
121
+ declare function EditableImage({ sectionKey, fieldKey, src, collection, docId, className, children, }: EditableImageProps): react__default.JSX.Element;
122
122
 
123
123
  interface UseMarkdownEditorOptions {
124
124
  initialValue: string;
@@ -1,5 +1,5 @@
1
- import * as React$1 from 'react';
2
- import React__default, { ReactNode } from 'react';
1
+ import * as react from 'react';
2
+ import react__default, { ReactNode } from 'react';
3
3
  import { NestedSections, ClientStorageAdapter, PendingImage, Section, CmsAuthState } from '../index.js';
4
4
  export { AuthAdapter, AuthIdentity, DataAdapter, Editable, EntityAddress, Query, QueryCondition, QueryFilter, QueryFilterGroup, QueryFilterOp, Ref, RelationConfig, SectionMap, ServerStorageAdapter, StorageAdapter } from '../index.js';
5
5
 
@@ -37,7 +37,7 @@ interface PageProviderProps {
37
37
  /** Notification sink. Defaults to `sonner` toasts. */
38
38
  notify?: Notifier;
39
39
  }
40
- declare const PageProvider: ({ children, initialSections, apiBasePath, storage, notify, }: PageProviderProps) => React$1.JSX.Element;
40
+ declare const PageProvider: ({ children, initialSections, apiBasePath, storage, notify, }: PageProviderProps) => react.JSX.Element;
41
41
  declare const usePageContext: () => PageContextType;
42
42
 
43
43
  /**
@@ -49,7 +49,7 @@ declare const usePageContext: () => PageContextType;
49
49
  * this context through the package's public `…/client` specifier so they share
50
50
  * the *same* context instance as the consumer's primitives at runtime.
51
51
  */
52
- declare const CmsAuthContext: React$1.Context<CmsAuthState | undefined>;
52
+ declare const CmsAuthContext: react.Context<CmsAuthState | undefined>;
53
53
  declare function useCmsAuth(): CmsAuthState;
54
54
  /**
55
55
  * Controlled provider for consumers wiring their own auth. Pass the resolved
@@ -58,7 +58,7 @@ declare function useCmsAuth(): CmsAuthState;
58
58
  declare function CmsAuthProvider({ value, children, }: {
59
59
  value: CmsAuthState;
60
60
  children: ReactNode;
61
- }): React$1.JSX.Element;
61
+ }): react.JSX.Element;
62
62
 
63
63
  /**
64
64
  * Inline-editable text primitive. Headless: it wires `contentEditable`, reads
@@ -75,13 +75,13 @@ interface ContentEditSpanProps {
75
75
  sectionKey: string;
76
76
  fieldKey: string;
77
77
  className?: string;
78
- children?: React__default.ReactNode;
78
+ children?: react__default.ReactNode;
79
79
  /** Element/tag to render. Defaults to `span`. */
80
- as?: React__default.ElementType;
80
+ as?: react__default.ElementType;
81
81
  /** Render the stored raw string into nodes. Defaults to plain text. */
82
- renderValue?: (raw: string) => React__default.ReactNode;
82
+ renderValue?: (raw: string) => react__default.ReactNode;
83
83
  }
84
- declare function ContentEditSpan({ collection, sectionKey, fieldKey, className, children, as, renderValue, }: ContentEditSpanProps): React__default.JSX.Element;
84
+ declare function ContentEditSpan({ collection, sectionKey, fieldKey, className, children, as, renderValue, }: ContentEditSpanProps): react__default.JSX.Element;
85
85
 
86
86
  /** State + actions handed to the {@link EditableImage} render-prop. */
87
87
  interface EditableImageRenderState {
@@ -116,9 +116,9 @@ interface EditableImageProps {
116
116
  * package ships no visual look — bring your own. When omitted, a bare
117
117
  * unstyled `<img>` is rendered.
118
118
  */
119
- children?: (state: EditableImageRenderState) => React__default.ReactNode;
119
+ children?: (state: EditableImageRenderState) => react__default.ReactNode;
120
120
  }
121
- declare function EditableImage({ sectionKey, fieldKey, src, collection, docId, className, children, }: EditableImageProps): React__default.JSX.Element;
121
+ declare function EditableImage({ sectionKey, fieldKey, src, collection, docId, className, children, }: EditableImageProps): react__default.JSX.Element;
122
122
 
123
123
  interface UseMarkdownEditorOptions {
124
124
  initialValue: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dalgoridim/headless-cms",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "description": "Database-agnostic, inline-edit headless CMS engine for React / Next.js apps",
5
5
  "license": "UNLICENSED",
6
6
  "author": "dalgoridim",
@@ -119,6 +119,7 @@
119
119
  "peerDependencies": {
120
120
  "@aws-sdk/client-s3": ">=3",
121
121
  "@aws-sdk/s3-request-presigner": ">=3",
122
+ "@react-oauth/google": ">=0.12",
122
123
  "cloudinary": ">=2",
123
124
  "firebase": ">=10",
124
125
  "firebase-admin": ">=12",
@@ -131,6 +132,9 @@
131
132
  "@aws-sdk/client-s3": {
132
133
  "optional": true
133
134
  },
135
+ "@react-oauth/google": {
136
+ "optional": true
137
+ },
134
138
  "@aws-sdk/s3-request-presigner": {
135
139
  "optional": true
136
140
  },
@@ -150,10 +154,10 @@
150
154
  "optional": true
151
155
  }
152
156
  },
153
- "dependencies": {},
154
157
  "devDependencies": {
155
158
  "@aws-sdk/client-s3": "^3.700.0",
156
159
  "@aws-sdk/s3-request-presigner": "^3.700.0",
160
+ "@react-oauth/google": "^0.13.5",
157
161
  "@types/node": "^20.0.0",
158
162
  "@types/pg": "^8.11.10",
159
163
  "@types/react": "^19.0.0",