@authrobo/react 0.1.0 → 0.2.1

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/dist/index.cjs CHANGED
@@ -27,7 +27,8 @@ __export(index_exports, {
27
27
  AuthRoboProvider: () => AuthRoboProvider,
28
28
  PROVIDER_META: () => PROVIDER_META,
29
29
  startSignIn: () => startSignIn,
30
- useAppConfig: () => useAppConfig
30
+ useAppConfig: () => useAppConfig,
31
+ useLastMethod: () => useLastMethod
31
32
  });
32
33
  module.exports = __toCommonJS(index_exports);
33
34
  var import_react = require("react");
@@ -63,7 +64,8 @@ var PROVIDER_META = {
63
64
  linkedin: { label: "LinkedIn", icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LinkedInIcon, {}) },
64
65
  github: { label: "GitHub", icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GitHubIcon, {}) },
65
66
  apple: { label: "Apple", icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AppleIcon, {}) },
66
- password: { label: "email", icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MailIcon, {}) }
67
+ password: { label: "email", icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MailIcon, {}) },
68
+ magic_link: { label: "magic link", icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MailIcon, {}) }
67
69
  };
68
70
 
69
71
  // src/index.tsx
@@ -90,6 +92,7 @@ function randomState() {
90
92
  (globalThis.crypto ?? globalThis.crypto).getRandomValues(b);
91
93
  return Array.from(b, (x) => x.toString(16).padStart(2, "0")).join("");
92
94
  }
95
+ var EMAIL_METHODS = ["password", "magic_link"];
93
96
  function startSignIn(config, method, opts) {
94
97
  const base = config.issuerUrl.replace(/\/$/, "");
95
98
  const state = opts?.state ?? randomState();
@@ -103,9 +106,24 @@ function startSignIn(config, method, opts) {
103
106
  state
104
107
  });
105
108
  if (opts?.loginHint) params.set("login_hint", opts.loginHint);
106
- const url = method === "password" ? `${base}/login?${params.toString()}` : `${base}/api/v1/auth/${method}/start?${params.toString()}`;
109
+ try {
110
+ localStorage.setItem(lastMethodKey(config.clientId), method);
111
+ } catch {
112
+ }
113
+ const url = EMAIL_METHODS.includes(method) ? `${base}/login?${params.toString()}` : `${base}/api/v1/auth/${method}/start?${params.toString()}`;
107
114
  window.location.assign(url);
108
115
  }
116
+ var lastMethodKey = (clientId) => `authrobo:last-method:${clientId}`;
117
+ function useLastMethod(clientId) {
118
+ const [last, setLast] = (0, import_react.useState)(null);
119
+ (0, import_react.useEffect)(() => {
120
+ try {
121
+ setLast(localStorage.getItem(lastMethodKey(clientId)) ?? null);
122
+ } catch {
123
+ }
124
+ }, [clientId]);
125
+ return last;
126
+ }
109
127
  function useAppConfig(config) {
110
128
  const c = useResolvedConfig(config);
111
129
  const [state, setState] = (0, import_react.useState)({
@@ -132,24 +150,52 @@ function useAppConfig(config) {
132
150
  return state;
133
151
  }
134
152
  var STYLE_ID = "authrobo-react-styles";
153
+ var TOKENS_LIGHT = `
154
+ --authrobo-btn-bg:#fff;--authrobo-btn-fg:#111827;--authrobo-btn-border:#d1d5db;
155
+ --authrobo-btn-hover-bg:#f8fafc;--authrobo-btn-hover-border:#9ca3af;
156
+ --authrobo-card-bg:#fff;--authrobo-title-fg:#0f172a;--authrobo-muted:#64748b;
157
+ --authrobo-error:#b91c1c;--authrobo-divider-fg:#9ca3af;--authrobo-divider-line:#e5e7eb;
158
+ --authrobo-overlay:rgba(2,6,23,.55);--authrobo-close:#94a3b8;--authrobo-close-hover:#334155;
159
+ --authrobo-focus:#2b8aff;--authrobo-card-shadow:rgba(2,6,23,.4);
160
+ --authrobo-last-bg:#e7efff;--authrobo-last-fg:#2563eb;`;
161
+ var TOKENS_DARK = `
162
+ --authrobo-btn-bg:#0e1626;--authrobo-btn-fg:#e6edf8;--authrobo-btn-border:#28374f;
163
+ --authrobo-btn-hover-bg:#16233a;--authrobo-btn-hover-border:#3a4d6b;
164
+ --authrobo-card-bg:#0b1424;--authrobo-title-fg:#f1f5fb;--authrobo-muted:#93a4bd;
165
+ --authrobo-error:#f87171;--authrobo-divider-fg:#64748b;--authrobo-divider-line:#223049;
166
+ --authrobo-overlay:rgba(2,6,23,.72);--authrobo-close:#64748b;--authrobo-close-hover:#cbd5e1;
167
+ --authrobo-focus:#46c8ff;--authrobo-card-shadow:rgba(0,0,0,.6);
168
+ --authrobo-last-bg:rgba(70,160,255,.18);--authrobo-last-fg:#8fc4ff;`;
169
+ var TOKEN_ROOTS = ".authrobo-btn,.authrobo-stack,.authrobo-card,.authrobo-overlay,.authrobo-divider,.authrobo-title";
170
+ var scoped = (prefix, tokens) => TOKEN_ROOTS.split(",").map((s) => `${prefix}${s.trim()}`).join(",") + `{${tokens}}`;
135
171
  var CSS = `
136
- .authrobo-btn{display:flex;width:100%;align-items:center;justify-content:center;gap:10px;
137
- border:1px solid #d1d5db;background:#fff;color:#111827;border-radius:10px;padding:10px 14px;
172
+ ${scoped("", TOKENS_LIGHT)}
173
+ @media (prefers-color-scheme:dark){${scoped("", TOKENS_DARK)}}
174
+ ${scoped('[data-theme="light"] ', TOKENS_LIGHT)}
175
+ ${scoped('[data-theme="dark"] ', TOKENS_DARK)}
176
+ .authrobo-btn{position:relative;display:flex;width:100%;align-items:center;justify-content:center;gap:10px;
177
+ border:1px solid var(--authrobo-btn-border);background:var(--authrobo-btn-bg);color:var(--authrobo-btn-fg);border-radius:10px;padding:10px 14px;
138
178
  font:600 14px/1.2 system-ui,-apple-system,Segoe UI,Roboto,sans-serif;cursor:pointer;
139
179
  transition:background .15s ease,border-color .15s ease,box-shadow .15s ease;}
140
- .authrobo-btn:hover{background:#f8fafc;border-color:#9ca3af;}
141
- .authrobo-btn:focus-visible{outline:2px solid #2b8aff;outline-offset:2px;}
180
+ .authrobo-btn:hover{background:var(--authrobo-btn-hover-bg);border-color:var(--authrobo-btn-hover-border);}
181
+ .authrobo-btn:focus-visible{outline:2px solid var(--authrobo-focus);outline-offset:2px;}
182
+ .authrobo-last{position:absolute;right:8px;top:50%;transform:translateY(-50%);pointer-events:none;
183
+ font:600 10px/1 system-ui,sans-serif;letter-spacing:.02em;padding:3px 7px;border-radius:999px;
184
+ background:var(--authrobo-last-bg);color:var(--authrobo-last-fg);}
142
185
  .authrobo-stack{display:flex;flex-direction:column;gap:10px;}
143
- .authrobo-divider{display:flex;align-items:center;gap:10px;color:#9ca3af;font:500 12px system-ui,sans-serif;}
144
- .authrobo-divider::before,.authrobo-divider::after{content:"";height:1px;flex:1;background:#e5e7eb;}
186
+ .authrobo-title{font:700 16px system-ui,sans-serif;color:var(--authrobo-title-fg);}
187
+ .authrobo-muted{color:var(--authrobo-muted);font:14px system-ui,sans-serif;margin:0;}
188
+ .authrobo-error{color:var(--authrobo-error);font:14px system-ui,sans-serif;margin:0;}
189
+ .authrobo-divider{display:flex;align-items:center;gap:10px;color:var(--authrobo-divider-fg);font:500 12px system-ui,sans-serif;}
190
+ .authrobo-divider::before,.authrobo-divider::after{content:"";height:1px;flex:1;background:var(--authrobo-divider-line);}
145
191
  .authrobo-overlay{position:fixed;inset:0;z-index:2147483000;display:flex;align-items:center;
146
- justify-content:center;padding:16px;background:rgba(2,6,23,.55);backdrop-filter:blur(2px);
192
+ justify-content:center;padding:16px;background:var(--authrobo-overlay);backdrop-filter:blur(2px);
147
193
  animation:authrobo-fade .16s ease-out both;}
148
- .authrobo-card{position:relative;width:100%;max-width:360px;background:#fff;border-radius:16px;
149
- padding:24px;box-shadow:0 20px 60px -12px rgba(2,6,23,.4);animation:authrobo-pop .2s cubic-bezier(.16,1,.3,1) both;}
194
+ .authrobo-card{position:relative;width:100%;max-width:360px;background:var(--authrobo-card-bg);border-radius:16px;
195
+ padding:24px;box-shadow:0 20px 60px -12px var(--authrobo-card-shadow);animation:authrobo-pop .2s cubic-bezier(.16,1,.3,1) both;}
150
196
  .authrobo-close{position:absolute;top:12px;right:12px;border:0;background:transparent;cursor:pointer;
151
- color:#94a3b8;font-size:18px;line-height:1;padding:4px;}
152
- .authrobo-close:hover{color:#334155;}
197
+ color:var(--authrobo-close);font-size:18px;line-height:1;padding:4px;}
198
+ .authrobo-close:hover{color:var(--authrobo-close-hover);}
153
199
  @keyframes authrobo-fade{from{opacity:0}to{opacity:1}}
154
200
  @keyframes authrobo-pop{from{opacity:0;transform:translateY(8px) scale(.97)}to{opacity:1;transform:none}}
155
201
  @media (prefers-reduced-motion: reduce){.authrobo-overlay,.authrobo-card{animation:none}}
@@ -169,7 +215,8 @@ function AuthRoboButton({
169
215
  label,
170
216
  className,
171
217
  style,
172
- onClick
218
+ onClick,
219
+ lastUsed
173
220
  }) {
174
221
  useInjectStyles();
175
222
  const c = useResolvedConfig(config);
@@ -186,7 +233,8 @@ function AuthRoboButton({
186
233
  },
187
234
  children: [
188
235
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { display: "inline-flex", alignItems: "center" }, children: meta.icon }),
189
- label ?? `Continue with ${meta.label}`
236
+ label ?? `Continue with ${meta.label}`,
237
+ lastUsed && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "authrobo-last", children: "Last used" })
190
238
  ]
191
239
  }
192
240
  );
@@ -202,6 +250,7 @@ function AuthRoboAuth({
202
250
  useInjectStyles();
203
251
  const c = useResolvedConfig(config);
204
252
  const { data, loading, error } = useAppConfig(c);
253
+ const lastMethod = useLastMethod(c.clientId);
205
254
  (0, import_react.useEffect)(() => {
206
255
  if (mode !== "modal" || !open) return;
207
256
  const onKey = (e) => e.key === "Escape" && onClose?.();
@@ -213,19 +262,29 @@ function AuthRoboAuth({
213
262
  const shown = (only ? only.filter((m) => available.includes(m)) : available).filter(
214
263
  (m) => m in PROVIDER_META
215
264
  );
216
- const social = shown.filter((m) => m !== "password");
217
- const hasPassword = shown.includes("password");
265
+ const social = shown.filter((m) => !EMAIL_METHODS.includes(m));
266
+ const email = shown.filter((m) => EMAIL_METHODS.includes(m));
267
+ const badge = (m) => shown.length > 1 && m === lastMethod;
218
268
  const panel = /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "authrobo-stack", children: [
219
269
  (data?.logoUrl || data?.name) && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 10, marginBottom: 4 }, children: [
220
270
  data?.logoUrl && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("img", { src: data.logoUrl, alt: "", width: 28, height: 28, style: { borderRadius: 6 } }),
221
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { font: "700 16px system-ui,sans-serif", color: "#0f172a" }, children: title ?? `Sign in${data?.name ? ` to ${data.name}` : ""}` })
271
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "authrobo-title", children: title ?? `Sign in${data?.name ? ` to ${data.name}` : ""}` })
222
272
  ] }),
223
- loading && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: { color: "#64748b", font: "14px system-ui,sans-serif" }, children: "Loading sign-in options\u2026" }),
224
- error && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: { color: "#b91c1c", font: "14px system-ui,sans-serif" }, children: "Couldn't load sign-in options." }),
225
- social.map((m) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(AuthRoboButton, { provider: m, config: c }, m)),
226
- hasPassword && social.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "authrobo-divider", children: "or" }),
227
- hasPassword && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(AuthRoboButton, { provider: "password", config: c, label: "Continue with email" }),
228
- !loading && !error && shown.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: { color: "#64748b", font: "14px system-ui,sans-serif" }, children: "No sign-in methods are enabled for this app." })
273
+ loading && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "authrobo-muted", children: "Loading sign-in options\u2026" }),
274
+ error && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "authrobo-error", children: "Couldn't load sign-in options." }),
275
+ social.map((m) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(AuthRoboButton, { provider: m, config: c, lastUsed: badge(m) }, m)),
276
+ email.length > 0 && social.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "authrobo-divider", children: "or" }),
277
+ email.map((m) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
278
+ AuthRoboButton,
279
+ {
280
+ provider: m,
281
+ config: c,
282
+ label: m === "magic_link" ? "Email me a sign-in link" : "Continue with email",
283
+ lastUsed: badge(m)
284
+ },
285
+ m
286
+ )),
287
+ !loading && !error && shown.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "authrobo-muted", children: "No sign-in methods are enabled for this app." })
229
288
  ] });
230
289
  if (mode === "inline") return panel;
231
290
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "authrobo-overlay", onClick: () => onClose?.(), children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "authrobo-card", role: "dialog", "aria-modal": "true", onClick: (e) => e.stopPropagation(), children: [
@@ -240,6 +299,7 @@ function AuthRoboAuth({
240
299
  AuthRoboProvider,
241
300
  PROVIDER_META,
242
301
  startSignIn,
243
- useAppConfig
302
+ useAppConfig,
303
+ useLastMethod
244
304
  });
245
305
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.tsx","../src/icons.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { createContext, useContext, useEffect, useState } from \"react\";\nimport { PROVIDER_META, type Method } from \"./icons\";\n\nexport type { Method } from \"./icons\";\nexport { PROVIDER_META } from \"./icons\";\n\nexport interface AuthRoboConfig {\n /** Your AuthRobo instance, e.g. \"https://authrobo.com\". */\n issuerUrl: string;\n /** Your app's client_id (arc_...). */\n clientId: string;\n /** Where AuthRobo returns the one-time `code` after sign-in. */\n redirectUri: string;\n}\n\n/** Shape returned by GET /api/v1/config. */\nexport interface AppConfig {\n name: string;\n brandColor: string | null;\n logoUrl: string | null;\n methods: Method[];\n}\n\nconst ConfigContext = createContext<AuthRoboConfig | null>(null);\n\nexport function AuthRoboProvider({\n config,\n children,\n}: {\n config: AuthRoboConfig;\n children: React.ReactNode;\n}) {\n return <ConfigContext.Provider value={config}>{children}</ConfigContext.Provider>;\n}\n\nfunction useResolvedConfig(override?: AuthRoboConfig): AuthRoboConfig {\n const ctx = useContext(ConfigContext);\n const c = override ?? ctx;\n if (!c) {\n throw new Error(\n \"@authrobo/react: wrap your app in <AuthRoboProvider config={...}> or pass a `config` prop.\",\n );\n }\n return c;\n}\n\nfunction randomState(): string {\n const b = new Uint8Array(16);\n (globalThis.crypto ?? (globalThis as { crypto: Crypto }).crypto).getRandomValues(b);\n return Array.from(b, (x) => x.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\n/**\n * Begin sign-in: navigate the browser to AuthRobo's hosted flow for `method`.\n * Social methods hit /api/v1/auth/<provider>/start; \"password\" opens the hosted\n * email login page. AuthRobo returns to your `redirectUri` with a `code` that\n * your backend exchanges (with your client_secret) for tokens.\n */\nexport function startSignIn(\n config: AuthRoboConfig,\n method: Method,\n opts?: { state?: string; loginHint?: string },\n): void {\n const base = config.issuerUrl.replace(/\\/$/, \"\");\n const state = opts?.state ?? randomState();\n try {\n sessionStorage.setItem(\"authrobo:state\", state);\n } catch {\n /* storage unavailable — state is still sent, just not persisted for a check */\n }\n const params = new URLSearchParams({\n client_id: config.clientId,\n redirect_uri: config.redirectUri,\n state,\n });\n if (opts?.loginHint) params.set(\"login_hint\", opts.loginHint);\n const url =\n method === \"password\"\n ? `${base}/login?${params.toString()}`\n : `${base}/api/v1/auth/${method}/start?${params.toString()}`;\n window.location.assign(url);\n}\n\n/** Fetch an app's public config (branding + enabled methods) for `clientId`. */\nexport function useAppConfig(config?: AuthRoboConfig): {\n data: AppConfig | null;\n loading: boolean;\n error: string | null;\n} {\n const c = useResolvedConfig(config);\n const [state, setState] = useState<{ data: AppConfig | null; loading: boolean; error: string | null }>({\n data: null,\n loading: true,\n error: null,\n });\n\n useEffect(() => {\n let active = true;\n setState({ data: null, loading: true, error: null });\n const base = c.issuerUrl.replace(/\\/$/, \"\");\n fetch(`${base}/api/v1/config?client_id=${encodeURIComponent(c.clientId)}`)\n .then((r) => (r.ok ? r.json() : Promise.reject(new Error(`config ${r.status}`))))\n .then((j) => {\n if (!active) return;\n setState({\n data: { name: j.name, brandColor: j.brand_color, logoUrl: j.logo_url, methods: j.methods },\n loading: false,\n error: null,\n });\n })\n .catch((e) => active && setState({ data: null, loading: false, error: String(e?.message ?? e) }));\n return () => {\n active = false;\n };\n }, [c.issuerUrl, c.clientId]);\n\n return state;\n}\n\n/* ----------------------------- Styling ---------------------------------- */\n\nconst STYLE_ID = \"authrobo-react-styles\";\nconst CSS = `\n.authrobo-btn{display:flex;width:100%;align-items:center;justify-content:center;gap:10px;\n border:1px solid #d1d5db;background:#fff;color:#111827;border-radius:10px;padding:10px 14px;\n font:600 14px/1.2 system-ui,-apple-system,Segoe UI,Roboto,sans-serif;cursor:pointer;\n transition:background .15s ease,border-color .15s ease,box-shadow .15s ease;}\n.authrobo-btn:hover{background:#f8fafc;border-color:#9ca3af;}\n.authrobo-btn:focus-visible{outline:2px solid #2b8aff;outline-offset:2px;}\n.authrobo-stack{display:flex;flex-direction:column;gap:10px;}\n.authrobo-divider{display:flex;align-items:center;gap:10px;color:#9ca3af;font:500 12px system-ui,sans-serif;}\n.authrobo-divider::before,.authrobo-divider::after{content:\"\";height:1px;flex:1;background:#e5e7eb;}\n.authrobo-overlay{position:fixed;inset:0;z-index:2147483000;display:flex;align-items:center;\n justify-content:center;padding:16px;background:rgba(2,6,23,.55);backdrop-filter:blur(2px);\n animation:authrobo-fade .16s ease-out both;}\n.authrobo-card{position:relative;width:100%;max-width:360px;background:#fff;border-radius:16px;\n padding:24px;box-shadow:0 20px 60px -12px rgba(2,6,23,.4);animation:authrobo-pop .2s cubic-bezier(.16,1,.3,1) both;}\n.authrobo-close{position:absolute;top:12px;right:12px;border:0;background:transparent;cursor:pointer;\n color:#94a3b8;font-size:18px;line-height:1;padding:4px;}\n.authrobo-close:hover{color:#334155;}\n@keyframes authrobo-fade{from{opacity:0}to{opacity:1}}\n@keyframes authrobo-pop{from{opacity:0;transform:translateY(8px) scale(.97)}to{opacity:1;transform:none}}\n@media (prefers-reduced-motion: reduce){.authrobo-overlay,.authrobo-card{animation:none}}\n`;\n\nfunction useInjectStyles() {\n useEffect(() => {\n if (typeof document === \"undefined\" || document.getElementById(STYLE_ID)) return;\n const el = document.createElement(\"style\");\n el.id = STYLE_ID;\n el.textContent = CSS;\n document.head.appendChild(el);\n }, []);\n}\n\n/* ----------------------------- Button ----------------------------------- */\n\nexport function AuthRoboButton({\n provider,\n config,\n label,\n className,\n style,\n onClick,\n}: {\n provider: Method;\n config?: AuthRoboConfig;\n label?: string;\n className?: string;\n style?: React.CSSProperties;\n onClick?: () => void;\n}) {\n useInjectStyles();\n const c = useResolvedConfig(config);\n const meta = PROVIDER_META[provider];\n return (\n <button\n type=\"button\"\n className={className ? `authrobo-btn ${className}` : \"authrobo-btn\"}\n style={style}\n onClick={() => {\n onClick?.();\n startSignIn(c, provider);\n }}\n >\n <span style={{ display: \"inline-flex\", alignItems: \"center\" }}>{meta.icon}</span>\n {label ?? `Continue with ${meta.label}`}\n </button>\n );\n}\n\n/* ---------------------- Auth modal / inline panel ----------------------- */\n\nexport function AuthRoboAuth({\n config,\n mode = \"inline\",\n open = true,\n onClose,\n title,\n methods: only,\n}: {\n config?: AuthRoboConfig;\n /** \"inline\" renders in place; \"modal\" renders a centered overlay. */\n mode?: \"inline\" | \"modal\";\n /** Only used in modal mode. */\n open?: boolean;\n onClose?: () => void;\n /** Heading; defaults to \"Sign in to <app name>\". */\n title?: string;\n /** Restrict/reorder which methods to show (defaults to all enabled ones). */\n methods?: Method[];\n}) {\n useInjectStyles();\n const c = useResolvedConfig(config);\n const { data, loading, error } = useAppConfig(c);\n\n useEffect(() => {\n if (mode !== \"modal\" || !open) return;\n const onKey = (e: KeyboardEvent) => e.key === \"Escape\" && onClose?.();\n document.addEventListener(\"keydown\", onKey);\n return () => document.removeEventListener(\"keydown\", onKey);\n }, [mode, open, onClose]);\n\n if (mode === \"modal\" && !open) return null;\n\n const available = data?.methods ?? [];\n const shown = (only ? only.filter((m) => available.includes(m)) : available).filter(\n (m) => m in PROVIDER_META,\n );\n const social = shown.filter((m) => m !== \"password\");\n const hasPassword = shown.includes(\"password\");\n\n const panel = (\n <div className=\"authrobo-stack\">\n {(data?.logoUrl || data?.name) && (\n <div style={{ display: \"flex\", alignItems: \"center\", gap: 10, marginBottom: 4 }}>\n {data?.logoUrl && (\n <img src={data.logoUrl} alt=\"\" width={28} height={28} style={{ borderRadius: 6 }} />\n )}\n <span style={{ font: \"700 16px system-ui,sans-serif\", color: \"#0f172a\" }}>\n {title ?? `Sign in${data?.name ? ` to ${data.name}` : \"\"}`}\n </span>\n </div>\n )}\n\n {loading && <p style={{ color: \"#64748b\", font: \"14px system-ui,sans-serif\" }}>Loading sign-in options…</p>}\n {error && (\n <p style={{ color: \"#b91c1c\", font: \"14px system-ui,sans-serif\" }}>\n Couldn&apos;t load sign-in options.\n </p>\n )}\n\n {social.map((m) => (\n <AuthRoboButton key={m} provider={m} config={c} />\n ))}\n {hasPassword && social.length > 0 && <div className=\"authrobo-divider\">or</div>}\n {hasPassword && <AuthRoboButton provider=\"password\" config={c} label=\"Continue with email\" />}\n\n {!loading && !error && shown.length === 0 && (\n <p style={{ color: \"#64748b\", font: \"14px system-ui,sans-serif\" }}>\n No sign-in methods are enabled for this app.\n </p>\n )}\n </div>\n );\n\n if (mode === \"inline\") return panel;\n\n return (\n <div className=\"authrobo-overlay\" onClick={() => onClose?.()}>\n <div className=\"authrobo-card\" role=\"dialog\" aria-modal=\"true\" onClick={(e) => e.stopPropagation()}>\n <button type=\"button\" className=\"authrobo-close\" aria-label=\"Close\" onClick={() => onClose?.()}>\n ✕\n </button>\n {panel}\n </div>\n </div>\n );\n}\n","import * as React from \"react\";\n\n// Sign-in methods AuthRobo can surface. \"password\" routes to the hosted email\n// login page; the rest are social OAuth providers.\nexport type Method = \"google\" | \"linkedin\" | \"github\" | \"apple\" | \"password\";\n\nconst sz = { width: 18, height: 18, \"aria-hidden\": true } as const;\n\nfunction GoogleIcon() {\n return (\n <svg viewBox=\"0 0 48 48\" {...sz}>\n <path fill=\"#FFC107\" d=\"M43.6 20.5H42V20H24v8h11.3C33.7 32.9 29.3 36 24 36c-6.6 0-12-5.4-12-12s5.4-12 12-12c3.1 0 5.9 1.2 8 3.1l5.7-5.7C34.6 6.1 29.6 4 24 4 12.9 4 4 12.9 4 24s8.9 20 20 20 20-8.9 20-20c0-1.3-.1-2.3-.4-3.5z\" />\n <path fill=\"#FF3D00\" d=\"M6.3 14.7l6.6 4.8C14.7 16 19 13 24 13c3.1 0 5.9 1.2 8 3.1l5.7-5.7C34.6 6.1 29.6 4 24 4 16.3 4 9.7 8.3 6.3 14.7z\" />\n <path fill=\"#4CAF50\" d=\"M24 44c5.2 0 10-2 13.6-5.2l-6.3-5.2C29.2 35 26.7 36 24 36c-5.3 0-9.7-3.4-11.3-8.1l-6.5 5C9.5 39.6 16.2 44 24 44z\" />\n <path fill=\"#1976D2\" d=\"M43.6 20.5H42V20H24v8h11.3c-.8 2.2-2.2 4.1-4.1 5.6l6.3 5.2C41.9 36 44 30.5 44 24c0-1.3-.1-2.3-.4-3.5z\" />\n </svg>\n );\n}\n\nfunction LinkedInIcon() {\n return (\n <svg viewBox=\"0 0 24 24\" {...sz} fill=\"#0A66C2\">\n <path d=\"M20.45 20.45h-3.56v-5.57c0-1.33-.02-3.04-1.85-3.04-1.85 0-2.13 1.45-2.13 2.94v5.67H9.35V9h3.41v1.56h.05c.48-.9 1.64-1.85 3.37-1.85 3.6 0 4.27 2.37 4.27 5.45v6.29zM5.34 7.43a2.06 2.06 0 1 1 0-4.13 2.06 2.06 0 0 1 0 4.13zM7.12 20.45H3.56V9h3.56v11.45zM22.22 0H1.77C.79 0 0 .77 0 1.72v20.56C0 23.23.79 24 1.77 24h20.45c.98 0 1.78-.77 1.78-1.72V1.72C24 .77 23.2 0 22.22 0z\" />\n </svg>\n );\n}\n\nfunction GitHubIcon() {\n return (\n <svg viewBox=\"0 0 24 24\" {...sz} fill=\"#181717\">\n <path d=\"M12 .3a12 12 0 0 0-3.8 23.4c.6.1.8-.3.8-.6v-2c-3.3.7-4-1.6-4-1.6-.6-1.4-1.3-1.8-1.3-1.8-1.1-.7.1-.7.1-.7 1.2.1 1.8 1.2 1.8 1.2 1.1 1.8 2.8 1.3 3.5 1 .1-.8.4-1.3.8-1.6-2.7-.3-5.5-1.3-5.5-6a4.7 4.7 0 0 1 1.2-3.2c-.1-.3-.5-1.5.1-3.2 0 0 1-.3 3.3 1.2a11.5 11.5 0 0 1 6 0c2.3-1.5 3.3-1.2 3.3-1.2.6 1.7.2 2.9.1 3.2a4.7 4.7 0 0 1 1.2 3.2c0 4.7-2.8 5.7-5.5 6 .4.4.8 1.1.8 2.2v3.3c0 .3.2.7.8.6A12 12 0 0 0 12 .3\" />\n </svg>\n );\n}\n\nfunction AppleIcon() {\n return (\n <svg viewBox=\"0 0 24 24\" {...sz} fill=\"#000\">\n <path d=\"M17.05 12.04c-.03-2.7 2.2-4 2.3-4.06-1.25-1.84-3.2-2.09-3.9-2.12-1.66-.17-3.24.98-4.08.98-.84 0-2.14-.96-3.52-.93-1.81.03-3.48 1.05-4.41 2.67-1.88 3.27-.48 8.1 1.35 10.76.9 1.3 1.97 2.76 3.38 2.71 1.36-.05 1.87-.88 3.51-.88 1.64 0 2.1.88 3.53.85 1.46-.03 2.38-1.33 3.27-2.63 1.03-1.51 1.46-2.97 1.48-3.05-.03-.01-2.84-1.09-2.87-4.32M14.4 4.2c.74-.9 1.24-2.15 1.1-3.4-1.07.04-2.36.71-3.13 1.61-.69.8-1.29 2.07-1.13 3.29 1.19.09 2.41-.61 3.16-1.5\" />\n </svg>\n );\n}\n\nfunction MailIcon() {\n return (\n <svg viewBox=\"0 0 24 24\" {...sz} fill=\"none\" stroke=\"#334155\" strokeWidth={2} strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <rect x=\"3\" y=\"5\" width=\"18\" height=\"14\" rx=\"2\" />\n <path d=\"m3 7 9 6 9-6\" />\n </svg>\n );\n}\n\nexport const PROVIDER_META: Record<Method, { label: string; icon: React.ReactNode }> = {\n google: { label: \"Google\", icon: <GoogleIcon /> },\n linkedin: { label: \"LinkedIn\", icon: <LinkedInIcon /> },\n github: { label: \"GitHub\", icon: <GitHubIcon /> },\n apple: { label: \"Apple\", icon: <AppleIcon /> },\n password: { label: \"email\", icon: <MailIcon /> },\n};\n"],"mappings":"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","names":["import_jsx_runtime"]}
1
+ {"version":3,"sources":["../src/index.tsx","../src/icons.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { createContext, useContext, useEffect, useState } from \"react\";\nimport { PROVIDER_META, type Method } from \"./icons\";\n\nexport type { Method } from \"./icons\";\nexport { PROVIDER_META } from \"./icons\";\n\nexport interface AuthRoboConfig {\n /** Your AuthRobo instance, e.g. \"https://authrobo.com\". */\n issuerUrl: string;\n /** Your app's client_id (arc_...). */\n clientId: string;\n /** Where AuthRobo returns the one-time `code` after sign-in. */\n redirectUri: string;\n}\n\n/** Shape returned by GET /api/v1/config. */\nexport interface AppConfig {\n name: string;\n brandColor: string | null;\n logoUrl: string | null;\n methods: Method[];\n}\n\nconst ConfigContext = createContext<AuthRoboConfig | null>(null);\n\nexport function AuthRoboProvider({\n config,\n children,\n}: {\n config: AuthRoboConfig;\n children: React.ReactNode;\n}) {\n return <ConfigContext.Provider value={config}>{children}</ConfigContext.Provider>;\n}\n\nfunction useResolvedConfig(override?: AuthRoboConfig): AuthRoboConfig {\n const ctx = useContext(ConfigContext);\n const c = override ?? ctx;\n if (!c) {\n throw new Error(\n \"@authrobo/react: wrap your app in <AuthRoboProvider config={...}> or pass a `config` prop.\",\n );\n }\n return c;\n}\n\nfunction randomState(): string {\n const b = new Uint8Array(16);\n (globalThis.crypto ?? (globalThis as { crypto: Crypto }).crypto).getRandomValues(b);\n return Array.from(b, (x) => x.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\n// Email-based methods open the hosted login page (which collects the address);\n// social providers redirect straight into their OAuth start endpoint.\nconst EMAIL_METHODS: readonly Method[] = [\"password\", \"magic_link\"];\n\n/**\n * Begin sign-in: navigate the browser to AuthRobo's hosted flow for `method`.\n * Social methods hit /api/v1/auth/<provider>/start; \"password\" and \"magic_link\"\n * open the hosted email login page (which renders whichever of the two the app\n * has enabled). AuthRobo returns to your `redirectUri` with a `code` that your\n * backend exchanges (with your client_secret) for tokens.\n */\nexport function startSignIn(\n config: AuthRoboConfig,\n method: Method,\n opts?: { state?: string; loginHint?: string },\n): void {\n const base = config.issuerUrl.replace(/\\/$/, \"\");\n const state = opts?.state ?? randomState();\n try {\n sessionStorage.setItem(\"authrobo:state\", state);\n } catch {\n /* storage unavailable — state is still sent, just not persisted for a check */\n }\n const params = new URLSearchParams({\n client_id: config.clientId,\n redirect_uri: config.redirectUri,\n state,\n });\n if (opts?.loginHint) params.set(\"login_hint\", opts.loginHint);\n // Remember the method so we can badge it as \"Last used\" next time. Scoped per\n // client_id so different apps on the same origin don't clobber each other.\n try {\n localStorage.setItem(lastMethodKey(config.clientId), method);\n } catch {\n /* storage unavailable — badge just won't show next time */\n }\n const url = EMAIL_METHODS.includes(method)\n ? `${base}/login?${params.toString()}`\n : `${base}/api/v1/auth/${method}/start?${params.toString()}`;\n window.location.assign(url);\n}\n\nconst lastMethodKey = (clientId: string) => `authrobo:last-method:${clientId}`;\n\n/** The method last used to sign in for this client (from a prior startSignIn). */\nexport function useLastMethod(clientId: string): Method | null {\n const [last, setLast] = useState<Method | null>(null);\n useEffect(() => {\n try {\n setLast((localStorage.getItem(lastMethodKey(clientId)) as Method | null) ?? null);\n } catch {\n /* storage unavailable */\n }\n }, [clientId]);\n return last;\n}\n\n/** Fetch an app's public config (branding + enabled methods) for `clientId`. */\nexport function useAppConfig(config?: AuthRoboConfig): {\n data: AppConfig | null;\n loading: boolean;\n error: string | null;\n} {\n const c = useResolvedConfig(config);\n const [state, setState] = useState<{ data: AppConfig | null; loading: boolean; error: string | null }>({\n data: null,\n loading: true,\n error: null,\n });\n\n useEffect(() => {\n let active = true;\n setState({ data: null, loading: true, error: null });\n const base = c.issuerUrl.replace(/\\/$/, \"\");\n fetch(`${base}/api/v1/config?client_id=${encodeURIComponent(c.clientId)}`)\n .then((r) => (r.ok ? r.json() : Promise.reject(new Error(`config ${r.status}`))))\n .then((j) => {\n if (!active) return;\n setState({\n data: { name: j.name, brandColor: j.brand_color, logoUrl: j.logo_url, methods: j.methods },\n loading: false,\n error: null,\n });\n })\n .catch((e) => active && setState({ data: null, loading: false, error: String(e?.message ?? e) }));\n return () => {\n active = false;\n };\n }, [c.issuerUrl, c.clientId]);\n\n return state;\n}\n\n/* ----------------------------- Styling ---------------------------------- */\n\nconst STYLE_ID = \"authrobo-react-styles\";\n\n// Theme-aware: tokens default to a soft light palette, flip to dark on the OS\n// preference, and can be forced either way by a `data-theme` attribute on any\n// ancestor (`<html data-theme=\"dark\">`) — the same hook AuthRobo's own hosted\n// pages use, so the injected widgets match a host app that drives its theme\n// that way. The explicit attribute wins over the OS media query by specificity.\nconst TOKENS_LIGHT = `\n --authrobo-btn-bg:#fff;--authrobo-btn-fg:#111827;--authrobo-btn-border:#d1d5db;\n --authrobo-btn-hover-bg:#f8fafc;--authrobo-btn-hover-border:#9ca3af;\n --authrobo-card-bg:#fff;--authrobo-title-fg:#0f172a;--authrobo-muted:#64748b;\n --authrobo-error:#b91c1c;--authrobo-divider-fg:#9ca3af;--authrobo-divider-line:#e5e7eb;\n --authrobo-overlay:rgba(2,6,23,.55);--authrobo-close:#94a3b8;--authrobo-close-hover:#334155;\n --authrobo-focus:#2b8aff;--authrobo-card-shadow:rgba(2,6,23,.4);\n --authrobo-last-bg:#e7efff;--authrobo-last-fg:#2563eb;`;\nconst TOKENS_DARK = `\n --authrobo-btn-bg:#0e1626;--authrobo-btn-fg:#e6edf8;--authrobo-btn-border:#28374f;\n --authrobo-btn-hover-bg:#16233a;--authrobo-btn-hover-border:#3a4d6b;\n --authrobo-card-bg:#0b1424;--authrobo-title-fg:#f1f5fb;--authrobo-muted:#93a4bd;\n --authrobo-error:#f87171;--authrobo-divider-fg:#64748b;--authrobo-divider-line:#223049;\n --authrobo-overlay:rgba(2,6,23,.72);--authrobo-close:#64748b;--authrobo-close-hover:#cbd5e1;\n --authrobo-focus:#46c8ff;--authrobo-card-shadow:rgba(0,0,0,.6);\n --authrobo-last-bg:rgba(70,160,255,.18);--authrobo-last-fg:#8fc4ff;`;\n// Elements that either read tokens directly or need them for their descendants\n// (a standalone button has no stack ancestor; the overlay can't inherit from\n// its own card child), so every token-owning root declares the full set.\nconst TOKEN_ROOTS =\n \".authrobo-btn,.authrobo-stack,.authrobo-card,.authrobo-overlay,.authrobo-divider,.authrobo-title\";\nconst scoped = (prefix: string, tokens: string) =>\n TOKEN_ROOTS.split(\",\")\n .map((s) => `${prefix}${s.trim()}`)\n .join(\",\") + `{${tokens}}`;\n\nconst CSS = `\n${scoped(\"\", TOKENS_LIGHT)}\n@media (prefers-color-scheme:dark){${scoped(\"\", TOKENS_DARK)}}\n${scoped('[data-theme=\"light\"] ', TOKENS_LIGHT)}\n${scoped('[data-theme=\"dark\"] ', TOKENS_DARK)}\n.authrobo-btn{position:relative;display:flex;width:100%;align-items:center;justify-content:center;gap:10px;\n border:1px solid var(--authrobo-btn-border);background:var(--authrobo-btn-bg);color:var(--authrobo-btn-fg);border-radius:10px;padding:10px 14px;\n font:600 14px/1.2 system-ui,-apple-system,Segoe UI,Roboto,sans-serif;cursor:pointer;\n transition:background .15s ease,border-color .15s ease,box-shadow .15s ease;}\n.authrobo-btn:hover{background:var(--authrobo-btn-hover-bg);border-color:var(--authrobo-btn-hover-border);}\n.authrobo-btn:focus-visible{outline:2px solid var(--authrobo-focus);outline-offset:2px;}\n.authrobo-last{position:absolute;right:8px;top:50%;transform:translateY(-50%);pointer-events:none;\n font:600 10px/1 system-ui,sans-serif;letter-spacing:.02em;padding:3px 7px;border-radius:999px;\n background:var(--authrobo-last-bg);color:var(--authrobo-last-fg);}\n.authrobo-stack{display:flex;flex-direction:column;gap:10px;}\n.authrobo-title{font:700 16px system-ui,sans-serif;color:var(--authrobo-title-fg);}\n.authrobo-muted{color:var(--authrobo-muted);font:14px system-ui,sans-serif;margin:0;}\n.authrobo-error{color:var(--authrobo-error);font:14px system-ui,sans-serif;margin:0;}\n.authrobo-divider{display:flex;align-items:center;gap:10px;color:var(--authrobo-divider-fg);font:500 12px system-ui,sans-serif;}\n.authrobo-divider::before,.authrobo-divider::after{content:\"\";height:1px;flex:1;background:var(--authrobo-divider-line);}\n.authrobo-overlay{position:fixed;inset:0;z-index:2147483000;display:flex;align-items:center;\n justify-content:center;padding:16px;background:var(--authrobo-overlay);backdrop-filter:blur(2px);\n animation:authrobo-fade .16s ease-out both;}\n.authrobo-card{position:relative;width:100%;max-width:360px;background:var(--authrobo-card-bg);border-radius:16px;\n padding:24px;box-shadow:0 20px 60px -12px var(--authrobo-card-shadow);animation:authrobo-pop .2s cubic-bezier(.16,1,.3,1) both;}\n.authrobo-close{position:absolute;top:12px;right:12px;border:0;background:transparent;cursor:pointer;\n color:var(--authrobo-close);font-size:18px;line-height:1;padding:4px;}\n.authrobo-close:hover{color:var(--authrobo-close-hover);}\n@keyframes authrobo-fade{from{opacity:0}to{opacity:1}}\n@keyframes authrobo-pop{from{opacity:0;transform:translateY(8px) scale(.97)}to{opacity:1;transform:none}}\n@media (prefers-reduced-motion: reduce){.authrobo-overlay,.authrobo-card{animation:none}}\n`;\n\nfunction useInjectStyles() {\n useEffect(() => {\n if (typeof document === \"undefined\" || document.getElementById(STYLE_ID)) return;\n const el = document.createElement(\"style\");\n el.id = STYLE_ID;\n el.textContent = CSS;\n document.head.appendChild(el);\n }, []);\n}\n\n/* ----------------------------- Button ----------------------------------- */\n\nexport function AuthRoboButton({\n provider,\n config,\n label,\n className,\n style,\n onClick,\n lastUsed,\n}: {\n provider: Method;\n config?: AuthRoboConfig;\n label?: string;\n className?: string;\n style?: React.CSSProperties;\n onClick?: () => void;\n /** Show a \"Last used\" badge (the method the visitor signed in with before). */\n lastUsed?: boolean;\n}) {\n useInjectStyles();\n const c = useResolvedConfig(config);\n const meta = PROVIDER_META[provider];\n return (\n <button\n type=\"button\"\n className={className ? `authrobo-btn ${className}` : \"authrobo-btn\"}\n style={style}\n onClick={() => {\n onClick?.();\n startSignIn(c, provider);\n }}\n >\n <span style={{ display: \"inline-flex\", alignItems: \"center\" }}>{meta.icon}</span>\n {label ?? `Continue with ${meta.label}`}\n {lastUsed && <span className=\"authrobo-last\">Last used</span>}\n </button>\n );\n}\n\n/* ---------------------- Auth modal / inline panel ----------------------- */\n\nexport function AuthRoboAuth({\n config,\n mode = \"inline\",\n open = true,\n onClose,\n title,\n methods: only,\n}: {\n config?: AuthRoboConfig;\n /** \"inline\" renders in place; \"modal\" renders a centered overlay. */\n mode?: \"inline\" | \"modal\";\n /** Only used in modal mode. */\n open?: boolean;\n onClose?: () => void;\n /** Heading; defaults to \"Sign in to <app name>\". */\n title?: string;\n /** Restrict/reorder which methods to show (defaults to all enabled ones). */\n methods?: Method[];\n}) {\n useInjectStyles();\n const c = useResolvedConfig(config);\n const { data, loading, error } = useAppConfig(c);\n const lastMethod = useLastMethod(c.clientId);\n\n useEffect(() => {\n if (mode !== \"modal\" || !open) return;\n const onKey = (e: KeyboardEvent) => e.key === \"Escape\" && onClose?.();\n document.addEventListener(\"keydown\", onKey);\n return () => document.removeEventListener(\"keydown\", onKey);\n }, [mode, open, onClose]);\n\n if (mode === \"modal\" && !open) return null;\n\n const available = data?.methods ?? [];\n const shown = (only ? only.filter((m) => available.includes(m)) : available).filter(\n (m) => m in PROVIDER_META,\n );\n const social = shown.filter((m) => !EMAIL_METHODS.includes(m));\n const email = shown.filter((m) => EMAIL_METHODS.includes(m));\n // Only worth pointing out the last-used method when there's a choice to make.\n const badge = (m: Method) => shown.length > 1 && m === lastMethod;\n\n const panel = (\n <div className=\"authrobo-stack\">\n {(data?.logoUrl || data?.name) && (\n <div style={{ display: \"flex\", alignItems: \"center\", gap: 10, marginBottom: 4 }}>\n {data?.logoUrl && (\n <img src={data.logoUrl} alt=\"\" width={28} height={28} style={{ borderRadius: 6 }} />\n )}\n <span className=\"authrobo-title\">\n {title ?? `Sign in${data?.name ? ` to ${data.name}` : \"\"}`}\n </span>\n </div>\n )}\n\n {loading && <p className=\"authrobo-muted\">Loading sign-in options…</p>}\n {error && <p className=\"authrobo-error\">Couldn&apos;t load sign-in options.</p>}\n\n {social.map((m) => (\n <AuthRoboButton key={m} provider={m} config={c} lastUsed={badge(m)} />\n ))}\n {email.length > 0 && social.length > 0 && <div className=\"authrobo-divider\">or</div>}\n {email.map((m) => (\n <AuthRoboButton\n key={m}\n provider={m}\n config={c}\n label={m === \"magic_link\" ? \"Email me a sign-in link\" : \"Continue with email\"}\n lastUsed={badge(m)}\n />\n ))}\n\n {!loading && !error && shown.length === 0 && (\n <p className=\"authrobo-muted\">No sign-in methods are enabled for this app.</p>\n )}\n </div>\n );\n\n if (mode === \"inline\") return panel;\n\n return (\n <div className=\"authrobo-overlay\" onClick={() => onClose?.()}>\n <div className=\"authrobo-card\" role=\"dialog\" aria-modal=\"true\" onClick={(e) => e.stopPropagation()}>\n <button type=\"button\" className=\"authrobo-close\" aria-label=\"Close\" onClick={() => onClose?.()}>\n ✕\n </button>\n {panel}\n </div>\n </div>\n );\n}\n","import * as React from \"react\";\n\n// Sign-in methods AuthRobo can surface. \"password\" and \"magic_link\" route to\n// the hosted email login page; the rest are social OAuth providers.\nexport type Method = \"google\" | \"linkedin\" | \"github\" | \"apple\" | \"password\" | \"magic_link\";\n\nconst sz = { width: 18, height: 18, \"aria-hidden\": true } as const;\n\nfunction GoogleIcon() {\n return (\n <svg viewBox=\"0 0 48 48\" {...sz}>\n <path fill=\"#FFC107\" d=\"M43.6 20.5H42V20H24v8h11.3C33.7 32.9 29.3 36 24 36c-6.6 0-12-5.4-12-12s5.4-12 12-12c3.1 0 5.9 1.2 8 3.1l5.7-5.7C34.6 6.1 29.6 4 24 4 12.9 4 4 12.9 4 24s8.9 20 20 20 20-8.9 20-20c0-1.3-.1-2.3-.4-3.5z\" />\n <path fill=\"#FF3D00\" d=\"M6.3 14.7l6.6 4.8C14.7 16 19 13 24 13c3.1 0 5.9 1.2 8 3.1l5.7-5.7C34.6 6.1 29.6 4 24 4 16.3 4 9.7 8.3 6.3 14.7z\" />\n <path fill=\"#4CAF50\" d=\"M24 44c5.2 0 10-2 13.6-5.2l-6.3-5.2C29.2 35 26.7 36 24 36c-5.3 0-9.7-3.4-11.3-8.1l-6.5 5C9.5 39.6 16.2 44 24 44z\" />\n <path fill=\"#1976D2\" d=\"M43.6 20.5H42V20H24v8h11.3c-.8 2.2-2.2 4.1-4.1 5.6l6.3 5.2C41.9 36 44 30.5 44 24c0-1.3-.1-2.3-.4-3.5z\" />\n </svg>\n );\n}\n\nfunction LinkedInIcon() {\n return (\n <svg viewBox=\"0 0 24 24\" {...sz} fill=\"#0A66C2\">\n <path d=\"M20.45 20.45h-3.56v-5.57c0-1.33-.02-3.04-1.85-3.04-1.85 0-2.13 1.45-2.13 2.94v5.67H9.35V9h3.41v1.56h.05c.48-.9 1.64-1.85 3.37-1.85 3.6 0 4.27 2.37 4.27 5.45v6.29zM5.34 7.43a2.06 2.06 0 1 1 0-4.13 2.06 2.06 0 0 1 0 4.13zM7.12 20.45H3.56V9h3.56v11.45zM22.22 0H1.77C.79 0 0 .77 0 1.72v20.56C0 23.23.79 24 1.77 24h20.45c.98 0 1.78-.77 1.78-1.72V1.72C24 .77 23.2 0 22.22 0z\" />\n </svg>\n );\n}\n\nfunction GitHubIcon() {\n return (\n <svg viewBox=\"0 0 24 24\" {...sz} fill=\"#181717\">\n <path d=\"M12 .3a12 12 0 0 0-3.8 23.4c.6.1.8-.3.8-.6v-2c-3.3.7-4-1.6-4-1.6-.6-1.4-1.3-1.8-1.3-1.8-1.1-.7.1-.7.1-.7 1.2.1 1.8 1.2 1.8 1.2 1.1 1.8 2.8 1.3 3.5 1 .1-.8.4-1.3.8-1.6-2.7-.3-5.5-1.3-5.5-6a4.7 4.7 0 0 1 1.2-3.2c-.1-.3-.5-1.5.1-3.2 0 0 1-.3 3.3 1.2a11.5 11.5 0 0 1 6 0c2.3-1.5 3.3-1.2 3.3-1.2.6 1.7.2 2.9.1 3.2a4.7 4.7 0 0 1 1.2 3.2c0 4.7-2.8 5.7-5.5 6 .4.4.8 1.1.8 2.2v3.3c0 .3.2.7.8.6A12 12 0 0 0 12 .3\" />\n </svg>\n );\n}\n\nfunction AppleIcon() {\n return (\n <svg viewBox=\"0 0 24 24\" {...sz} fill=\"#000\">\n <path d=\"M17.05 12.04c-.03-2.7 2.2-4 2.3-4.06-1.25-1.84-3.2-2.09-3.9-2.12-1.66-.17-3.24.98-4.08.98-.84 0-2.14-.96-3.52-.93-1.81.03-3.48 1.05-4.41 2.67-1.88 3.27-.48 8.1 1.35 10.76.9 1.3 1.97 2.76 3.38 2.71 1.36-.05 1.87-.88 3.51-.88 1.64 0 2.1.88 3.53.85 1.46-.03 2.38-1.33 3.27-2.63 1.03-1.51 1.46-2.97 1.48-3.05-.03-.01-2.84-1.09-2.87-4.32M14.4 4.2c.74-.9 1.24-2.15 1.1-3.4-1.07.04-2.36.71-3.13 1.61-.69.8-1.29 2.07-1.13 3.29 1.19.09 2.41-.61 3.16-1.5\" />\n </svg>\n );\n}\n\nfunction MailIcon() {\n return (\n <svg viewBox=\"0 0 24 24\" {...sz} fill=\"none\" stroke=\"#334155\" strokeWidth={2} strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <rect x=\"3\" y=\"5\" width=\"18\" height=\"14\" rx=\"2\" />\n <path d=\"m3 7 9 6 9-6\" />\n </svg>\n );\n}\n\nexport const PROVIDER_META: Record<Method, { label: string; icon: React.ReactNode }> = {\n google: { label: \"Google\", icon: <GoogleIcon /> },\n linkedin: { label: \"LinkedIn\", icon: <LinkedInIcon /> },\n github: { label: \"GitHub\", icon: <GitHubIcon /> },\n apple: { label: \"Apple\", icon: <AppleIcon /> },\n password: { label: \"email\", icon: <MailIcon /> },\n magic_link: { label: \"magic link\", icon: <MailIcon /> },\n};\n"],"mappings":"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","names":["import_jsx_runtime"]}
package/dist/index.d.cts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as React from 'react';
2
2
 
3
- type Method = "google" | "linkedin" | "github" | "apple" | "password";
3
+ type Method = "google" | "linkedin" | "github" | "apple" | "password" | "magic_link";
4
4
  declare const PROVIDER_META: Record<Method, {
5
5
  label: string;
6
6
  icon: React.ReactNode;
@@ -27,27 +27,32 @@ declare function AuthRoboProvider({ config, children, }: {
27
27
  }): React.JSX.Element;
28
28
  /**
29
29
  * Begin sign-in: navigate the browser to AuthRobo's hosted flow for `method`.
30
- * Social methods hit /api/v1/auth/<provider>/start; "password" opens the hosted
31
- * email login page. AuthRobo returns to your `redirectUri` with a `code` that
32
- * your backend exchanges (with your client_secret) for tokens.
30
+ * Social methods hit /api/v1/auth/<provider>/start; "password" and "magic_link"
31
+ * open the hosted email login page (which renders whichever of the two the app
32
+ * has enabled). AuthRobo returns to your `redirectUri` with a `code` that your
33
+ * backend exchanges (with your client_secret) for tokens.
33
34
  */
34
35
  declare function startSignIn(config: AuthRoboConfig, method: Method, opts?: {
35
36
  state?: string;
36
37
  loginHint?: string;
37
38
  }): void;
39
+ /** The method last used to sign in for this client (from a prior startSignIn). */
40
+ declare function useLastMethod(clientId: string): Method | null;
38
41
  /** Fetch an app's public config (branding + enabled methods) for `clientId`. */
39
42
  declare function useAppConfig(config?: AuthRoboConfig): {
40
43
  data: AppConfig | null;
41
44
  loading: boolean;
42
45
  error: string | null;
43
46
  };
44
- declare function AuthRoboButton({ provider, config, label, className, style, onClick, }: {
47
+ declare function AuthRoboButton({ provider, config, label, className, style, onClick, lastUsed, }: {
45
48
  provider: Method;
46
49
  config?: AuthRoboConfig;
47
50
  label?: string;
48
51
  className?: string;
49
52
  style?: React.CSSProperties;
50
53
  onClick?: () => void;
54
+ /** Show a "Last used" badge (the method the visitor signed in with before). */
55
+ lastUsed?: boolean;
51
56
  }): React.JSX.Element;
52
57
  declare function AuthRoboAuth({ config, mode, open, onClose, title, methods: only, }: {
53
58
  config?: AuthRoboConfig;
@@ -62,4 +67,4 @@ declare function AuthRoboAuth({ config, mode, open, onClose, title, methods: onl
62
67
  methods?: Method[];
63
68
  }): React.JSX.Element | null;
64
69
 
65
- export { type AppConfig, AuthRoboAuth, AuthRoboButton, type AuthRoboConfig, AuthRoboProvider, type Method, PROVIDER_META, startSignIn, useAppConfig };
70
+ export { type AppConfig, AuthRoboAuth, AuthRoboButton, type AuthRoboConfig, AuthRoboProvider, type Method, PROVIDER_META, startSignIn, useAppConfig, useLastMethod };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as React from 'react';
2
2
 
3
- type Method = "google" | "linkedin" | "github" | "apple" | "password";
3
+ type Method = "google" | "linkedin" | "github" | "apple" | "password" | "magic_link";
4
4
  declare const PROVIDER_META: Record<Method, {
5
5
  label: string;
6
6
  icon: React.ReactNode;
@@ -27,27 +27,32 @@ declare function AuthRoboProvider({ config, children, }: {
27
27
  }): React.JSX.Element;
28
28
  /**
29
29
  * Begin sign-in: navigate the browser to AuthRobo's hosted flow for `method`.
30
- * Social methods hit /api/v1/auth/<provider>/start; "password" opens the hosted
31
- * email login page. AuthRobo returns to your `redirectUri` with a `code` that
32
- * your backend exchanges (with your client_secret) for tokens.
30
+ * Social methods hit /api/v1/auth/<provider>/start; "password" and "magic_link"
31
+ * open the hosted email login page (which renders whichever of the two the app
32
+ * has enabled). AuthRobo returns to your `redirectUri` with a `code` that your
33
+ * backend exchanges (with your client_secret) for tokens.
33
34
  */
34
35
  declare function startSignIn(config: AuthRoboConfig, method: Method, opts?: {
35
36
  state?: string;
36
37
  loginHint?: string;
37
38
  }): void;
39
+ /** The method last used to sign in for this client (from a prior startSignIn). */
40
+ declare function useLastMethod(clientId: string): Method | null;
38
41
  /** Fetch an app's public config (branding + enabled methods) for `clientId`. */
39
42
  declare function useAppConfig(config?: AuthRoboConfig): {
40
43
  data: AppConfig | null;
41
44
  loading: boolean;
42
45
  error: string | null;
43
46
  };
44
- declare function AuthRoboButton({ provider, config, label, className, style, onClick, }: {
47
+ declare function AuthRoboButton({ provider, config, label, className, style, onClick, lastUsed, }: {
45
48
  provider: Method;
46
49
  config?: AuthRoboConfig;
47
50
  label?: string;
48
51
  className?: string;
49
52
  style?: React.CSSProperties;
50
53
  onClick?: () => void;
54
+ /** Show a "Last used" badge (the method the visitor signed in with before). */
55
+ lastUsed?: boolean;
51
56
  }): React.JSX.Element;
52
57
  declare function AuthRoboAuth({ config, mode, open, onClose, title, methods: only, }: {
53
58
  config?: AuthRoboConfig;
@@ -62,4 +67,4 @@ declare function AuthRoboAuth({ config, mode, open, onClose, title, methods: onl
62
67
  methods?: Method[];
63
68
  }): React.JSX.Element | null;
64
69
 
65
- export { type AppConfig, AuthRoboAuth, AuthRoboButton, type AuthRoboConfig, AuthRoboProvider, type Method, PROVIDER_META, startSignIn, useAppConfig };
70
+ export { type AppConfig, AuthRoboAuth, AuthRoboButton, type AuthRoboConfig, AuthRoboProvider, type Method, PROVIDER_META, startSignIn, useAppConfig, useLastMethod };
package/dist/index.js CHANGED
@@ -35,7 +35,8 @@ var PROVIDER_META = {
35
35
  linkedin: { label: "LinkedIn", icon: /* @__PURE__ */ jsx(LinkedInIcon, {}) },
36
36
  github: { label: "GitHub", icon: /* @__PURE__ */ jsx(GitHubIcon, {}) },
37
37
  apple: { label: "Apple", icon: /* @__PURE__ */ jsx(AppleIcon, {}) },
38
- password: { label: "email", icon: /* @__PURE__ */ jsx(MailIcon, {}) }
38
+ password: { label: "email", icon: /* @__PURE__ */ jsx(MailIcon, {}) },
39
+ magic_link: { label: "magic link", icon: /* @__PURE__ */ jsx(MailIcon, {}) }
39
40
  };
40
41
 
41
42
  // src/index.tsx
@@ -62,6 +63,7 @@ function randomState() {
62
63
  (globalThis.crypto ?? globalThis.crypto).getRandomValues(b);
63
64
  return Array.from(b, (x) => x.toString(16).padStart(2, "0")).join("");
64
65
  }
66
+ var EMAIL_METHODS = ["password", "magic_link"];
65
67
  function startSignIn(config, method, opts) {
66
68
  const base = config.issuerUrl.replace(/\/$/, "");
67
69
  const state = opts?.state ?? randomState();
@@ -75,9 +77,24 @@ function startSignIn(config, method, opts) {
75
77
  state
76
78
  });
77
79
  if (opts?.loginHint) params.set("login_hint", opts.loginHint);
78
- const url = method === "password" ? `${base}/login?${params.toString()}` : `${base}/api/v1/auth/${method}/start?${params.toString()}`;
80
+ try {
81
+ localStorage.setItem(lastMethodKey(config.clientId), method);
82
+ } catch {
83
+ }
84
+ const url = EMAIL_METHODS.includes(method) ? `${base}/login?${params.toString()}` : `${base}/api/v1/auth/${method}/start?${params.toString()}`;
79
85
  window.location.assign(url);
80
86
  }
87
+ var lastMethodKey = (clientId) => `authrobo:last-method:${clientId}`;
88
+ function useLastMethod(clientId) {
89
+ const [last, setLast] = useState(null);
90
+ useEffect(() => {
91
+ try {
92
+ setLast(localStorage.getItem(lastMethodKey(clientId)) ?? null);
93
+ } catch {
94
+ }
95
+ }, [clientId]);
96
+ return last;
97
+ }
81
98
  function useAppConfig(config) {
82
99
  const c = useResolvedConfig(config);
83
100
  const [state, setState] = useState({
@@ -104,24 +121,52 @@ function useAppConfig(config) {
104
121
  return state;
105
122
  }
106
123
  var STYLE_ID = "authrobo-react-styles";
124
+ var TOKENS_LIGHT = `
125
+ --authrobo-btn-bg:#fff;--authrobo-btn-fg:#111827;--authrobo-btn-border:#d1d5db;
126
+ --authrobo-btn-hover-bg:#f8fafc;--authrobo-btn-hover-border:#9ca3af;
127
+ --authrobo-card-bg:#fff;--authrobo-title-fg:#0f172a;--authrobo-muted:#64748b;
128
+ --authrobo-error:#b91c1c;--authrobo-divider-fg:#9ca3af;--authrobo-divider-line:#e5e7eb;
129
+ --authrobo-overlay:rgba(2,6,23,.55);--authrobo-close:#94a3b8;--authrobo-close-hover:#334155;
130
+ --authrobo-focus:#2b8aff;--authrobo-card-shadow:rgba(2,6,23,.4);
131
+ --authrobo-last-bg:#e7efff;--authrobo-last-fg:#2563eb;`;
132
+ var TOKENS_DARK = `
133
+ --authrobo-btn-bg:#0e1626;--authrobo-btn-fg:#e6edf8;--authrobo-btn-border:#28374f;
134
+ --authrobo-btn-hover-bg:#16233a;--authrobo-btn-hover-border:#3a4d6b;
135
+ --authrobo-card-bg:#0b1424;--authrobo-title-fg:#f1f5fb;--authrobo-muted:#93a4bd;
136
+ --authrobo-error:#f87171;--authrobo-divider-fg:#64748b;--authrobo-divider-line:#223049;
137
+ --authrobo-overlay:rgba(2,6,23,.72);--authrobo-close:#64748b;--authrobo-close-hover:#cbd5e1;
138
+ --authrobo-focus:#46c8ff;--authrobo-card-shadow:rgba(0,0,0,.6);
139
+ --authrobo-last-bg:rgba(70,160,255,.18);--authrobo-last-fg:#8fc4ff;`;
140
+ var TOKEN_ROOTS = ".authrobo-btn,.authrobo-stack,.authrobo-card,.authrobo-overlay,.authrobo-divider,.authrobo-title";
141
+ var scoped = (prefix, tokens) => TOKEN_ROOTS.split(",").map((s) => `${prefix}${s.trim()}`).join(",") + `{${tokens}}`;
107
142
  var CSS = `
108
- .authrobo-btn{display:flex;width:100%;align-items:center;justify-content:center;gap:10px;
109
- border:1px solid #d1d5db;background:#fff;color:#111827;border-radius:10px;padding:10px 14px;
143
+ ${scoped("", TOKENS_LIGHT)}
144
+ @media (prefers-color-scheme:dark){${scoped("", TOKENS_DARK)}}
145
+ ${scoped('[data-theme="light"] ', TOKENS_LIGHT)}
146
+ ${scoped('[data-theme="dark"] ', TOKENS_DARK)}
147
+ .authrobo-btn{position:relative;display:flex;width:100%;align-items:center;justify-content:center;gap:10px;
148
+ border:1px solid var(--authrobo-btn-border);background:var(--authrobo-btn-bg);color:var(--authrobo-btn-fg);border-radius:10px;padding:10px 14px;
110
149
  font:600 14px/1.2 system-ui,-apple-system,Segoe UI,Roboto,sans-serif;cursor:pointer;
111
150
  transition:background .15s ease,border-color .15s ease,box-shadow .15s ease;}
112
- .authrobo-btn:hover{background:#f8fafc;border-color:#9ca3af;}
113
- .authrobo-btn:focus-visible{outline:2px solid #2b8aff;outline-offset:2px;}
151
+ .authrobo-btn:hover{background:var(--authrobo-btn-hover-bg);border-color:var(--authrobo-btn-hover-border);}
152
+ .authrobo-btn:focus-visible{outline:2px solid var(--authrobo-focus);outline-offset:2px;}
153
+ .authrobo-last{position:absolute;right:8px;top:50%;transform:translateY(-50%);pointer-events:none;
154
+ font:600 10px/1 system-ui,sans-serif;letter-spacing:.02em;padding:3px 7px;border-radius:999px;
155
+ background:var(--authrobo-last-bg);color:var(--authrobo-last-fg);}
114
156
  .authrobo-stack{display:flex;flex-direction:column;gap:10px;}
115
- .authrobo-divider{display:flex;align-items:center;gap:10px;color:#9ca3af;font:500 12px system-ui,sans-serif;}
116
- .authrobo-divider::before,.authrobo-divider::after{content:"";height:1px;flex:1;background:#e5e7eb;}
157
+ .authrobo-title{font:700 16px system-ui,sans-serif;color:var(--authrobo-title-fg);}
158
+ .authrobo-muted{color:var(--authrobo-muted);font:14px system-ui,sans-serif;margin:0;}
159
+ .authrobo-error{color:var(--authrobo-error);font:14px system-ui,sans-serif;margin:0;}
160
+ .authrobo-divider{display:flex;align-items:center;gap:10px;color:var(--authrobo-divider-fg);font:500 12px system-ui,sans-serif;}
161
+ .authrobo-divider::before,.authrobo-divider::after{content:"";height:1px;flex:1;background:var(--authrobo-divider-line);}
117
162
  .authrobo-overlay{position:fixed;inset:0;z-index:2147483000;display:flex;align-items:center;
118
- justify-content:center;padding:16px;background:rgba(2,6,23,.55);backdrop-filter:blur(2px);
163
+ justify-content:center;padding:16px;background:var(--authrobo-overlay);backdrop-filter:blur(2px);
119
164
  animation:authrobo-fade .16s ease-out both;}
120
- .authrobo-card{position:relative;width:100%;max-width:360px;background:#fff;border-radius:16px;
121
- padding:24px;box-shadow:0 20px 60px -12px rgba(2,6,23,.4);animation:authrobo-pop .2s cubic-bezier(.16,1,.3,1) both;}
165
+ .authrobo-card{position:relative;width:100%;max-width:360px;background:var(--authrobo-card-bg);border-radius:16px;
166
+ padding:24px;box-shadow:0 20px 60px -12px var(--authrobo-card-shadow);animation:authrobo-pop .2s cubic-bezier(.16,1,.3,1) both;}
122
167
  .authrobo-close{position:absolute;top:12px;right:12px;border:0;background:transparent;cursor:pointer;
123
- color:#94a3b8;font-size:18px;line-height:1;padding:4px;}
124
- .authrobo-close:hover{color:#334155;}
168
+ color:var(--authrobo-close);font-size:18px;line-height:1;padding:4px;}
169
+ .authrobo-close:hover{color:var(--authrobo-close-hover);}
125
170
  @keyframes authrobo-fade{from{opacity:0}to{opacity:1}}
126
171
  @keyframes authrobo-pop{from{opacity:0;transform:translateY(8px) scale(.97)}to{opacity:1;transform:none}}
127
172
  @media (prefers-reduced-motion: reduce){.authrobo-overlay,.authrobo-card{animation:none}}
@@ -141,7 +186,8 @@ function AuthRoboButton({
141
186
  label,
142
187
  className,
143
188
  style,
144
- onClick
189
+ onClick,
190
+ lastUsed
145
191
  }) {
146
192
  useInjectStyles();
147
193
  const c = useResolvedConfig(config);
@@ -158,7 +204,8 @@ function AuthRoboButton({
158
204
  },
159
205
  children: [
160
206
  /* @__PURE__ */ jsx2("span", { style: { display: "inline-flex", alignItems: "center" }, children: meta.icon }),
161
- label ?? `Continue with ${meta.label}`
207
+ label ?? `Continue with ${meta.label}`,
208
+ lastUsed && /* @__PURE__ */ jsx2("span", { className: "authrobo-last", children: "Last used" })
162
209
  ]
163
210
  }
164
211
  );
@@ -174,6 +221,7 @@ function AuthRoboAuth({
174
221
  useInjectStyles();
175
222
  const c = useResolvedConfig(config);
176
223
  const { data, loading, error } = useAppConfig(c);
224
+ const lastMethod = useLastMethod(c.clientId);
177
225
  useEffect(() => {
178
226
  if (mode !== "modal" || !open) return;
179
227
  const onKey = (e) => e.key === "Escape" && onClose?.();
@@ -185,19 +233,29 @@ function AuthRoboAuth({
185
233
  const shown = (only ? only.filter((m) => available.includes(m)) : available).filter(
186
234
  (m) => m in PROVIDER_META
187
235
  );
188
- const social = shown.filter((m) => m !== "password");
189
- const hasPassword = shown.includes("password");
236
+ const social = shown.filter((m) => !EMAIL_METHODS.includes(m));
237
+ const email = shown.filter((m) => EMAIL_METHODS.includes(m));
238
+ const badge = (m) => shown.length > 1 && m === lastMethod;
190
239
  const panel = /* @__PURE__ */ jsxs2("div", { className: "authrobo-stack", children: [
191
240
  (data?.logoUrl || data?.name) && /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", gap: 10, marginBottom: 4 }, children: [
192
241
  data?.logoUrl && /* @__PURE__ */ jsx2("img", { src: data.logoUrl, alt: "", width: 28, height: 28, style: { borderRadius: 6 } }),
193
- /* @__PURE__ */ jsx2("span", { style: { font: "700 16px system-ui,sans-serif", color: "#0f172a" }, children: title ?? `Sign in${data?.name ? ` to ${data.name}` : ""}` })
242
+ /* @__PURE__ */ jsx2("span", { className: "authrobo-title", children: title ?? `Sign in${data?.name ? ` to ${data.name}` : ""}` })
194
243
  ] }),
195
- loading && /* @__PURE__ */ jsx2("p", { style: { color: "#64748b", font: "14px system-ui,sans-serif" }, children: "Loading sign-in options\u2026" }),
196
- error && /* @__PURE__ */ jsx2("p", { style: { color: "#b91c1c", font: "14px system-ui,sans-serif" }, children: "Couldn't load sign-in options." }),
197
- social.map((m) => /* @__PURE__ */ jsx2(AuthRoboButton, { provider: m, config: c }, m)),
198
- hasPassword && social.length > 0 && /* @__PURE__ */ jsx2("div", { className: "authrobo-divider", children: "or" }),
199
- hasPassword && /* @__PURE__ */ jsx2(AuthRoboButton, { provider: "password", config: c, label: "Continue with email" }),
200
- !loading && !error && shown.length === 0 && /* @__PURE__ */ jsx2("p", { style: { color: "#64748b", font: "14px system-ui,sans-serif" }, children: "No sign-in methods are enabled for this app." })
244
+ loading && /* @__PURE__ */ jsx2("p", { className: "authrobo-muted", children: "Loading sign-in options\u2026" }),
245
+ error && /* @__PURE__ */ jsx2("p", { className: "authrobo-error", children: "Couldn't load sign-in options." }),
246
+ social.map((m) => /* @__PURE__ */ jsx2(AuthRoboButton, { provider: m, config: c, lastUsed: badge(m) }, m)),
247
+ email.length > 0 && social.length > 0 && /* @__PURE__ */ jsx2("div", { className: "authrobo-divider", children: "or" }),
248
+ email.map((m) => /* @__PURE__ */ jsx2(
249
+ AuthRoboButton,
250
+ {
251
+ provider: m,
252
+ config: c,
253
+ label: m === "magic_link" ? "Email me a sign-in link" : "Continue with email",
254
+ lastUsed: badge(m)
255
+ },
256
+ m
257
+ )),
258
+ !loading && !error && shown.length === 0 && /* @__PURE__ */ jsx2("p", { className: "authrobo-muted", children: "No sign-in methods are enabled for this app." })
201
259
  ] });
202
260
  if (mode === "inline") return panel;
203
261
  return /* @__PURE__ */ jsx2("div", { className: "authrobo-overlay", onClick: () => onClose?.(), children: /* @__PURE__ */ jsxs2("div", { className: "authrobo-card", role: "dialog", "aria-modal": "true", onClick: (e) => e.stopPropagation(), children: [
@@ -211,6 +269,7 @@ export {
211
269
  AuthRoboProvider,
212
270
  PROVIDER_META,
213
271
  startSignIn,
214
- useAppConfig
272
+ useAppConfig,
273
+ useLastMethod
215
274
  };
216
275
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.tsx","../src/icons.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { createContext, useContext, useEffect, useState } from \"react\";\nimport { PROVIDER_META, type Method } from \"./icons\";\n\nexport type { Method } from \"./icons\";\nexport { PROVIDER_META } from \"./icons\";\n\nexport interface AuthRoboConfig {\n /** Your AuthRobo instance, e.g. \"https://authrobo.com\". */\n issuerUrl: string;\n /** Your app's client_id (arc_...). */\n clientId: string;\n /** Where AuthRobo returns the one-time `code` after sign-in. */\n redirectUri: string;\n}\n\n/** Shape returned by GET /api/v1/config. */\nexport interface AppConfig {\n name: string;\n brandColor: string | null;\n logoUrl: string | null;\n methods: Method[];\n}\n\nconst ConfigContext = createContext<AuthRoboConfig | null>(null);\n\nexport function AuthRoboProvider({\n config,\n children,\n}: {\n config: AuthRoboConfig;\n children: React.ReactNode;\n}) {\n return <ConfigContext.Provider value={config}>{children}</ConfigContext.Provider>;\n}\n\nfunction useResolvedConfig(override?: AuthRoboConfig): AuthRoboConfig {\n const ctx = useContext(ConfigContext);\n const c = override ?? ctx;\n if (!c) {\n throw new Error(\n \"@authrobo/react: wrap your app in <AuthRoboProvider config={...}> or pass a `config` prop.\",\n );\n }\n return c;\n}\n\nfunction randomState(): string {\n const b = new Uint8Array(16);\n (globalThis.crypto ?? (globalThis as { crypto: Crypto }).crypto).getRandomValues(b);\n return Array.from(b, (x) => x.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\n/**\n * Begin sign-in: navigate the browser to AuthRobo's hosted flow for `method`.\n * Social methods hit /api/v1/auth/<provider>/start; \"password\" opens the hosted\n * email login page. AuthRobo returns to your `redirectUri` with a `code` that\n * your backend exchanges (with your client_secret) for tokens.\n */\nexport function startSignIn(\n config: AuthRoboConfig,\n method: Method,\n opts?: { state?: string; loginHint?: string },\n): void {\n const base = config.issuerUrl.replace(/\\/$/, \"\");\n const state = opts?.state ?? randomState();\n try {\n sessionStorage.setItem(\"authrobo:state\", state);\n } catch {\n /* storage unavailable — state is still sent, just not persisted for a check */\n }\n const params = new URLSearchParams({\n client_id: config.clientId,\n redirect_uri: config.redirectUri,\n state,\n });\n if (opts?.loginHint) params.set(\"login_hint\", opts.loginHint);\n const url =\n method === \"password\"\n ? `${base}/login?${params.toString()}`\n : `${base}/api/v1/auth/${method}/start?${params.toString()}`;\n window.location.assign(url);\n}\n\n/** Fetch an app's public config (branding + enabled methods) for `clientId`. */\nexport function useAppConfig(config?: AuthRoboConfig): {\n data: AppConfig | null;\n loading: boolean;\n error: string | null;\n} {\n const c = useResolvedConfig(config);\n const [state, setState] = useState<{ data: AppConfig | null; loading: boolean; error: string | null }>({\n data: null,\n loading: true,\n error: null,\n });\n\n useEffect(() => {\n let active = true;\n setState({ data: null, loading: true, error: null });\n const base = c.issuerUrl.replace(/\\/$/, \"\");\n fetch(`${base}/api/v1/config?client_id=${encodeURIComponent(c.clientId)}`)\n .then((r) => (r.ok ? r.json() : Promise.reject(new Error(`config ${r.status}`))))\n .then((j) => {\n if (!active) return;\n setState({\n data: { name: j.name, brandColor: j.brand_color, logoUrl: j.logo_url, methods: j.methods },\n loading: false,\n error: null,\n });\n })\n .catch((e) => active && setState({ data: null, loading: false, error: String(e?.message ?? e) }));\n return () => {\n active = false;\n };\n }, [c.issuerUrl, c.clientId]);\n\n return state;\n}\n\n/* ----------------------------- Styling ---------------------------------- */\n\nconst STYLE_ID = \"authrobo-react-styles\";\nconst CSS = `\n.authrobo-btn{display:flex;width:100%;align-items:center;justify-content:center;gap:10px;\n border:1px solid #d1d5db;background:#fff;color:#111827;border-radius:10px;padding:10px 14px;\n font:600 14px/1.2 system-ui,-apple-system,Segoe UI,Roboto,sans-serif;cursor:pointer;\n transition:background .15s ease,border-color .15s ease,box-shadow .15s ease;}\n.authrobo-btn:hover{background:#f8fafc;border-color:#9ca3af;}\n.authrobo-btn:focus-visible{outline:2px solid #2b8aff;outline-offset:2px;}\n.authrobo-stack{display:flex;flex-direction:column;gap:10px;}\n.authrobo-divider{display:flex;align-items:center;gap:10px;color:#9ca3af;font:500 12px system-ui,sans-serif;}\n.authrobo-divider::before,.authrobo-divider::after{content:\"\";height:1px;flex:1;background:#e5e7eb;}\n.authrobo-overlay{position:fixed;inset:0;z-index:2147483000;display:flex;align-items:center;\n justify-content:center;padding:16px;background:rgba(2,6,23,.55);backdrop-filter:blur(2px);\n animation:authrobo-fade .16s ease-out both;}\n.authrobo-card{position:relative;width:100%;max-width:360px;background:#fff;border-radius:16px;\n padding:24px;box-shadow:0 20px 60px -12px rgba(2,6,23,.4);animation:authrobo-pop .2s cubic-bezier(.16,1,.3,1) both;}\n.authrobo-close{position:absolute;top:12px;right:12px;border:0;background:transparent;cursor:pointer;\n color:#94a3b8;font-size:18px;line-height:1;padding:4px;}\n.authrobo-close:hover{color:#334155;}\n@keyframes authrobo-fade{from{opacity:0}to{opacity:1}}\n@keyframes authrobo-pop{from{opacity:0;transform:translateY(8px) scale(.97)}to{opacity:1;transform:none}}\n@media (prefers-reduced-motion: reduce){.authrobo-overlay,.authrobo-card{animation:none}}\n`;\n\nfunction useInjectStyles() {\n useEffect(() => {\n if (typeof document === \"undefined\" || document.getElementById(STYLE_ID)) return;\n const el = document.createElement(\"style\");\n el.id = STYLE_ID;\n el.textContent = CSS;\n document.head.appendChild(el);\n }, []);\n}\n\n/* ----------------------------- Button ----------------------------------- */\n\nexport function AuthRoboButton({\n provider,\n config,\n label,\n className,\n style,\n onClick,\n}: {\n provider: Method;\n config?: AuthRoboConfig;\n label?: string;\n className?: string;\n style?: React.CSSProperties;\n onClick?: () => void;\n}) {\n useInjectStyles();\n const c = useResolvedConfig(config);\n const meta = PROVIDER_META[provider];\n return (\n <button\n type=\"button\"\n className={className ? `authrobo-btn ${className}` : \"authrobo-btn\"}\n style={style}\n onClick={() => {\n onClick?.();\n startSignIn(c, provider);\n }}\n >\n <span style={{ display: \"inline-flex\", alignItems: \"center\" }}>{meta.icon}</span>\n {label ?? `Continue with ${meta.label}`}\n </button>\n );\n}\n\n/* ---------------------- Auth modal / inline panel ----------------------- */\n\nexport function AuthRoboAuth({\n config,\n mode = \"inline\",\n open = true,\n onClose,\n title,\n methods: only,\n}: {\n config?: AuthRoboConfig;\n /** \"inline\" renders in place; \"modal\" renders a centered overlay. */\n mode?: \"inline\" | \"modal\";\n /** Only used in modal mode. */\n open?: boolean;\n onClose?: () => void;\n /** Heading; defaults to \"Sign in to <app name>\". */\n title?: string;\n /** Restrict/reorder which methods to show (defaults to all enabled ones). */\n methods?: Method[];\n}) {\n useInjectStyles();\n const c = useResolvedConfig(config);\n const { data, loading, error } = useAppConfig(c);\n\n useEffect(() => {\n if (mode !== \"modal\" || !open) return;\n const onKey = (e: KeyboardEvent) => e.key === \"Escape\" && onClose?.();\n document.addEventListener(\"keydown\", onKey);\n return () => document.removeEventListener(\"keydown\", onKey);\n }, [mode, open, onClose]);\n\n if (mode === \"modal\" && !open) return null;\n\n const available = data?.methods ?? [];\n const shown = (only ? only.filter((m) => available.includes(m)) : available).filter(\n (m) => m in PROVIDER_META,\n );\n const social = shown.filter((m) => m !== \"password\");\n const hasPassword = shown.includes(\"password\");\n\n const panel = (\n <div className=\"authrobo-stack\">\n {(data?.logoUrl || data?.name) && (\n <div style={{ display: \"flex\", alignItems: \"center\", gap: 10, marginBottom: 4 }}>\n {data?.logoUrl && (\n <img src={data.logoUrl} alt=\"\" width={28} height={28} style={{ borderRadius: 6 }} />\n )}\n <span style={{ font: \"700 16px system-ui,sans-serif\", color: \"#0f172a\" }}>\n {title ?? `Sign in${data?.name ? ` to ${data.name}` : \"\"}`}\n </span>\n </div>\n )}\n\n {loading && <p style={{ color: \"#64748b\", font: \"14px system-ui,sans-serif\" }}>Loading sign-in options…</p>}\n {error && (\n <p style={{ color: \"#b91c1c\", font: \"14px system-ui,sans-serif\" }}>\n Couldn&apos;t load sign-in options.\n </p>\n )}\n\n {social.map((m) => (\n <AuthRoboButton key={m} provider={m} config={c} />\n ))}\n {hasPassword && social.length > 0 && <div className=\"authrobo-divider\">or</div>}\n {hasPassword && <AuthRoboButton provider=\"password\" config={c} label=\"Continue with email\" />}\n\n {!loading && !error && shown.length === 0 && (\n <p style={{ color: \"#64748b\", font: \"14px system-ui,sans-serif\" }}>\n No sign-in methods are enabled for this app.\n </p>\n )}\n </div>\n );\n\n if (mode === \"inline\") return panel;\n\n return (\n <div className=\"authrobo-overlay\" onClick={() => onClose?.()}>\n <div className=\"authrobo-card\" role=\"dialog\" aria-modal=\"true\" onClick={(e) => e.stopPropagation()}>\n <button type=\"button\" className=\"authrobo-close\" aria-label=\"Close\" onClick={() => onClose?.()}>\n ✕\n </button>\n {panel}\n </div>\n </div>\n );\n}\n","import * as React from \"react\";\n\n// Sign-in methods AuthRobo can surface. \"password\" routes to the hosted email\n// login page; the rest are social OAuth providers.\nexport type Method = \"google\" | \"linkedin\" | \"github\" | \"apple\" | \"password\";\n\nconst sz = { width: 18, height: 18, \"aria-hidden\": true } as const;\n\nfunction GoogleIcon() {\n return (\n <svg viewBox=\"0 0 48 48\" {...sz}>\n <path fill=\"#FFC107\" d=\"M43.6 20.5H42V20H24v8h11.3C33.7 32.9 29.3 36 24 36c-6.6 0-12-5.4-12-12s5.4-12 12-12c3.1 0 5.9 1.2 8 3.1l5.7-5.7C34.6 6.1 29.6 4 24 4 12.9 4 4 12.9 4 24s8.9 20 20 20 20-8.9 20-20c0-1.3-.1-2.3-.4-3.5z\" />\n <path fill=\"#FF3D00\" d=\"M6.3 14.7l6.6 4.8C14.7 16 19 13 24 13c3.1 0 5.9 1.2 8 3.1l5.7-5.7C34.6 6.1 29.6 4 24 4 16.3 4 9.7 8.3 6.3 14.7z\" />\n <path fill=\"#4CAF50\" d=\"M24 44c5.2 0 10-2 13.6-5.2l-6.3-5.2C29.2 35 26.7 36 24 36c-5.3 0-9.7-3.4-11.3-8.1l-6.5 5C9.5 39.6 16.2 44 24 44z\" />\n <path fill=\"#1976D2\" d=\"M43.6 20.5H42V20H24v8h11.3c-.8 2.2-2.2 4.1-4.1 5.6l6.3 5.2C41.9 36 44 30.5 44 24c0-1.3-.1-2.3-.4-3.5z\" />\n </svg>\n );\n}\n\nfunction LinkedInIcon() {\n return (\n <svg viewBox=\"0 0 24 24\" {...sz} fill=\"#0A66C2\">\n <path d=\"M20.45 20.45h-3.56v-5.57c0-1.33-.02-3.04-1.85-3.04-1.85 0-2.13 1.45-2.13 2.94v5.67H9.35V9h3.41v1.56h.05c.48-.9 1.64-1.85 3.37-1.85 3.6 0 4.27 2.37 4.27 5.45v6.29zM5.34 7.43a2.06 2.06 0 1 1 0-4.13 2.06 2.06 0 0 1 0 4.13zM7.12 20.45H3.56V9h3.56v11.45zM22.22 0H1.77C.79 0 0 .77 0 1.72v20.56C0 23.23.79 24 1.77 24h20.45c.98 0 1.78-.77 1.78-1.72V1.72C24 .77 23.2 0 22.22 0z\" />\n </svg>\n );\n}\n\nfunction GitHubIcon() {\n return (\n <svg viewBox=\"0 0 24 24\" {...sz} fill=\"#181717\">\n <path d=\"M12 .3a12 12 0 0 0-3.8 23.4c.6.1.8-.3.8-.6v-2c-3.3.7-4-1.6-4-1.6-.6-1.4-1.3-1.8-1.3-1.8-1.1-.7.1-.7.1-.7 1.2.1 1.8 1.2 1.8 1.2 1.1 1.8 2.8 1.3 3.5 1 .1-.8.4-1.3.8-1.6-2.7-.3-5.5-1.3-5.5-6a4.7 4.7 0 0 1 1.2-3.2c-.1-.3-.5-1.5.1-3.2 0 0 1-.3 3.3 1.2a11.5 11.5 0 0 1 6 0c2.3-1.5 3.3-1.2 3.3-1.2.6 1.7.2 2.9.1 3.2a4.7 4.7 0 0 1 1.2 3.2c0 4.7-2.8 5.7-5.5 6 .4.4.8 1.1.8 2.2v3.3c0 .3.2.7.8.6A12 12 0 0 0 12 .3\" />\n </svg>\n );\n}\n\nfunction AppleIcon() {\n return (\n <svg viewBox=\"0 0 24 24\" {...sz} fill=\"#000\">\n <path d=\"M17.05 12.04c-.03-2.7 2.2-4 2.3-4.06-1.25-1.84-3.2-2.09-3.9-2.12-1.66-.17-3.24.98-4.08.98-.84 0-2.14-.96-3.52-.93-1.81.03-3.48 1.05-4.41 2.67-1.88 3.27-.48 8.1 1.35 10.76.9 1.3 1.97 2.76 3.38 2.71 1.36-.05 1.87-.88 3.51-.88 1.64 0 2.1.88 3.53.85 1.46-.03 2.38-1.33 3.27-2.63 1.03-1.51 1.46-2.97 1.48-3.05-.03-.01-2.84-1.09-2.87-4.32M14.4 4.2c.74-.9 1.24-2.15 1.1-3.4-1.07.04-2.36.71-3.13 1.61-.69.8-1.29 2.07-1.13 3.29 1.19.09 2.41-.61 3.16-1.5\" />\n </svg>\n );\n}\n\nfunction MailIcon() {\n return (\n <svg viewBox=\"0 0 24 24\" {...sz} fill=\"none\" stroke=\"#334155\" strokeWidth={2} strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <rect x=\"3\" y=\"5\" width=\"18\" height=\"14\" rx=\"2\" />\n <path d=\"m3 7 9 6 9-6\" />\n </svg>\n );\n}\n\nexport const PROVIDER_META: Record<Method, { label: string; icon: React.ReactNode }> = {\n google: { label: \"Google\", icon: <GoogleIcon /> },\n linkedin: { label: \"LinkedIn\", icon: <LinkedInIcon /> },\n github: { label: \"GitHub\", icon: <GitHubIcon /> },\n apple: { label: \"Apple\", icon: <AppleIcon /> },\n password: { label: \"email\", icon: <MailIcon /> },\n};\n"],"mappings":"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","names":["jsx","jsxs"]}
1
+ {"version":3,"sources":["../src/index.tsx","../src/icons.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { createContext, useContext, useEffect, useState } from \"react\";\nimport { PROVIDER_META, type Method } from \"./icons\";\n\nexport type { Method } from \"./icons\";\nexport { PROVIDER_META } from \"./icons\";\n\nexport interface AuthRoboConfig {\n /** Your AuthRobo instance, e.g. \"https://authrobo.com\". */\n issuerUrl: string;\n /** Your app's client_id (arc_...). */\n clientId: string;\n /** Where AuthRobo returns the one-time `code` after sign-in. */\n redirectUri: string;\n}\n\n/** Shape returned by GET /api/v1/config. */\nexport interface AppConfig {\n name: string;\n brandColor: string | null;\n logoUrl: string | null;\n methods: Method[];\n}\n\nconst ConfigContext = createContext<AuthRoboConfig | null>(null);\n\nexport function AuthRoboProvider({\n config,\n children,\n}: {\n config: AuthRoboConfig;\n children: React.ReactNode;\n}) {\n return <ConfigContext.Provider value={config}>{children}</ConfigContext.Provider>;\n}\n\nfunction useResolvedConfig(override?: AuthRoboConfig): AuthRoboConfig {\n const ctx = useContext(ConfigContext);\n const c = override ?? ctx;\n if (!c) {\n throw new Error(\n \"@authrobo/react: wrap your app in <AuthRoboProvider config={...}> or pass a `config` prop.\",\n );\n }\n return c;\n}\n\nfunction randomState(): string {\n const b = new Uint8Array(16);\n (globalThis.crypto ?? (globalThis as { crypto: Crypto }).crypto).getRandomValues(b);\n return Array.from(b, (x) => x.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\n// Email-based methods open the hosted login page (which collects the address);\n// social providers redirect straight into their OAuth start endpoint.\nconst EMAIL_METHODS: readonly Method[] = [\"password\", \"magic_link\"];\n\n/**\n * Begin sign-in: navigate the browser to AuthRobo's hosted flow for `method`.\n * Social methods hit /api/v1/auth/<provider>/start; \"password\" and \"magic_link\"\n * open the hosted email login page (which renders whichever of the two the app\n * has enabled). AuthRobo returns to your `redirectUri` with a `code` that your\n * backend exchanges (with your client_secret) for tokens.\n */\nexport function startSignIn(\n config: AuthRoboConfig,\n method: Method,\n opts?: { state?: string; loginHint?: string },\n): void {\n const base = config.issuerUrl.replace(/\\/$/, \"\");\n const state = opts?.state ?? randomState();\n try {\n sessionStorage.setItem(\"authrobo:state\", state);\n } catch {\n /* storage unavailable — state is still sent, just not persisted for a check */\n }\n const params = new URLSearchParams({\n client_id: config.clientId,\n redirect_uri: config.redirectUri,\n state,\n });\n if (opts?.loginHint) params.set(\"login_hint\", opts.loginHint);\n // Remember the method so we can badge it as \"Last used\" next time. Scoped per\n // client_id so different apps on the same origin don't clobber each other.\n try {\n localStorage.setItem(lastMethodKey(config.clientId), method);\n } catch {\n /* storage unavailable — badge just won't show next time */\n }\n const url = EMAIL_METHODS.includes(method)\n ? `${base}/login?${params.toString()}`\n : `${base}/api/v1/auth/${method}/start?${params.toString()}`;\n window.location.assign(url);\n}\n\nconst lastMethodKey = (clientId: string) => `authrobo:last-method:${clientId}`;\n\n/** The method last used to sign in for this client (from a prior startSignIn). */\nexport function useLastMethod(clientId: string): Method | null {\n const [last, setLast] = useState<Method | null>(null);\n useEffect(() => {\n try {\n setLast((localStorage.getItem(lastMethodKey(clientId)) as Method | null) ?? null);\n } catch {\n /* storage unavailable */\n }\n }, [clientId]);\n return last;\n}\n\n/** Fetch an app's public config (branding + enabled methods) for `clientId`. */\nexport function useAppConfig(config?: AuthRoboConfig): {\n data: AppConfig | null;\n loading: boolean;\n error: string | null;\n} {\n const c = useResolvedConfig(config);\n const [state, setState] = useState<{ data: AppConfig | null; loading: boolean; error: string | null }>({\n data: null,\n loading: true,\n error: null,\n });\n\n useEffect(() => {\n let active = true;\n setState({ data: null, loading: true, error: null });\n const base = c.issuerUrl.replace(/\\/$/, \"\");\n fetch(`${base}/api/v1/config?client_id=${encodeURIComponent(c.clientId)}`)\n .then((r) => (r.ok ? r.json() : Promise.reject(new Error(`config ${r.status}`))))\n .then((j) => {\n if (!active) return;\n setState({\n data: { name: j.name, brandColor: j.brand_color, logoUrl: j.logo_url, methods: j.methods },\n loading: false,\n error: null,\n });\n })\n .catch((e) => active && setState({ data: null, loading: false, error: String(e?.message ?? e) }));\n return () => {\n active = false;\n };\n }, [c.issuerUrl, c.clientId]);\n\n return state;\n}\n\n/* ----------------------------- Styling ---------------------------------- */\n\nconst STYLE_ID = \"authrobo-react-styles\";\n\n// Theme-aware: tokens default to a soft light palette, flip to dark on the OS\n// preference, and can be forced either way by a `data-theme` attribute on any\n// ancestor (`<html data-theme=\"dark\">`) — the same hook AuthRobo's own hosted\n// pages use, so the injected widgets match a host app that drives its theme\n// that way. The explicit attribute wins over the OS media query by specificity.\nconst TOKENS_LIGHT = `\n --authrobo-btn-bg:#fff;--authrobo-btn-fg:#111827;--authrobo-btn-border:#d1d5db;\n --authrobo-btn-hover-bg:#f8fafc;--authrobo-btn-hover-border:#9ca3af;\n --authrobo-card-bg:#fff;--authrobo-title-fg:#0f172a;--authrobo-muted:#64748b;\n --authrobo-error:#b91c1c;--authrobo-divider-fg:#9ca3af;--authrobo-divider-line:#e5e7eb;\n --authrobo-overlay:rgba(2,6,23,.55);--authrobo-close:#94a3b8;--authrobo-close-hover:#334155;\n --authrobo-focus:#2b8aff;--authrobo-card-shadow:rgba(2,6,23,.4);\n --authrobo-last-bg:#e7efff;--authrobo-last-fg:#2563eb;`;\nconst TOKENS_DARK = `\n --authrobo-btn-bg:#0e1626;--authrobo-btn-fg:#e6edf8;--authrobo-btn-border:#28374f;\n --authrobo-btn-hover-bg:#16233a;--authrobo-btn-hover-border:#3a4d6b;\n --authrobo-card-bg:#0b1424;--authrobo-title-fg:#f1f5fb;--authrobo-muted:#93a4bd;\n --authrobo-error:#f87171;--authrobo-divider-fg:#64748b;--authrobo-divider-line:#223049;\n --authrobo-overlay:rgba(2,6,23,.72);--authrobo-close:#64748b;--authrobo-close-hover:#cbd5e1;\n --authrobo-focus:#46c8ff;--authrobo-card-shadow:rgba(0,0,0,.6);\n --authrobo-last-bg:rgba(70,160,255,.18);--authrobo-last-fg:#8fc4ff;`;\n// Elements that either read tokens directly or need them for their descendants\n// (a standalone button has no stack ancestor; the overlay can't inherit from\n// its own card child), so every token-owning root declares the full set.\nconst TOKEN_ROOTS =\n \".authrobo-btn,.authrobo-stack,.authrobo-card,.authrobo-overlay,.authrobo-divider,.authrobo-title\";\nconst scoped = (prefix: string, tokens: string) =>\n TOKEN_ROOTS.split(\",\")\n .map((s) => `${prefix}${s.trim()}`)\n .join(\",\") + `{${tokens}}`;\n\nconst CSS = `\n${scoped(\"\", TOKENS_LIGHT)}\n@media (prefers-color-scheme:dark){${scoped(\"\", TOKENS_DARK)}}\n${scoped('[data-theme=\"light\"] ', TOKENS_LIGHT)}\n${scoped('[data-theme=\"dark\"] ', TOKENS_DARK)}\n.authrobo-btn{position:relative;display:flex;width:100%;align-items:center;justify-content:center;gap:10px;\n border:1px solid var(--authrobo-btn-border);background:var(--authrobo-btn-bg);color:var(--authrobo-btn-fg);border-radius:10px;padding:10px 14px;\n font:600 14px/1.2 system-ui,-apple-system,Segoe UI,Roboto,sans-serif;cursor:pointer;\n transition:background .15s ease,border-color .15s ease,box-shadow .15s ease;}\n.authrobo-btn:hover{background:var(--authrobo-btn-hover-bg);border-color:var(--authrobo-btn-hover-border);}\n.authrobo-btn:focus-visible{outline:2px solid var(--authrobo-focus);outline-offset:2px;}\n.authrobo-last{position:absolute;right:8px;top:50%;transform:translateY(-50%);pointer-events:none;\n font:600 10px/1 system-ui,sans-serif;letter-spacing:.02em;padding:3px 7px;border-radius:999px;\n background:var(--authrobo-last-bg);color:var(--authrobo-last-fg);}\n.authrobo-stack{display:flex;flex-direction:column;gap:10px;}\n.authrobo-title{font:700 16px system-ui,sans-serif;color:var(--authrobo-title-fg);}\n.authrobo-muted{color:var(--authrobo-muted);font:14px system-ui,sans-serif;margin:0;}\n.authrobo-error{color:var(--authrobo-error);font:14px system-ui,sans-serif;margin:0;}\n.authrobo-divider{display:flex;align-items:center;gap:10px;color:var(--authrobo-divider-fg);font:500 12px system-ui,sans-serif;}\n.authrobo-divider::before,.authrobo-divider::after{content:\"\";height:1px;flex:1;background:var(--authrobo-divider-line);}\n.authrobo-overlay{position:fixed;inset:0;z-index:2147483000;display:flex;align-items:center;\n justify-content:center;padding:16px;background:var(--authrobo-overlay);backdrop-filter:blur(2px);\n animation:authrobo-fade .16s ease-out both;}\n.authrobo-card{position:relative;width:100%;max-width:360px;background:var(--authrobo-card-bg);border-radius:16px;\n padding:24px;box-shadow:0 20px 60px -12px var(--authrobo-card-shadow);animation:authrobo-pop .2s cubic-bezier(.16,1,.3,1) both;}\n.authrobo-close{position:absolute;top:12px;right:12px;border:0;background:transparent;cursor:pointer;\n color:var(--authrobo-close);font-size:18px;line-height:1;padding:4px;}\n.authrobo-close:hover{color:var(--authrobo-close-hover);}\n@keyframes authrobo-fade{from{opacity:0}to{opacity:1}}\n@keyframes authrobo-pop{from{opacity:0;transform:translateY(8px) scale(.97)}to{opacity:1;transform:none}}\n@media (prefers-reduced-motion: reduce){.authrobo-overlay,.authrobo-card{animation:none}}\n`;\n\nfunction useInjectStyles() {\n useEffect(() => {\n if (typeof document === \"undefined\" || document.getElementById(STYLE_ID)) return;\n const el = document.createElement(\"style\");\n el.id = STYLE_ID;\n el.textContent = CSS;\n document.head.appendChild(el);\n }, []);\n}\n\n/* ----------------------------- Button ----------------------------------- */\n\nexport function AuthRoboButton({\n provider,\n config,\n label,\n className,\n style,\n onClick,\n lastUsed,\n}: {\n provider: Method;\n config?: AuthRoboConfig;\n label?: string;\n className?: string;\n style?: React.CSSProperties;\n onClick?: () => void;\n /** Show a \"Last used\" badge (the method the visitor signed in with before). */\n lastUsed?: boolean;\n}) {\n useInjectStyles();\n const c = useResolvedConfig(config);\n const meta = PROVIDER_META[provider];\n return (\n <button\n type=\"button\"\n className={className ? `authrobo-btn ${className}` : \"authrobo-btn\"}\n style={style}\n onClick={() => {\n onClick?.();\n startSignIn(c, provider);\n }}\n >\n <span style={{ display: \"inline-flex\", alignItems: \"center\" }}>{meta.icon}</span>\n {label ?? `Continue with ${meta.label}`}\n {lastUsed && <span className=\"authrobo-last\">Last used</span>}\n </button>\n );\n}\n\n/* ---------------------- Auth modal / inline panel ----------------------- */\n\nexport function AuthRoboAuth({\n config,\n mode = \"inline\",\n open = true,\n onClose,\n title,\n methods: only,\n}: {\n config?: AuthRoboConfig;\n /** \"inline\" renders in place; \"modal\" renders a centered overlay. */\n mode?: \"inline\" | \"modal\";\n /** Only used in modal mode. */\n open?: boolean;\n onClose?: () => void;\n /** Heading; defaults to \"Sign in to <app name>\". */\n title?: string;\n /** Restrict/reorder which methods to show (defaults to all enabled ones). */\n methods?: Method[];\n}) {\n useInjectStyles();\n const c = useResolvedConfig(config);\n const { data, loading, error } = useAppConfig(c);\n const lastMethod = useLastMethod(c.clientId);\n\n useEffect(() => {\n if (mode !== \"modal\" || !open) return;\n const onKey = (e: KeyboardEvent) => e.key === \"Escape\" && onClose?.();\n document.addEventListener(\"keydown\", onKey);\n return () => document.removeEventListener(\"keydown\", onKey);\n }, [mode, open, onClose]);\n\n if (mode === \"modal\" && !open) return null;\n\n const available = data?.methods ?? [];\n const shown = (only ? only.filter((m) => available.includes(m)) : available).filter(\n (m) => m in PROVIDER_META,\n );\n const social = shown.filter((m) => !EMAIL_METHODS.includes(m));\n const email = shown.filter((m) => EMAIL_METHODS.includes(m));\n // Only worth pointing out the last-used method when there's a choice to make.\n const badge = (m: Method) => shown.length > 1 && m === lastMethod;\n\n const panel = (\n <div className=\"authrobo-stack\">\n {(data?.logoUrl || data?.name) && (\n <div style={{ display: \"flex\", alignItems: \"center\", gap: 10, marginBottom: 4 }}>\n {data?.logoUrl && (\n <img src={data.logoUrl} alt=\"\" width={28} height={28} style={{ borderRadius: 6 }} />\n )}\n <span className=\"authrobo-title\">\n {title ?? `Sign in${data?.name ? ` to ${data.name}` : \"\"}`}\n </span>\n </div>\n )}\n\n {loading && <p className=\"authrobo-muted\">Loading sign-in options…</p>}\n {error && <p className=\"authrobo-error\">Couldn&apos;t load sign-in options.</p>}\n\n {social.map((m) => (\n <AuthRoboButton key={m} provider={m} config={c} lastUsed={badge(m)} />\n ))}\n {email.length > 0 && social.length > 0 && <div className=\"authrobo-divider\">or</div>}\n {email.map((m) => (\n <AuthRoboButton\n key={m}\n provider={m}\n config={c}\n label={m === \"magic_link\" ? \"Email me a sign-in link\" : \"Continue with email\"}\n lastUsed={badge(m)}\n />\n ))}\n\n {!loading && !error && shown.length === 0 && (\n <p className=\"authrobo-muted\">No sign-in methods are enabled for this app.</p>\n )}\n </div>\n );\n\n if (mode === \"inline\") return panel;\n\n return (\n <div className=\"authrobo-overlay\" onClick={() => onClose?.()}>\n <div className=\"authrobo-card\" role=\"dialog\" aria-modal=\"true\" onClick={(e) => e.stopPropagation()}>\n <button type=\"button\" className=\"authrobo-close\" aria-label=\"Close\" onClick={() => onClose?.()}>\n ✕\n </button>\n {panel}\n </div>\n </div>\n );\n}\n","import * as React from \"react\";\n\n// Sign-in methods AuthRobo can surface. \"password\" and \"magic_link\" route to\n// the hosted email login page; the rest are social OAuth providers.\nexport type Method = \"google\" | \"linkedin\" | \"github\" | \"apple\" | \"password\" | \"magic_link\";\n\nconst sz = { width: 18, height: 18, \"aria-hidden\": true } as const;\n\nfunction GoogleIcon() {\n return (\n <svg viewBox=\"0 0 48 48\" {...sz}>\n <path fill=\"#FFC107\" d=\"M43.6 20.5H42V20H24v8h11.3C33.7 32.9 29.3 36 24 36c-6.6 0-12-5.4-12-12s5.4-12 12-12c3.1 0 5.9 1.2 8 3.1l5.7-5.7C34.6 6.1 29.6 4 24 4 12.9 4 4 12.9 4 24s8.9 20 20 20 20-8.9 20-20c0-1.3-.1-2.3-.4-3.5z\" />\n <path fill=\"#FF3D00\" d=\"M6.3 14.7l6.6 4.8C14.7 16 19 13 24 13c3.1 0 5.9 1.2 8 3.1l5.7-5.7C34.6 6.1 29.6 4 24 4 16.3 4 9.7 8.3 6.3 14.7z\" />\n <path fill=\"#4CAF50\" d=\"M24 44c5.2 0 10-2 13.6-5.2l-6.3-5.2C29.2 35 26.7 36 24 36c-5.3 0-9.7-3.4-11.3-8.1l-6.5 5C9.5 39.6 16.2 44 24 44z\" />\n <path fill=\"#1976D2\" d=\"M43.6 20.5H42V20H24v8h11.3c-.8 2.2-2.2 4.1-4.1 5.6l6.3 5.2C41.9 36 44 30.5 44 24c0-1.3-.1-2.3-.4-3.5z\" />\n </svg>\n );\n}\n\nfunction LinkedInIcon() {\n return (\n <svg viewBox=\"0 0 24 24\" {...sz} fill=\"#0A66C2\">\n <path d=\"M20.45 20.45h-3.56v-5.57c0-1.33-.02-3.04-1.85-3.04-1.85 0-2.13 1.45-2.13 2.94v5.67H9.35V9h3.41v1.56h.05c.48-.9 1.64-1.85 3.37-1.85 3.6 0 4.27 2.37 4.27 5.45v6.29zM5.34 7.43a2.06 2.06 0 1 1 0-4.13 2.06 2.06 0 0 1 0 4.13zM7.12 20.45H3.56V9h3.56v11.45zM22.22 0H1.77C.79 0 0 .77 0 1.72v20.56C0 23.23.79 24 1.77 24h20.45c.98 0 1.78-.77 1.78-1.72V1.72C24 .77 23.2 0 22.22 0z\" />\n </svg>\n );\n}\n\nfunction GitHubIcon() {\n return (\n <svg viewBox=\"0 0 24 24\" {...sz} fill=\"#181717\">\n <path d=\"M12 .3a12 12 0 0 0-3.8 23.4c.6.1.8-.3.8-.6v-2c-3.3.7-4-1.6-4-1.6-.6-1.4-1.3-1.8-1.3-1.8-1.1-.7.1-.7.1-.7 1.2.1 1.8 1.2 1.8 1.2 1.1 1.8 2.8 1.3 3.5 1 .1-.8.4-1.3.8-1.6-2.7-.3-5.5-1.3-5.5-6a4.7 4.7 0 0 1 1.2-3.2c-.1-.3-.5-1.5.1-3.2 0 0 1-.3 3.3 1.2a11.5 11.5 0 0 1 6 0c2.3-1.5 3.3-1.2 3.3-1.2.6 1.7.2 2.9.1 3.2a4.7 4.7 0 0 1 1.2 3.2c0 4.7-2.8 5.7-5.5 6 .4.4.8 1.1.8 2.2v3.3c0 .3.2.7.8.6A12 12 0 0 0 12 .3\" />\n </svg>\n );\n}\n\nfunction AppleIcon() {\n return (\n <svg viewBox=\"0 0 24 24\" {...sz} fill=\"#000\">\n <path d=\"M17.05 12.04c-.03-2.7 2.2-4 2.3-4.06-1.25-1.84-3.2-2.09-3.9-2.12-1.66-.17-3.24.98-4.08.98-.84 0-2.14-.96-3.52-.93-1.81.03-3.48 1.05-4.41 2.67-1.88 3.27-.48 8.1 1.35 10.76.9 1.3 1.97 2.76 3.38 2.71 1.36-.05 1.87-.88 3.51-.88 1.64 0 2.1.88 3.53.85 1.46-.03 2.38-1.33 3.27-2.63 1.03-1.51 1.46-2.97 1.48-3.05-.03-.01-2.84-1.09-2.87-4.32M14.4 4.2c.74-.9 1.24-2.15 1.1-3.4-1.07.04-2.36.71-3.13 1.61-.69.8-1.29 2.07-1.13 3.29 1.19.09 2.41-.61 3.16-1.5\" />\n </svg>\n );\n}\n\nfunction MailIcon() {\n return (\n <svg viewBox=\"0 0 24 24\" {...sz} fill=\"none\" stroke=\"#334155\" strokeWidth={2} strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <rect x=\"3\" y=\"5\" width=\"18\" height=\"14\" rx=\"2\" />\n <path d=\"m3 7 9 6 9-6\" />\n </svg>\n );\n}\n\nexport const PROVIDER_META: Record<Method, { label: string; icon: React.ReactNode }> = {\n google: { label: \"Google\", icon: <GoogleIcon /> },\n linkedin: { label: \"LinkedIn\", icon: <LinkedInIcon /> },\n github: { label: \"GitHub\", icon: <GitHubIcon /> },\n apple: { label: \"Apple\", icon: <AppleIcon /> },\n password: { label: \"email\", icon: <MailIcon /> },\n magic_link: { label: \"magic link\", icon: <MailIcon /> },\n};\n"],"mappings":"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","names":["jsx","jsxs"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@authrobo/react",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "React sign-in buttons and a configurable auth modal for AuthRobo — Google, LinkedIn, GitHub, Apple, and email.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -10,16 +10,22 @@
10
10
  "types": "./dist/index.d.ts",
11
11
  "exports": {
12
12
  ".": {
13
- "types": "./dist/index.d.ts",
14
- "import": "./dist/index.js",
15
- "require": "./dist/index.cjs"
16
- }
13
+ "import": {
14
+ "types": "./dist/index.d.ts",
15
+ "default": "./dist/index.js"
16
+ },
17
+ "require": {
18
+ "types": "./dist/index.d.cts",
19
+ "default": "./dist/index.cjs"
20
+ }
21
+ },
22
+ "./package.json": "./package.json"
17
23
  },
18
24
  "files": ["dist", "README.md"],
19
25
  "scripts": {
20
26
  "build": "tsup",
21
27
  "typecheck": "tsc --noEmit",
22
- "prepublishOnly": "npm run build"
28
+ "prepublishOnly": "npm run build && node ../../scripts/check-package-exports.mjs"
23
29
  },
24
30
  "peerDependencies": {
25
31
  "react": ">=18",