@authrobo/react 0.2.1 → 0.5.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
@@ -25,7 +25,10 @@ __export(index_exports, {
25
25
  AuthRoboAuth: () => AuthRoboAuth,
26
26
  AuthRoboButton: () => AuthRoboButton,
27
27
  AuthRoboProvider: () => AuthRoboProvider,
28
+ BRAND_STYLE: () => BRAND_STYLE,
28
29
  PROVIDER_META: () => PROVIDER_META,
30
+ Spinner: () => Spinner,
31
+ prefetchAppConfig: () => prefetchAppConfig,
29
32
  startSignIn: () => startSignIn,
30
33
  useAppConfig: () => useAppConfig,
31
34
  useLastMethod: () => useLastMethod
@@ -36,6 +39,13 @@ var import_react = require("react");
36
39
  // src/icons.tsx
37
40
  var import_jsx_runtime = require("react/jsx-runtime");
38
41
  var sz = { width: 18, height: 18, "aria-hidden": true };
42
+ var LINKEDIN_PATH = "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";
43
+ var GITHUB_PATH = "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";
44
+ var APPLE_PATH = "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";
45
+ var FACEBOOK_PATH = "M24 12.07C24 5.4 18.63 0 12 0S0 5.4 0 12.07C0 18.1 4.39 23.1 10.13 24v-8.44H7.08v-3.49h3.05V9.41c0-3.02 1.79-4.69 4.53-4.69 1.31 0 2.68.24 2.68.24v2.97h-1.51c-1.49 0-1.95.93-1.95 1.88v2.26h3.32l-.53 3.49h-2.79V24C19.61 23.1 24 18.1 24 12.07";
46
+ function Glyph({ d, fill }) {
47
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { viewBox: "0 0 24 24", ...sz, fill, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d }) });
48
+ }
39
49
  function GoogleIcon() {
40
50
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { viewBox: "0 0 48 48", ...sz, children: [
41
51
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("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" }),
@@ -44,15 +54,6 @@ function GoogleIcon() {
44
54
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("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" })
45
55
  ] });
46
56
  }
47
- function LinkedInIcon() {
48
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { viewBox: "0 0 24 24", ...sz, fill: "#0A66C2", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("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" }) });
49
- }
50
- function GitHubIcon() {
51
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { viewBox: "0 0 24 24", ...sz, fill: "#181717", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("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" }) });
52
- }
53
- function AppleIcon() {
54
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { viewBox: "0 0 24 24", ...sz, fill: "#000", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("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" }) });
55
- }
56
57
  function MailIcon() {
57
58
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { viewBox: "0 0 24 24", ...sz, fill: "none", stroke: "#334155", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", children: [
58
59
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "3", y: "5", width: "18", height: "14", rx: "2" }),
@@ -61,12 +62,26 @@ function MailIcon() {
61
62
  }
62
63
  var PROVIDER_META = {
63
64
  google: { label: "Google", icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GoogleIcon, {}) },
64
- linkedin: { label: "LinkedIn", icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LinkedInIcon, {}) },
65
- github: { label: "GitHub", icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GitHubIcon, {}) },
66
- apple: { label: "Apple", icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AppleIcon, {}) },
65
+ linkedin: { label: "LinkedIn", icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Glyph, { d: LINKEDIN_PATH, fill: "#0A66C2" }) },
66
+ github: { label: "GitHub", icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Glyph, { d: GITHUB_PATH, fill: "#181717" }) },
67
+ apple: { label: "Apple", icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Glyph, { d: APPLE_PATH, fill: "#000" }) },
68
+ facebook: { label: "Facebook", icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Glyph, { d: FACEBOOK_PATH, fill: "#1877F2" }) },
67
69
  password: { label: "email", icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MailIcon, {}) },
68
70
  magic_link: { label: "magic link", icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MailIcon, {}) }
69
71
  };
72
+ var WHITE_MARKS = {
73
+ linkedin: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Glyph, { d: LINKEDIN_PATH, fill: "#fff" }),
74
+ github: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Glyph, { d: GITHUB_PATH, fill: "#fff" }),
75
+ apple: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Glyph, { d: APPLE_PATH, fill: "#fff" }),
76
+ facebook: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Glyph, { d: FACEBOOK_PATH, fill: "#fff" })
77
+ };
78
+ var BRAND_STYLE = {
79
+ google: { bg: "#ffffff", fg: "#1a73e8", border: "#dadce0" },
80
+ linkedin: { bg: "#0a66c2", fg: "#ffffff", border: "#0a66c2", whiteIcon: true },
81
+ github: { bg: "#181717", fg: "#ffffff", border: "#181717", whiteIcon: true },
82
+ apple: { bg: "#000000", fg: "#ffffff", border: "#000000", whiteIcon: true },
83
+ facebook: { bg: "#1877f2", fg: "#ffffff", border: "#1877f2", whiteIcon: true }
84
+ };
70
85
 
71
86
  // src/index.tsx
72
87
  var import_jsx_runtime2 = require("react/jsx-runtime");
@@ -75,6 +90,9 @@ function AuthRoboProvider({
75
90
  config,
76
91
  children
77
92
  }) {
93
+ (0, import_react.useEffect)(() => {
94
+ prefetchAppConfig(config);
95
+ }, [config.issuerUrl, config.clientId]);
78
96
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ConfigContext.Provider, { value: config, children });
79
97
  }
80
98
  function useResolvedConfig(override) {
@@ -94,6 +112,18 @@ function randomState() {
94
112
  }
95
113
  var EMAIL_METHODS = ["password", "magic_link"];
96
114
  function startSignIn(config, method, opts) {
115
+ try {
116
+ localStorage.setItem(lastMethodKey(config.clientId), method);
117
+ } catch {
118
+ }
119
+ if (opts?.startPath) {
120
+ const p = new URLSearchParams();
121
+ if (opts.loginHint) p.set("login_hint", opts.loginHint);
122
+ if (opts.screen) p.set("screen", opts.screen);
123
+ const qs = p.toString();
124
+ window.location.assign(`${opts.startPath.replace(/\/$/, "")}/${method}/start${qs ? `?${qs}` : ""}`);
125
+ return;
126
+ }
97
127
  const base = config.issuerUrl.replace(/\/$/, "");
98
128
  const state = opts?.state ?? randomState();
99
129
  try {
@@ -106,12 +136,16 @@ function startSignIn(config, method, opts) {
106
136
  state
107
137
  });
108
138
  if (opts?.loginHint) params.set("login_hint", opts.loginHint);
109
- try {
110
- localStorage.setItem(lastMethodKey(config.clientId), method);
111
- } catch {
139
+ let path;
140
+ if (method === "magic_link") {
141
+ params.set("screen", "magic");
142
+ path = "/login";
143
+ } else if (method === "password") {
144
+ path = opts?.screen === "register" ? "/register" : "/login";
145
+ } else {
146
+ path = `/api/v1/auth/${method}/start`;
112
147
  }
113
- const url = EMAIL_METHODS.includes(method) ? `${base}/login?${params.toString()}` : `${base}/api/v1/auth/${method}/start?${params.toString()}`;
114
- window.location.assign(url);
148
+ window.location.assign(`${base}${path}?${params.toString()}`);
115
149
  }
116
150
  var lastMethodKey = (clientId) => `authrobo:last-method:${clientId}`;
117
151
  function useLastMethod(clientId) {
@@ -124,28 +158,54 @@ function useLastMethod(clientId) {
124
158
  }, [clientId]);
125
159
  return last;
126
160
  }
161
+ var LOADING_STATE = { data: null, loading: true, error: null };
162
+ var configStore = /* @__PURE__ */ new Map();
163
+ var configKey = (c) => `${c.issuerUrl.replace(/\/$/, "")}|${c.clientId}`;
164
+ function configEntry(key) {
165
+ let e = configStore.get(key);
166
+ if (!e) {
167
+ e = { state: LOADING_STATE, listeners: /* @__PURE__ */ new Set() };
168
+ configStore.set(key, e);
169
+ }
170
+ return e;
171
+ }
172
+ function prefetchAppConfig(config) {
173
+ if (typeof window === "undefined") return Promise.resolve();
174
+ const key = configKey(config);
175
+ const e = configEntry(key);
176
+ if (e.state.data || e.promise) return e.promise ?? Promise.resolve();
177
+ const base = config.issuerUrl.replace(/\/$/, "");
178
+ e.promise = fetch(`${base}/api/v1/config?client_id=${encodeURIComponent(config.clientId)}`).then((r) => r.ok ? r.json() : Promise.reject(new Error(`config ${r.status}`))).then((j) => {
179
+ e.state = {
180
+ data: { name: j.name, brandColor: j.brand_color, logoUrl: j.logo_url, methods: j.methods, poweredBy: j.powered_by !== false },
181
+ loading: false,
182
+ error: null
183
+ };
184
+ }).catch((err) => {
185
+ e.state = { data: null, loading: false, error: String(err?.message ?? err) };
186
+ }).finally(() => {
187
+ e.promise = void 0;
188
+ e.listeners.forEach((l) => l());
189
+ });
190
+ return e.promise;
191
+ }
127
192
  function useAppConfig(config) {
128
193
  const c = useResolvedConfig(config);
129
- const [state, setState] = (0, import_react.useState)({
130
- data: null,
131
- loading: true,
132
- error: null
133
- });
194
+ const key = configKey(c);
195
+ const subscribe = (0, import_react.useCallback)(
196
+ (cb) => {
197
+ const e = configEntry(key);
198
+ e.listeners.add(cb);
199
+ return () => {
200
+ e.listeners.delete(cb);
201
+ };
202
+ },
203
+ [key]
204
+ );
205
+ const getSnapshot = (0, import_react.useCallback)(() => configEntry(key).state, [key]);
206
+ const state = (0, import_react.useSyncExternalStore)(subscribe, getSnapshot, () => LOADING_STATE);
134
207
  (0, import_react.useEffect)(() => {
135
- let active = true;
136
- setState({ data: null, loading: true, error: null });
137
- const base = c.issuerUrl.replace(/\/$/, "");
138
- fetch(`${base}/api/v1/config?client_id=${encodeURIComponent(c.clientId)}`).then((r) => r.ok ? r.json() : Promise.reject(new Error(`config ${r.status}`))).then((j) => {
139
- if (!active) return;
140
- setState({
141
- data: { name: j.name, brandColor: j.brand_color, logoUrl: j.logo_url, methods: j.methods },
142
- loading: false,
143
- error: null
144
- });
145
- }).catch((e) => active && setState({ data: null, loading: false, error: String(e?.message ?? e) }));
146
- return () => {
147
- active = false;
148
- };
208
+ prefetchAppConfig(c);
149
209
  }, [c.issuerUrl, c.clientId]);
150
210
  return state;
151
211
  }
@@ -157,7 +217,9 @@ var TOKENS_LIGHT = `
157
217
  --authrobo-error:#b91c1c;--authrobo-divider-fg:#9ca3af;--authrobo-divider-line:#e5e7eb;
158
218
  --authrobo-overlay:rgba(2,6,23,.55);--authrobo-close:#94a3b8;--authrobo-close-hover:#334155;
159
219
  --authrobo-focus:#2b8aff;--authrobo-card-shadow:rgba(2,6,23,.4);
160
- --authrobo-last-bg:#e7efff;--authrobo-last-fg:#2563eb;`;
220
+ --authrobo-last-bg:#e7efff;--authrobo-last-fg:#2563eb;
221
+ --authrobo-input-bg:#fff;--authrobo-input-fg:#111827;
222
+ --authrobo-tabs-bg:#f1f5f9;--authrobo-accent:#4338ca;--authrobo-accent-fg:#fff;`;
161
223
  var TOKENS_DARK = `
162
224
  --authrobo-btn-bg:#0e1626;--authrobo-btn-fg:#e6edf8;--authrobo-btn-border:#28374f;
163
225
  --authrobo-btn-hover-bg:#16233a;--authrobo-btn-hover-border:#3a4d6b;
@@ -165,7 +227,9 @@ var TOKENS_DARK = `
165
227
  --authrobo-error:#f87171;--authrobo-divider-fg:#64748b;--authrobo-divider-line:#223049;
166
228
  --authrobo-overlay:rgba(2,6,23,.72);--authrobo-close:#64748b;--authrobo-close-hover:#cbd5e1;
167
229
  --authrobo-focus:#46c8ff;--authrobo-card-shadow:rgba(0,0,0,.6);
168
- --authrobo-last-bg:rgba(70,160,255,.18);--authrobo-last-fg:#8fc4ff;`;
230
+ --authrobo-last-bg:rgba(70,160,255,.18);--authrobo-last-fg:#8fc4ff;
231
+ --authrobo-input-bg:#0e1626;--authrobo-input-fg:#e6edf8;
232
+ --authrobo-tabs-bg:#0e1626;--authrobo-accent:#4338ca;--authrobo-accent-fg:#fff;`;
169
233
  var TOKEN_ROOTS = ".authrobo-btn,.authrobo-stack,.authrobo-card,.authrobo-overlay,.authrobo-divider,.authrobo-title";
170
234
  var scoped = (prefix, tokens) => TOKEN_ROOTS.split(",").map((s) => `${prefix}${s.trim()}`).join(",") + `{${tokens}}`;
171
235
  var CSS = `
@@ -186,8 +250,39 @@ ${scoped('[data-theme="dark"] ', TOKENS_DARK)}
186
250
  .authrobo-title{font:700 16px system-ui,sans-serif;color:var(--authrobo-title-fg);}
187
251
  .authrobo-muted{color:var(--authrobo-muted);font:14px system-ui,sans-serif;margin:0;}
188
252
  .authrobo-error{color:var(--authrobo-error);font:14px system-ui,sans-serif;margin:0;}
253
+ .authrobo-powered{display:block;margin:2px 0 -2px;text-align:center;text-decoration:none;
254
+ font:500 11.5px system-ui,sans-serif;color:var(--authrobo-muted);opacity:.85;transition:opacity .15s ease;}
255
+ .authrobo-powered:hover{opacity:1;}
256
+ .authrobo-powered b{font-weight:700;color:var(--authrobo-title-fg);}
189
257
  .authrobo-divider{display:flex;align-items:center;gap:10px;color:var(--authrobo-divider-fg);font:500 12px system-ui,sans-serif;}
190
258
  .authrobo-divider::before,.authrobo-divider::after{content:"";height:1px;flex:1;background:var(--authrobo-divider-line);}
259
+ .authrobo-tabs{display:flex;gap:4px;padding:4px;border:1px solid var(--authrobo-btn-border);
260
+ background:var(--authrobo-tabs-bg);border-radius:10px;}
261
+ .authrobo-tab{flex:1;border:0;background:transparent;border-radius:7px;padding:8px 0;cursor:pointer;
262
+ font:600 13px system-ui,sans-serif;color:var(--authrobo-muted);transition:background .15s ease,color .15s ease;}
263
+ .authrobo-tab:hover{color:var(--authrobo-title-fg);}
264
+ .authrobo-tab[aria-selected="true"]{background:var(--authrobo-accent);color:var(--authrobo-accent-fg);}
265
+ .authrobo-tab:focus-visible{outline:2px solid var(--authrobo-focus);outline-offset:2px;}
266
+ .authrobo-form{display:flex;flex-direction:column;gap:10px;}
267
+ .authrobo-label{font:500 12.5px system-ui,sans-serif;color:var(--authrobo-muted);}
268
+ .authrobo-input{width:100%;box-sizing:border-box;margin-top:6px;border:1px solid var(--authrobo-btn-border);
269
+ background:var(--authrobo-input-bg);color:var(--authrobo-input-fg);border-radius:10px;padding:10px 12px;
270
+ font:14px system-ui,sans-serif;}
271
+ .authrobo-input:focus{outline:none;border-color:var(--authrobo-accent);}
272
+ .authrobo-primary{display:flex;width:100%;align-items:center;justify-content:center;gap:8px;border:0;
273
+ background:var(--authrobo-accent);color:var(--authrobo-accent-fg);border-radius:10px;padding:11px 14px;
274
+ font:700 14px system-ui,sans-serif;cursor:pointer;transition:filter .15s ease;}
275
+ .authrobo-primary:hover{filter:brightness(.96);}
276
+ .authrobo-primary:focus-visible{outline:2px solid var(--authrobo-focus);outline-offset:2px;}
277
+ .authrobo-primary:disabled{opacity:.7;cursor:default;}
278
+ .authrobo-btn-brand{transition:filter .15s ease,box-shadow .15s ease;}
279
+ .authrobo-btn-brand:hover{filter:brightness(.96);}
280
+ .authrobo-spinner{display:inline-block;width:1.15em;height:1.15em;vertical-align:-0.2em;border-radius:50%;
281
+ border:2px solid color-mix(in srgb, currentColor 24%, transparent);border-top-color:currentColor;
282
+ animation:authrobo-spin .7s linear infinite;}
283
+ .authrobo-loading{display:flex;align-items:center;justify-content:center;gap:10px;padding:20px 0;}
284
+ @keyframes authrobo-spin{to{transform:rotate(360deg)}}
285
+ @media (prefers-reduced-motion: reduce){.authrobo-spinner{animation-duration:1.4s}}
191
286
  .authrobo-overlay{position:fixed;inset:0;z-index:2147483000;display:flex;align-items:center;
192
287
  justify-content:center;padding:16px;background:var(--authrobo-overlay);backdrop-filter:blur(2px);
193
288
  animation:authrobo-fade .16s ease-out both;}
@@ -209,6 +304,17 @@ function useInjectStyles() {
209
304
  document.head.appendChild(el);
210
305
  }, []);
211
306
  }
307
+ function Spinner({ style, className }) {
308
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
309
+ "span",
310
+ {
311
+ className: className ? `authrobo-spinner ${className}` : "authrobo-spinner",
312
+ role: "status",
313
+ "aria-label": "Loading",
314
+ style
315
+ }
316
+ );
317
+ }
212
318
  function AuthRoboButton({
213
319
  provider,
214
320
  config,
@@ -216,36 +322,216 @@ function AuthRoboButton({
216
322
  className,
217
323
  style,
218
324
  onClick,
219
- lastUsed
325
+ lastUsed,
326
+ startPath,
327
+ buttonStyle = "neutral"
220
328
  }) {
221
329
  useInjectStyles();
222
330
  const c = useResolvedConfig(config);
223
331
  const meta = PROVIDER_META[provider];
332
+ const brand = buttonStyle === "branded" ? BRAND_STYLE[provider] : void 0;
333
+ const icon = brand?.whiteIcon ? WHITE_MARKS[provider] ?? meta.icon : meta.icon;
334
+ const classes = ["authrobo-btn", brand ? "authrobo-btn-brand" : "", className].filter(Boolean).join(" ");
335
+ const brandStyle = brand ? { background: brand.bg, color: brand.fg, borderColor: brand.border } : void 0;
224
336
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
225
337
  "button",
226
338
  {
227
339
  type: "button",
228
- className: className ? `authrobo-btn ${className}` : "authrobo-btn",
229
- style,
340
+ className: classes,
341
+ style: { ...brandStyle, ...style },
230
342
  onClick: () => {
231
343
  onClick?.();
232
- startSignIn(c, provider);
344
+ startSignIn(c, provider, { startPath });
233
345
  },
234
346
  children: [
235
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { display: "inline-flex", alignItems: "center" }, children: meta.icon }),
347
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { display: "inline-flex", alignItems: "center" }, children: icon }),
236
348
  label ?? `Continue with ${meta.label}`,
237
349
  lastUsed && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "authrobo-last", children: "Last used" })
238
350
  ]
239
351
  }
240
352
  );
241
353
  }
354
+ var EMAIL_TABS = [
355
+ { key: "magic", method: "magic_link", label: "Magic Link", cta: "Email me a link", requires: "magic_link", fields: { name: false, password: false }, proxyPath: "magic" },
356
+ { key: "register", method: "password", label: "Register", cta: "Create account", requires: "password", fields: { name: true, password: true }, proxyPath: "register" },
357
+ { key: "login", method: "password", label: "Login", cta: "Sign in", requires: "password", fields: { name: false, password: true }, proxyPath: "login" }
358
+ ];
359
+ function EmailSwitcher({
360
+ config,
361
+ methods,
362
+ startPath,
363
+ onSuccess
364
+ }) {
365
+ const tabs = (0, import_react.useMemo)(() => EMAIL_TABS.filter((t) => methods.includes(t.requires)), [methods]);
366
+ const [active, setActive] = (0, import_react.useState)(tabs[0]?.key ?? "login");
367
+ const [name, setName] = (0, import_react.useState)("");
368
+ const [email, setEmail] = (0, import_react.useState)("");
369
+ const [password, setPassword] = (0, import_react.useState)("");
370
+ const [loading, setLoading] = (0, import_react.useState)(false);
371
+ const [error, setError] = (0, import_react.useState)(null);
372
+ const [sent, setSent] = (0, import_react.useState)(false);
373
+ const stateRef = (0, import_react.useRef)(null);
374
+ const proxy = !!startPath;
375
+ (0, import_react.useEffect)(() => {
376
+ if (tabs.length && !tabs.some((t) => t.key === active)) setActive(tabs[0].key);
377
+ }, [tabs, active]);
378
+ if (!tabs.length) return null;
379
+ const current = tabs.find((t) => t.key === active) ?? tabs[0];
380
+ const base = config.issuerUrl.replace(/\/$/, "");
381
+ const directAction = current.key === "magic" ? `${base}/api/v1/auth/magic/start` : `${base}/api/v1/auth/authorize`;
382
+ const selectTab = (key) => {
383
+ setActive(key);
384
+ setError(null);
385
+ setSent(false);
386
+ };
387
+ async function proxySubmit(e) {
388
+ e.preventDefault();
389
+ setError(null);
390
+ setLoading(true);
391
+ try {
392
+ const body = { email: email.trim() };
393
+ if (current.fields.password) body.password = password;
394
+ if (current.fields.name && name.trim()) body.name = name.trim();
395
+ const res = await fetch(`${startPath.replace(/\/$/, "")}/${current.proxyPath}`, {
396
+ method: "POST",
397
+ headers: { "content-type": "application/json" },
398
+ body: JSON.stringify(body)
399
+ });
400
+ if (!res.ok) {
401
+ const d = await res.json().catch(() => ({}));
402
+ throw new Error(d?.error || "Something went wrong. Please try again.");
403
+ }
404
+ try {
405
+ localStorage.setItem(lastMethodKey(config.clientId), current.method);
406
+ } catch {
407
+ }
408
+ if (current.key === "magic") setSent(true);
409
+ else onSuccess?.();
410
+ } catch (err) {
411
+ setError(err instanceof Error ? err.message : "Something went wrong.");
412
+ } finally {
413
+ setLoading(false);
414
+ }
415
+ }
416
+ function directSubmit() {
417
+ const s = randomState();
418
+ try {
419
+ sessionStorage.setItem("authrobo:state", s);
420
+ } catch {
421
+ }
422
+ if (stateRef.current) stateRef.current.value = s;
423
+ try {
424
+ localStorage.setItem(lastMethodKey(config.clientId), current.method);
425
+ } catch {
426
+ }
427
+ setLoading(true);
428
+ }
429
+ if (sent) {
430
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "authrobo-stack", role: "status", children: [
431
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "authrobo-title", children: "Check your email" }),
432
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { className: "authrobo-muted", children: [
433
+ "We sent a sign-in link to ",
434
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("b", { children: email.trim() }),
435
+ ". It works once and expires shortly."
436
+ ] })
437
+ ] });
438
+ }
439
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "authrobo-stack", children: [
440
+ tabs.length > 1 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "authrobo-tabs", role: "tablist", "aria-label": "Sign-in method", children: tabs.map((t) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
441
+ "button",
442
+ {
443
+ type: "button",
444
+ role: "tab",
445
+ "aria-selected": current.key === t.key,
446
+ className: "authrobo-tab",
447
+ onClick: () => selectTab(t.key),
448
+ children: t.label
449
+ },
450
+ t.key
451
+ )) }),
452
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
453
+ "form",
454
+ {
455
+ className: "authrobo-form",
456
+ method: proxy ? void 0 : "post",
457
+ action: proxy ? void 0 : directAction,
458
+ onSubmit: proxy ? proxySubmit : directSubmit,
459
+ children: [
460
+ !proxy && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
461
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("input", { type: "hidden", name: "client_id", value: config.clientId }),
462
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("input", { type: "hidden", name: "redirect_uri", value: config.redirectUri }),
463
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("input", { type: "hidden", name: "state", ref: stateRef }),
464
+ current.key !== "magic" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("input", { type: "hidden", name: "mode", value: current.key === "register" ? "register" : "login" })
465
+ ] }),
466
+ current.fields.name && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "authrobo-label", children: [
467
+ "Name",
468
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
469
+ "input",
470
+ {
471
+ className: "authrobo-input",
472
+ name: "name",
473
+ type: "text",
474
+ autoComplete: "name",
475
+ placeholder: "Your name",
476
+ value: name,
477
+ onChange: (e) => setName(e.target.value)
478
+ }
479
+ )
480
+ ] }),
481
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "authrobo-label", children: [
482
+ "Email",
483
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
484
+ "input",
485
+ {
486
+ className: "authrobo-input",
487
+ name: "email",
488
+ type: "email",
489
+ required: true,
490
+ autoComplete: "email",
491
+ placeholder: "you@example.com",
492
+ value: email,
493
+ onChange: (e) => setEmail(e.target.value)
494
+ }
495
+ )
496
+ ] }),
497
+ current.fields.password && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "authrobo-label", children: [
498
+ "Password",
499
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
500
+ "input",
501
+ {
502
+ className: "authrobo-input",
503
+ name: "password",
504
+ type: "password",
505
+ required: true,
506
+ minLength: current.key === "register" ? 8 : void 0,
507
+ autoComplete: current.key === "register" ? "new-password" : "current-password",
508
+ placeholder: current.key === "register" ? "At least 8 characters" : "Your password",
509
+ value: password,
510
+ onChange: (e) => setPassword(e.target.value)
511
+ }
512
+ )
513
+ ] }),
514
+ error && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "authrobo-error", children: error }),
515
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "submit", className: "authrobo-primary", disabled: loading, children: loading ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
516
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Spinner, {}),
517
+ " Please wait\u2026"
518
+ ] }) : current.cta })
519
+ ]
520
+ }
521
+ )
522
+ ] });
523
+ }
242
524
  function AuthRoboAuth({
243
525
  config,
244
526
  mode = "inline",
245
527
  open = true,
246
528
  onClose,
529
+ onSuccess,
247
530
  title,
248
- methods: only
531
+ showHeader = true,
532
+ methods: only,
533
+ startPath,
534
+ buttonStyle = "neutral"
249
535
  }) {
250
536
  useInjectStyles();
251
537
  const c = useResolvedConfig(config);
@@ -266,25 +552,33 @@ function AuthRoboAuth({
266
552
  const email = shown.filter((m) => EMAIL_METHODS.includes(m));
267
553
  const badge = (m) => shown.length > 1 && m === lastMethod;
268
554
  const panel = /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "authrobo-stack", children: [
269
- (data?.logoUrl || data?.name) && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 10, marginBottom: 4 }, children: [
555
+ showHeader && (data?.logoUrl || data?.name) && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 10, marginBottom: 4 }, children: [
270
556
  data?.logoUrl && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("img", { src: data.logoUrl, alt: "", width: 28, height: 28, style: { borderRadius: 6 } }),
271
557
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "authrobo-title", children: title ?? `Sign in${data?.name ? ` to ${data.name}` : ""}` })
272
558
  ] }),
273
- loading && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "authrobo-muted", children: "Loading sign-in options\u2026" }),
559
+ loading && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "authrobo-loading", children: [
560
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Spinner, { style: { color: "var(--authrobo-accent)" } }),
561
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "authrobo-muted", children: "Loading sign-in options\u2026" })
562
+ ] }),
274
563
  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)(
564
+ social.map((m) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
278
565
  AuthRoboButton,
279
566
  {
280
567
  provider: m,
281
568
  config: c,
282
- label: m === "magic_link" ? "Email me a sign-in link" : "Continue with email",
283
- lastUsed: badge(m)
569
+ lastUsed: badge(m),
570
+ startPath,
571
+ buttonStyle
284
572
  },
285
573
  m
286
574
  )),
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." })
575
+ email.length > 0 && social.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "authrobo-divider", children: "or" }),
576
+ email.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(EmailSwitcher, { config: c, methods: email, startPath, onSuccess }),
577
+ !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." }),
578
+ !loading && !error && data?.poweredBy && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("a", { className: "authrobo-powered", href: "https://authrobo.com", target: "_blank", rel: "noreferrer", children: [
579
+ "Authentication provided by ",
580
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("b", { children: "AuthRobo" })
581
+ ] })
288
582
  ] });
289
583
  if (mode === "inline") return panel;
290
584
  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: [
@@ -297,7 +591,10 @@ function AuthRoboAuth({
297
591
  AuthRoboAuth,
298
592
  AuthRoboButton,
299
593
  AuthRoboProvider,
594
+ BRAND_STYLE,
300
595
  PROVIDER_META,
596
+ Spinner,
597
+ prefetchAppConfig,
301
598
  startSignIn,
302
599
  useAppConfig,
303
600
  useLastMethod