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