@authrobo/react 0.2.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +160 -33
- package/dist/index.cjs +340 -51
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +78 -15
- package/dist/index.d.ts +78 -15
- package/dist/index.js +348 -53
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
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)(
|
|
65
|
-
github: { label: "GitHub", icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
66
|
-
apple: { label: "Apple", icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
|
|
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
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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.
|
|
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)(
|
|
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
|
-
|
|
283
|
-
|
|
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
|