flowlink-auth 2.8.1 → 2.8.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/SignIn.js +181 -31
- package/dist/SignUp.js +259 -262
- package/package.json +1 -1
- package/src/SignIn.jsx +206 -43
- package/src/SignUp.jsx +326 -326
package/dist/SignIn.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import React, { useState } from "react";
|
|
2
|
+
import React, { useEffect, useRef, useState } from "react";
|
|
3
|
+
import Link from "next/link";
|
|
3
4
|
import { useAuth } from "./provider.js";
|
|
4
5
|
function SignIn({ onSuccess } = {}) {
|
|
5
6
|
const {
|
|
@@ -12,12 +13,45 @@ function SignIn({ onSuccess } = {}) {
|
|
|
12
13
|
completeLogin,
|
|
13
14
|
fetchMe,
|
|
14
15
|
setUser
|
|
15
|
-
} = useAuth();
|
|
16
|
+
} = (typeof useAuth === "function" ? useAuth() : {}) || {};
|
|
16
17
|
const [email, setEmail] = useState("");
|
|
17
18
|
const [password, setPassword] = useState("");
|
|
18
19
|
const [loading, setLoading] = useState(false);
|
|
19
|
-
const [
|
|
20
|
-
const
|
|
20
|
+
const [loadingOauth, setLoadingOauth] = useState({ google: false, github: false });
|
|
21
|
+
const redirectTimer = useRef(null);
|
|
22
|
+
const toastId = useRef(0);
|
|
23
|
+
const [toasts, setToasts] = useState([]);
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
const meta = document.createElement("meta");
|
|
26
|
+
meta.name = "viewport";
|
|
27
|
+
meta.content = "width=device-width, initial-scale=1, maximum-scale=1";
|
|
28
|
+
document.head.appendChild(meta);
|
|
29
|
+
return () => {
|
|
30
|
+
if (redirectTimer.current) clearTimeout(redirectTimer.current);
|
|
31
|
+
const existing = document.querySelector('meta[name="viewport"]');
|
|
32
|
+
if (existing && existing.content === meta.content) document.head.removeChild(existing);
|
|
33
|
+
toasts.forEach((t) => {
|
|
34
|
+
if (t._timer) clearTimeout(t._timer);
|
|
35
|
+
});
|
|
36
|
+
};
|
|
37
|
+
}, []);
|
|
38
|
+
function showToast(type, message, ms = 5e3) {
|
|
39
|
+
const id = ++toastId.current;
|
|
40
|
+
const t = { id, type, message, _timer: null };
|
|
41
|
+
setToasts((prev) => [t, ...prev].slice(0, 6));
|
|
42
|
+
const timer = setTimeout(() => {
|
|
43
|
+
setToasts((prev) => prev.filter((x) => x.id !== id));
|
|
44
|
+
}, ms);
|
|
45
|
+
t._timer = timer;
|
|
46
|
+
}
|
|
47
|
+
function removeToast(id) {
|
|
48
|
+
setToasts((prev) => {
|
|
49
|
+
prev.forEach((t) => {
|
|
50
|
+
if (t.id === id && t._timer) clearTimeout(t._timer);
|
|
51
|
+
});
|
|
52
|
+
return prev.filter((x) => x.id !== id);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
21
55
|
if (loadingUser) return null;
|
|
22
56
|
if (user && redirect) {
|
|
23
57
|
if (typeof redirectTo === "function") redirectTo(redirect);
|
|
@@ -26,15 +60,15 @@ function SignIn({ onSuccess } = {}) {
|
|
|
26
60
|
}
|
|
27
61
|
async function submit(e) {
|
|
28
62
|
e.preventDefault();
|
|
29
|
-
|
|
30
|
-
|
|
63
|
+
if (loading) return;
|
|
64
|
+
setLoading(true);
|
|
31
65
|
if (!email || !password) {
|
|
32
|
-
|
|
66
|
+
showToast("error", "Email and password are required");
|
|
67
|
+
setLoading(false);
|
|
33
68
|
return;
|
|
34
69
|
}
|
|
35
|
-
|
|
70
|
+
const endpoint = `${(baseUrl || "").replace(/\/+$/, "")}/api/sdk/login`;
|
|
36
71
|
try {
|
|
37
|
-
const endpoint = `${(baseUrl || "").replace(/\/+$/, "")}/api/sdk/login`;
|
|
38
72
|
const res = await fetch(endpoint, {
|
|
39
73
|
method: "POST",
|
|
40
74
|
credentials: "include",
|
|
@@ -46,9 +80,9 @@ function SignIn({ onSuccess } = {}) {
|
|
|
46
80
|
});
|
|
47
81
|
const ct = res.headers.get("content-type") || "";
|
|
48
82
|
let data = {};
|
|
49
|
-
if (ct.includes("application/json")) data = await res.json();
|
|
83
|
+
if (ct.includes("application/json")) data = await res.json().catch(() => ({}));
|
|
50
84
|
else {
|
|
51
|
-
const text = await res.text();
|
|
85
|
+
const text = await res.text().catch(() => "");
|
|
52
86
|
throw new Error(`Unexpected response (status ${res.status}): ${text.slice(0, 200)}`);
|
|
53
87
|
}
|
|
54
88
|
if (!res.ok) throw new Error(data.error || data.message || `Login failed (status ${res.status})`);
|
|
@@ -74,26 +108,27 @@ function SignIn({ onSuccess } = {}) {
|
|
|
74
108
|
} catch (_) {
|
|
75
109
|
}
|
|
76
110
|
}
|
|
77
|
-
|
|
111
|
+
showToast("success", "Signed in. Redirecting...");
|
|
78
112
|
if (redirect) {
|
|
79
|
-
setTimeout(() => {
|
|
113
|
+
redirectTimer.current = setTimeout(() => {
|
|
80
114
|
if (typeof redirectTo === "function") redirectTo(redirect);
|
|
81
115
|
else if (typeof window !== "undefined") window.location.assign(redirect);
|
|
82
116
|
}, 250);
|
|
83
117
|
}
|
|
84
118
|
} catch (err) {
|
|
85
|
-
|
|
119
|
+
showToast("error", err?.message || "Network error");
|
|
120
|
+
console.error("Signin error:", err);
|
|
86
121
|
} finally {
|
|
87
122
|
setLoading(false);
|
|
88
123
|
}
|
|
89
124
|
}
|
|
90
125
|
async function startOAuthFlow(provider) {
|
|
91
|
-
|
|
92
|
-
|
|
126
|
+
if (loading || loadingOauth[provider]) return;
|
|
127
|
+
setLoadingOauth((prev) => ({ ...prev, [provider]: true }));
|
|
93
128
|
try {
|
|
94
129
|
const rid = typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID() : `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
|
|
95
|
-
const callbackUrl = encodeURIComponent(`${window.location.origin}/signin`);
|
|
96
|
-
const sdkBase = typeof
|
|
130
|
+
const callbackUrl = encodeURIComponent(`${typeof window !== "undefined" ? window.location.origin : ""}/signin`);
|
|
131
|
+
const sdkBase = baseUrl || (typeof window !== "undefined" ? window.location.origin.replace(/\/+$/, "") : "");
|
|
97
132
|
const startUrl = `${sdkBase.replace(/\/+$/, "")}/sdk/auth/start?rid=${rid}&source=${encodeURIComponent(provider)}&callbackUrl=${callbackUrl}`;
|
|
98
133
|
if (!publishableKey) {
|
|
99
134
|
throw new Error("Missing publishable key (client side). Set NEXT_PUBLIC_FLOWLINK_PUBLISHABLE_KEY or provide publishableKey in provider.");
|
|
@@ -106,11 +141,11 @@ function SignIn({ onSuccess } = {}) {
|
|
|
106
141
|
const data = await res.json().catch(() => null);
|
|
107
142
|
if (!res.ok) throw new Error(data?.error || `OAuth start failed (${res.status})`);
|
|
108
143
|
if (!data?.oauthUrl) throw new Error("SDK start did not return oauthUrl");
|
|
109
|
-
window.location.href = data.oauthUrl;
|
|
144
|
+
if (typeof window !== "undefined") window.location.href = data.oauthUrl;
|
|
110
145
|
} catch (err) {
|
|
146
|
+
showToast("error", err?.message || "OAuth start failed");
|
|
111
147
|
console.error("OAuth start error:", err);
|
|
112
|
-
|
|
113
|
-
setLoading(false);
|
|
148
|
+
setLoadingOauth((prev) => ({ ...prev, [provider]: false }));
|
|
114
149
|
}
|
|
115
150
|
}
|
|
116
151
|
const handleGoogle = (e) => {
|
|
@@ -121,7 +156,26 @@ function SignIn({ onSuccess } = {}) {
|
|
|
121
156
|
if (e && typeof e.preventDefault === "function") e.preventDefault();
|
|
122
157
|
startOAuthFlow("github");
|
|
123
158
|
};
|
|
124
|
-
return /* @__PURE__ */ React.createElement("div", { style: overlay }, /* @__PURE__ */ React.createElement("div", { style:
|
|
159
|
+
return /* @__PURE__ */ React.createElement("div", { style: overlay }, /* @__PURE__ */ React.createElement("div", { style: toastContainer, "aria-live": "polite", "aria-atomic": "true" }, toasts.map((t) => /* @__PURE__ */ React.createElement(
|
|
160
|
+
"div",
|
|
161
|
+
{
|
|
162
|
+
key: t.id,
|
|
163
|
+
role: "status",
|
|
164
|
+
style: {
|
|
165
|
+
...toastBase,
|
|
166
|
+
...t.type === "error" ? toastError : t.type === "success" ? toastSuccess : toastInfo
|
|
167
|
+
},
|
|
168
|
+
onMouseEnter: () => {
|
|
169
|
+
if (t._timer) clearTimeout(t._timer);
|
|
170
|
+
},
|
|
171
|
+
onMouseLeave: () => {
|
|
172
|
+
const timer = setTimeout(() => removeToast(t.id), 3e3);
|
|
173
|
+
setToasts((prev) => prev.map((x) => x.id === t.id ? { ...x, _timer: timer } : x));
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
/* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }, t.message),
|
|
177
|
+
/* @__PURE__ */ React.createElement("button", { "aria-label": "Dismiss", onClick: () => removeToast(t.id), style: toastCloseBtn }, "\u2715")
|
|
178
|
+
))), /* @__PURE__ */ React.createElement("div", { style: modal }, /* @__PURE__ */ React.createElement("h2", { style: title }, "Sign in"), /* @__PURE__ */ React.createElement("p", { style: subtitle }, "Welcome back \u2014 enter your credentials."), /* @__PURE__ */ React.createElement("form", { onSubmit: submit, style: { width: "100%" } }, /* @__PURE__ */ React.createElement("label", { style: label }, "Email"), /* @__PURE__ */ React.createElement(
|
|
125
179
|
"input",
|
|
126
180
|
{
|
|
127
181
|
style: input,
|
|
@@ -139,19 +193,115 @@ function SignIn({ onSuccess } = {}) {
|
|
|
139
193
|
onChange: (e) => setPassword(e.target.value),
|
|
140
194
|
required: true
|
|
141
195
|
}
|
|
142
|
-
), /* @__PURE__ */ React.createElement("div", { style: { marginTop: 12 } }, /* @__PURE__ */ React.createElement("button", { style: button, type: "submit", disabled: loading }, loading ? "Signing in\u2026" : "Sign in")), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", gap: 8, marginTop: 16 } }, /* @__PURE__ */ React.createElement("button", { type: "button", onClick: handleGoogle, style: oauthButtonGoogle, disabled: loading }, "
|
|
196
|
+
), /* @__PURE__ */ React.createElement("div", { style: { marginTop: 12 } }, /* @__PURE__ */ React.createElement("button", { style: button, type: "submit", disabled: loading }, loading ? "Signing in\u2026" : "Sign in")), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", gap: 8, marginTop: 16 } }, /* @__PURE__ */ React.createElement("button", { type: "button", onClick: handleGoogle, style: oauthButtonGoogle, disabled: loading || loadingOauth.google }, /* @__PURE__ */ React.createElement("svg", { width: 18, style: { marginRight: 8 }, viewBox: "-3 0 262 262", xmlns: "http://www.w3.org/2000/svg", fill: "#000000", "aria-hidden": true }, /* @__PURE__ */ React.createElement("path", { d: "M255.878 133.451c0-10.734-.871-18.567-2.756-26.69H130.55v48.448h71.947c-1.45 12.04-9.283 30.172-26.69 42.356l-.244 1.622 38.755 30.023 2.685.268c24.659-22.774 38.875-56.282 38.875-96.027", fill: "#4285F4" }), /* @__PURE__ */ React.createElement("path", { d: "M130.55 261.1c35.248 0 64.839-11.605 86.453-31.622l-41.196-31.913c-11.024 7.688-25.82 13.055-45.257 13.055-34.523 0-63.824-22.773-74.269-54.25l-1.531.13-40.298 31.187-.527 1.465C35.393 231.798 79.49 261.1 130.55 261.1", fill: "#34A853" }), /* @__PURE__ */ React.createElement("path", { d: "M56.281 156.37c-2.756-8.123-4.351-16.827-4.351-25.82 0-8.994 1.595-17.697 4.206-25.82l-.073-1.73L15.26 71.312l-1.335.635C5.077 89.644 0 109.517 0 130.55s5.077 40.905 13.925 58.602l42.356-32.782", fill: "#FBBC05" }), /* @__PURE__ */ React.createElement("path", { d: "M130.55 50.479c24.514 0 41.05 10.589 50.479 19.438l36.844-35.974C195.245 12.91 165.798 0 130.55 0 79.49 0 35.393 29.301 13.925 71.947l42.211 32.783c10.59-31.477 39.891-54.251 74.414-54.251", fill: "#EB4335" })), /* @__PURE__ */ React.createElement("span", null, loadingOauth.google ? "Loading..." : "Google")), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: handleGithub, style: oauthButtonGithub, disabled: loading || loadingOauth.github }, /* @__PURE__ */ React.createElement("svg", { width: 18, style: { marginRight: 8 }, xmlns: "http://www.w3.org/2000/svg", fill: "white", viewBox: "0 0 20 20", "aria-hidden": true }, /* @__PURE__ */ React.createElement("path", { fillRule: "evenodd", d: "M10 .333A9.911 9.911 0 0 0 6.866 19.65c.5.092.678-.215.678-.477 0-.237-.01-1.017-.014-1.845-2.757.6-3.338-1.169-3.338-1.169a2.627 2.627 0 0 0-1.1-1.451c-.9-.615.07-.6.07-.6a2.084 2.084 0 0 1 1.518 1.021 2.11 2.11 0 0 0 2.884.823c.044-.503.268-.973.63-1.325-2.2-.25-4.516-1.1-4.516-4.9A3.832 3.832 0 0 1 4.7 7.068a3.56 3.56 0 0 1 .095-2.623s.832-.266 2.726 1.016a9.409 9.409 0 0 1 4.962 0c1.89-1.282 2.717-1.016 2.717-1.016.366.83.402 1.768.1 2.623a3.827 3.827 0 0 1 1.02 2.659c0 3.807-2.319 4.644-4.525 4.889a2.366 2.366 0 0 1 .673 1.834c0 1.326-.012 2.394-.012 2.72 0 .263.18.572.681.475A9.911 9.911 0 0 0 10 .333Z", clipRule: "evenodd" })), /* @__PURE__ */ React.createElement("span", null, loadingOauth.github ? "Loading..." : "GitHub"))))));
|
|
143
197
|
}
|
|
144
|
-
const overlay = {
|
|
145
|
-
|
|
198
|
+
const overlay = {
|
|
199
|
+
position: "fixed",
|
|
200
|
+
inset: 0,
|
|
201
|
+
display: "block",
|
|
202
|
+
// allow page scroll
|
|
203
|
+
padding: 20,
|
|
204
|
+
background: "linear-gradient(180deg, rgba(2,6,23,0.22), rgba(2,6,23,0.32))",
|
|
205
|
+
backdropFilter: "blur(6px)",
|
|
206
|
+
overflowY: "auto",
|
|
207
|
+
WebkitOverflowScrolling: "touch",
|
|
208
|
+
minHeight: "100vh",
|
|
209
|
+
zIndex: 9999
|
|
210
|
+
};
|
|
211
|
+
const modal = {
|
|
212
|
+
width: "100%",
|
|
213
|
+
maxWidth: 460,
|
|
214
|
+
margin: "40px auto",
|
|
215
|
+
borderRadius: 14,
|
|
216
|
+
background: "linear-gradient(180deg, rgba(15,19,24,0.85), rgba(10,12,16,0.85))",
|
|
217
|
+
border: "1px solid rgba(99,102,106,0.12)",
|
|
218
|
+
boxShadow: "0 18px 48px rgba(2,6,23,0.55), inset 0 1px 0 rgba(255,255,255,0.02)",
|
|
219
|
+
padding: 22,
|
|
220
|
+
color: "#fff"
|
|
221
|
+
};
|
|
146
222
|
const title = { margin: 0, fontSize: 20, fontWeight: 600 };
|
|
147
223
|
const subtitle = { marginTop: 6, marginBottom: 14, color: "#cbd5e1", fontSize: 13 };
|
|
148
224
|
const label = { display: "block", color: "#cbd5e1", fontSize: 13, marginTop: 8 };
|
|
149
|
-
const input = {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
225
|
+
const input = {
|
|
226
|
+
width: "100%",
|
|
227
|
+
padding: "10px 12px",
|
|
228
|
+
marginTop: 6,
|
|
229
|
+
borderRadius: 10,
|
|
230
|
+
border: "1px solid rgba(148,163,184,0.10)",
|
|
231
|
+
background: "rgba(255,255,255,0.02)",
|
|
232
|
+
color: "#e6e6e6",
|
|
233
|
+
boxSizing: "border-box"
|
|
234
|
+
};
|
|
235
|
+
const button = {
|
|
236
|
+
width: "100%",
|
|
237
|
+
padding: "10px 12px",
|
|
238
|
+
borderRadius: 10,
|
|
239
|
+
background: "linear-gradient(90deg,#06b6d4,#2563eb)",
|
|
240
|
+
color: "#0b1220",
|
|
241
|
+
border: "none",
|
|
242
|
+
fontWeight: 700,
|
|
243
|
+
cursor: "pointer"
|
|
244
|
+
};
|
|
245
|
+
const oauthButtonGoogle = {
|
|
246
|
+
flex: 1,
|
|
247
|
+
padding: "10px 12px",
|
|
248
|
+
borderRadius: 10,
|
|
249
|
+
background: "linear-gradient(180deg, rgba(255,255,255,0.03), rgba(255,255,255,0.01))",
|
|
250
|
+
color: "#fff",
|
|
251
|
+
border: "1px solid rgba(148,163,184,0.08)",
|
|
252
|
+
cursor: "pointer",
|
|
253
|
+
display: "inline-flex",
|
|
254
|
+
alignItems: "center",
|
|
255
|
+
justifyContent: "center",
|
|
256
|
+
gap: 8
|
|
257
|
+
};
|
|
258
|
+
const oauthButtonGithub = {
|
|
259
|
+
flex: 1,
|
|
260
|
+
padding: "10px 12px",
|
|
261
|
+
borderRadius: 10,
|
|
262
|
+
background: "linear-gradient(180deg, rgba(255,255,255,0.02), rgba(255,255,255,0.00))",
|
|
263
|
+
color: "#fff",
|
|
264
|
+
border: "1px solid rgba(148,163,184,0.08)",
|
|
265
|
+
cursor: "pointer",
|
|
266
|
+
display: "inline-flex",
|
|
267
|
+
alignItems: "center",
|
|
268
|
+
justifyContent: "center",
|
|
269
|
+
gap: 8
|
|
270
|
+
};
|
|
271
|
+
const toastContainer = {
|
|
272
|
+
position: "fixed",
|
|
273
|
+
top: 18,
|
|
274
|
+
right: 18,
|
|
275
|
+
width: 360,
|
|
276
|
+
maxWidth: "calc(100% - 36px)",
|
|
277
|
+
display: "flex",
|
|
278
|
+
flexDirection: "column",
|
|
279
|
+
gap: 10,
|
|
280
|
+
zIndex: 6e4
|
|
281
|
+
};
|
|
282
|
+
const toastBase = {
|
|
283
|
+
display: "flex",
|
|
284
|
+
gap: 10,
|
|
285
|
+
alignItems: "center",
|
|
286
|
+
padding: "10px 12px",
|
|
287
|
+
borderRadius: 10,
|
|
288
|
+
boxShadow: "0 8px 20px rgba(2,6,23,0.6)",
|
|
289
|
+
color: "#fff",
|
|
290
|
+
fontSize: 13,
|
|
291
|
+
minWidth: 120
|
|
292
|
+
};
|
|
293
|
+
const toastError = { background: "#000", border: "1px solid rgba(255,255,255,0.06)" };
|
|
294
|
+
const toastSuccess = { background: "#000", border: "1px solid rgba(255,255,255,0.06)" };
|
|
295
|
+
const toastInfo = { background: "#000", border: "1px solid rgba(255,255,255,0.06)" };
|
|
296
|
+
const toastCloseBtn = {
|
|
297
|
+
marginLeft: 8,
|
|
298
|
+
background: "transparent",
|
|
299
|
+
border: "none",
|
|
300
|
+
color: "rgba(255,255,255,0.7)",
|
|
301
|
+
cursor: "pointer",
|
|
302
|
+
fontSize: 14,
|
|
303
|
+
lineHeight: 1
|
|
304
|
+
};
|
|
155
305
|
export {
|
|
156
306
|
SignIn as default
|
|
157
307
|
};
|