@authrobo/react 0.2.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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 },
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 = `
@@ -188,6 +252,33 @@ ${scoped('[data-theme="dark"] ', TOKENS_DARK)}
188
252
  .authrobo-error{color:var(--authrobo-error);font:14px system-ui,sans-serif;margin:0;}
189
253
  .authrobo-divider{display:flex;align-items:center;gap:10px;color:var(--authrobo-divider-fg);font:500 12px system-ui,sans-serif;}
190
254
  .authrobo-divider::before,.authrobo-divider::after{content:"";height:1px;flex:1;background:var(--authrobo-divider-line);}
255
+ .authrobo-tabs{display:flex;gap:4px;padding:4px;border:1px solid var(--authrobo-btn-border);
256
+ background:var(--authrobo-tabs-bg);border-radius:10px;}
257
+ .authrobo-tab{flex:1;border:0;background:transparent;border-radius:7px;padding:8px 0;cursor:pointer;
258
+ font:600 13px system-ui,sans-serif;color:var(--authrobo-muted);transition:background .15s ease,color .15s ease;}
259
+ .authrobo-tab:hover{color:var(--authrobo-title-fg);}
260
+ .authrobo-tab[aria-selected="true"]{background:var(--authrobo-accent);color:var(--authrobo-accent-fg);}
261
+ .authrobo-tab:focus-visible{outline:2px solid var(--authrobo-focus);outline-offset:2px;}
262
+ .authrobo-form{display:flex;flex-direction:column;gap:10px;}
263
+ .authrobo-label{font:500 12.5px system-ui,sans-serif;color:var(--authrobo-muted);}
264
+ .authrobo-input{width:100%;box-sizing:border-box;margin-top:6px;border:1px solid var(--authrobo-btn-border);
265
+ background:var(--authrobo-input-bg);color:var(--authrobo-input-fg);border-radius:10px;padding:10px 12px;
266
+ font:14px system-ui,sans-serif;}
267
+ .authrobo-input:focus{outline:none;border-color:var(--authrobo-accent);}
268
+ .authrobo-primary{display:flex;width:100%;align-items:center;justify-content:center;gap:8px;border:0;
269
+ background:var(--authrobo-accent);color:var(--authrobo-accent-fg);border-radius:10px;padding:11px 14px;
270
+ font:700 14px system-ui,sans-serif;cursor:pointer;transition:filter .15s ease;}
271
+ .authrobo-primary:hover{filter:brightness(.96);}
272
+ .authrobo-primary:focus-visible{outline:2px solid var(--authrobo-focus);outline-offset:2px;}
273
+ .authrobo-primary:disabled{opacity:.7;cursor:default;}
274
+ .authrobo-btn-brand{transition:filter .15s ease,box-shadow .15s ease;}
275
+ .authrobo-btn-brand:hover{filter:brightness(.96);}
276
+ .authrobo-spinner{display:inline-block;width:1.15em;height:1.15em;vertical-align:-0.2em;border-radius:50%;
277
+ border:2px solid color-mix(in srgb, currentColor 24%, transparent);border-top-color:currentColor;
278
+ animation:authrobo-spin .7s linear infinite;}
279
+ .authrobo-loading{display:flex;align-items:center;gap:10px;padding:4px 0;}
280
+ @keyframes authrobo-spin{to{transform:rotate(360deg)}}
281
+ @media (prefers-reduced-motion: reduce){.authrobo-spinner{animation-duration:1.4s}}
191
282
  .authrobo-overlay{position:fixed;inset:0;z-index:2147483000;display:flex;align-items:center;
192
283
  justify-content:center;padding:16px;background:var(--authrobo-overlay);backdrop-filter:blur(2px);
193
284
  animation:authrobo-fade .16s ease-out both;}
@@ -209,6 +300,17 @@ function useInjectStyles() {
209
300
  document.head.appendChild(el);
210
301
  }, []);
211
302
  }
303
+ function Spinner({ style, className }) {
304
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
305
+ "span",
306
+ {
307
+ className: className ? `authrobo-spinner ${className}` : "authrobo-spinner",
308
+ role: "status",
309
+ "aria-label": "Loading",
310
+ style
311
+ }
312
+ );
313
+ }
212
314
  function AuthRoboButton({
213
315
  provider,
214
316
  config,
@@ -216,36 +318,216 @@ function AuthRoboButton({
216
318
  className,
217
319
  style,
218
320
  onClick,
219
- lastUsed
321
+ lastUsed,
322
+ startPath,
323
+ buttonStyle = "neutral"
220
324
  }) {
221
325
  useInjectStyles();
222
326
  const c = useResolvedConfig(config);
223
327
  const meta = PROVIDER_META[provider];
328
+ const brand = buttonStyle === "branded" ? BRAND_STYLE[provider] : void 0;
329
+ const icon = brand?.whiteIcon ? WHITE_MARKS[provider] ?? meta.icon : meta.icon;
330
+ const classes = ["authrobo-btn", brand ? "authrobo-btn-brand" : "", className].filter(Boolean).join(" ");
331
+ const brandStyle = brand ? { background: brand.bg, color: brand.fg, borderColor: brand.border } : void 0;
224
332
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
225
333
  "button",
226
334
  {
227
335
  type: "button",
228
- className: className ? `authrobo-btn ${className}` : "authrobo-btn",
229
- style,
336
+ className: classes,
337
+ style: { ...brandStyle, ...style },
230
338
  onClick: () => {
231
339
  onClick?.();
232
- startSignIn(c, provider);
340
+ startSignIn(c, provider, { startPath });
233
341
  },
234
342
  children: [
235
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { display: "inline-flex", alignItems: "center" }, children: meta.icon }),
343
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { display: "inline-flex", alignItems: "center" }, children: icon }),
236
344
  label ?? `Continue with ${meta.label}`,
237
345
  lastUsed && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "authrobo-last", children: "Last used" })
238
346
  ]
239
347
  }
240
348
  );
241
349
  }
350
+ var EMAIL_TABS = [
351
+ { key: "magic", method: "magic_link", label: "Magic Link", cta: "Email me a link", requires: "magic_link", fields: { name: false, password: false }, proxyPath: "magic" },
352
+ { key: "register", method: "password", label: "Register", cta: "Create account", requires: "password", fields: { name: true, password: true }, proxyPath: "register" },
353
+ { key: "login", method: "password", label: "Login", cta: "Sign in", requires: "password", fields: { name: false, password: true }, proxyPath: "login" }
354
+ ];
355
+ function EmailSwitcher({
356
+ config,
357
+ methods,
358
+ startPath,
359
+ onSuccess
360
+ }) {
361
+ const tabs = (0, import_react.useMemo)(() => EMAIL_TABS.filter((t) => methods.includes(t.requires)), [methods]);
362
+ const [active, setActive] = (0, import_react.useState)(tabs[0]?.key ?? "login");
363
+ const [name, setName] = (0, import_react.useState)("");
364
+ const [email, setEmail] = (0, import_react.useState)("");
365
+ const [password, setPassword] = (0, import_react.useState)("");
366
+ const [loading, setLoading] = (0, import_react.useState)(false);
367
+ const [error, setError] = (0, import_react.useState)(null);
368
+ const [sent, setSent] = (0, import_react.useState)(false);
369
+ const stateRef = (0, import_react.useRef)(null);
370
+ const proxy = !!startPath;
371
+ (0, import_react.useEffect)(() => {
372
+ if (tabs.length && !tabs.some((t) => t.key === active)) setActive(tabs[0].key);
373
+ }, [tabs, active]);
374
+ if (!tabs.length) return null;
375
+ const current = tabs.find((t) => t.key === active) ?? tabs[0];
376
+ const base = config.issuerUrl.replace(/\/$/, "");
377
+ const directAction = current.key === "magic" ? `${base}/api/v1/auth/magic/start` : `${base}/api/v1/auth/authorize`;
378
+ const selectTab = (key) => {
379
+ setActive(key);
380
+ setError(null);
381
+ setSent(false);
382
+ };
383
+ async function proxySubmit(e) {
384
+ e.preventDefault();
385
+ setError(null);
386
+ setLoading(true);
387
+ try {
388
+ const body = { email: email.trim() };
389
+ if (current.fields.password) body.password = password;
390
+ if (current.fields.name && name.trim()) body.name = name.trim();
391
+ const res = await fetch(`${startPath.replace(/\/$/, "")}/${current.proxyPath}`, {
392
+ method: "POST",
393
+ headers: { "content-type": "application/json" },
394
+ body: JSON.stringify(body)
395
+ });
396
+ if (!res.ok) {
397
+ const d = await res.json().catch(() => ({}));
398
+ throw new Error(d?.error || "Something went wrong. Please try again.");
399
+ }
400
+ try {
401
+ localStorage.setItem(lastMethodKey(config.clientId), current.method);
402
+ } catch {
403
+ }
404
+ if (current.key === "magic") setSent(true);
405
+ else onSuccess?.();
406
+ } catch (err) {
407
+ setError(err instanceof Error ? err.message : "Something went wrong.");
408
+ } finally {
409
+ setLoading(false);
410
+ }
411
+ }
412
+ function directSubmit() {
413
+ const s = randomState();
414
+ try {
415
+ sessionStorage.setItem("authrobo:state", s);
416
+ } catch {
417
+ }
418
+ if (stateRef.current) stateRef.current.value = s;
419
+ try {
420
+ localStorage.setItem(lastMethodKey(config.clientId), current.method);
421
+ } catch {
422
+ }
423
+ setLoading(true);
424
+ }
425
+ if (sent) {
426
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "authrobo-stack", role: "status", children: [
427
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "authrobo-title", children: "Check your email" }),
428
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { className: "authrobo-muted", children: [
429
+ "We sent a sign-in link to ",
430
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("b", { children: email.trim() }),
431
+ ". It works once and expires shortly."
432
+ ] })
433
+ ] });
434
+ }
435
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "authrobo-stack", children: [
436
+ 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)(
437
+ "button",
438
+ {
439
+ type: "button",
440
+ role: "tab",
441
+ "aria-selected": current.key === t.key,
442
+ className: "authrobo-tab",
443
+ onClick: () => selectTab(t.key),
444
+ children: t.label
445
+ },
446
+ t.key
447
+ )) }),
448
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
449
+ "form",
450
+ {
451
+ className: "authrobo-form",
452
+ method: proxy ? void 0 : "post",
453
+ action: proxy ? void 0 : directAction,
454
+ onSubmit: proxy ? proxySubmit : directSubmit,
455
+ children: [
456
+ !proxy && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
457
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("input", { type: "hidden", name: "client_id", value: config.clientId }),
458
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("input", { type: "hidden", name: "redirect_uri", value: config.redirectUri }),
459
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("input", { type: "hidden", name: "state", ref: stateRef }),
460
+ current.key !== "magic" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("input", { type: "hidden", name: "mode", value: current.key === "register" ? "register" : "login" })
461
+ ] }),
462
+ current.fields.name && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "authrobo-label", children: [
463
+ "Name",
464
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
465
+ "input",
466
+ {
467
+ className: "authrobo-input",
468
+ name: "name",
469
+ type: "text",
470
+ autoComplete: "name",
471
+ placeholder: "Your name",
472
+ value: name,
473
+ onChange: (e) => setName(e.target.value)
474
+ }
475
+ )
476
+ ] }),
477
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "authrobo-label", children: [
478
+ "Email",
479
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
480
+ "input",
481
+ {
482
+ className: "authrobo-input",
483
+ name: "email",
484
+ type: "email",
485
+ required: true,
486
+ autoComplete: "email",
487
+ placeholder: "you@example.com",
488
+ value: email,
489
+ onChange: (e) => setEmail(e.target.value)
490
+ }
491
+ )
492
+ ] }),
493
+ current.fields.password && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "authrobo-label", children: [
494
+ "Password",
495
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
496
+ "input",
497
+ {
498
+ className: "authrobo-input",
499
+ name: "password",
500
+ type: "password",
501
+ required: true,
502
+ minLength: current.key === "register" ? 8 : void 0,
503
+ autoComplete: current.key === "register" ? "new-password" : "current-password",
504
+ placeholder: current.key === "register" ? "At least 8 characters" : "Your password",
505
+ value: password,
506
+ onChange: (e) => setPassword(e.target.value)
507
+ }
508
+ )
509
+ ] }),
510
+ error && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "authrobo-error", children: error }),
511
+ /* @__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: [
512
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Spinner, {}),
513
+ " Please wait\u2026"
514
+ ] }) : current.cta })
515
+ ]
516
+ }
517
+ )
518
+ ] });
519
+ }
242
520
  function AuthRoboAuth({
243
521
  config,
244
522
  mode = "inline",
245
523
  open = true,
246
524
  onClose,
525
+ onSuccess,
247
526
  title,
248
- methods: only
527
+ showHeader = true,
528
+ methods: only,
529
+ startPath,
530
+ buttonStyle = "neutral"
249
531
  }) {
250
532
  useInjectStyles();
251
533
  const c = useResolvedConfig(config);
@@ -266,24 +548,28 @@ function AuthRoboAuth({
266
548
  const email = shown.filter((m) => EMAIL_METHODS.includes(m));
267
549
  const badge = (m) => shown.length > 1 && m === lastMethod;
268
550
  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: [
551
+ showHeader && (data?.logoUrl || data?.name) && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 10, marginBottom: 4 }, children: [
270
552
  data?.logoUrl && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("img", { src: data.logoUrl, alt: "", width: 28, height: 28, style: { borderRadius: 6 } }),
271
553
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "authrobo-title", children: title ?? `Sign in${data?.name ? ` to ${data.name}` : ""}` })
272
554
  ] }),
273
- loading && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "authrobo-muted", children: "Loading sign-in options\u2026" }),
555
+ loading && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "authrobo-loading", children: [
556
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Spinner, { style: { color: "var(--authrobo-accent)" } }),
557
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "authrobo-muted", children: "Loading sign-in options\u2026" })
558
+ ] }),
274
559
  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)(
560
+ social.map((m) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
278
561
  AuthRoboButton,
279
562
  {
280
563
  provider: m,
281
564
  config: c,
282
- label: m === "magic_link" ? "Email me a sign-in link" : "Continue with email",
283
- lastUsed: badge(m)
565
+ lastUsed: badge(m),
566
+ startPath,
567
+ buttonStyle
284
568
  },
285
569
  m
286
570
  )),
571
+ email.length > 0 && social.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "authrobo-divider", children: "or" }),
572
+ email.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(EmailSwitcher, { config: c, methods: email, startPath, onSuccess }),
287
573
  !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." })
288
574
  ] });
289
575
  if (mode === "inline") return panel;
@@ -297,7 +583,10 @@ function AuthRoboAuth({
297
583
  AuthRoboAuth,
298
584
  AuthRoboButton,
299
585
  AuthRoboProvider,
586
+ BRAND_STYLE,
300
587
  PROVIDER_META,
588
+ Spinner,
589
+ prefetchAppConfig,
301
590
  startSignIn,
302
591
  useAppConfig,
303
592
  useLastMethod