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