flowlink-auth 2.8.4 → 2.8.6
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 +174 -150
- package/package.json +1 -1
- package/src/SignIn.jsx +257 -208
package/dist/SignIn.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import React, { useEffect, useRef, useState } from "react";
|
|
3
|
+
import Image from "next/image";
|
|
3
4
|
import Link from "next/link";
|
|
5
|
+
import { ToastContainer, toast } from "react-toastify";
|
|
6
|
+
import "react-toastify/dist/ReactToastify.css";
|
|
4
7
|
import { useAuth } from "./provider.js";
|
|
5
|
-
function SignIn({ onSuccess } = {}) {
|
|
8
|
+
function SignIn({ agency = { name: "chest", logo: "/logo.png" }, onSuccess } = {}) {
|
|
6
9
|
const {
|
|
7
10
|
publishableKey,
|
|
8
11
|
baseUrl,
|
|
@@ -14,13 +17,10 @@ function SignIn({ onSuccess } = {}) {
|
|
|
14
17
|
fetchMe,
|
|
15
18
|
setUser
|
|
16
19
|
} = (typeof useAuth === "function" ? useAuth() : {}) || {};
|
|
17
|
-
const [
|
|
18
|
-
const [password, setPassword] = useState("");
|
|
20
|
+
const [form2, setForm] = useState({ email: "", password: "" });
|
|
19
21
|
const [loading, setLoading] = useState(false);
|
|
20
22
|
const [loadingOauth, setLoadingOauth] = useState({ google: false, github: false });
|
|
21
23
|
const redirectTimer = useRef(null);
|
|
22
|
-
const toastId = useRef(0);
|
|
23
|
-
const [toasts, setToasts] = useState([]);
|
|
24
24
|
useEffect(() => {
|
|
25
25
|
const meta = document.createElement("meta");
|
|
26
26
|
meta.name = "viewport";
|
|
@@ -30,40 +30,23 @@ function SignIn({ onSuccess } = {}) {
|
|
|
30
30
|
if (redirectTimer.current) clearTimeout(redirectTimer.current);
|
|
31
31
|
const existing = document.querySelector('meta[name="viewport"]');
|
|
32
32
|
if (existing && existing.content === meta.content) document.head.removeChild(existing);
|
|
33
|
-
toasts.forEach((t) => {
|
|
34
|
-
if (t._timer) clearTimeout(t._timer);
|
|
35
|
-
});
|
|
36
33
|
};
|
|
37
34
|
}, []);
|
|
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
|
-
}
|
|
55
35
|
if (loadingUser) return null;
|
|
56
36
|
if (user && redirect) {
|
|
57
37
|
if (typeof redirectTo === "function") redirectTo(redirect);
|
|
58
38
|
else if (typeof window !== "undefined") window.location.assign(redirect);
|
|
59
39
|
return null;
|
|
60
40
|
}
|
|
41
|
+
function handleChange(e) {
|
|
42
|
+
setForm((prev) => ({ ...prev, [e.target.id]: e.target.value }));
|
|
43
|
+
}
|
|
61
44
|
async function submit(e) {
|
|
62
45
|
e.preventDefault();
|
|
63
46
|
if (loading) return;
|
|
64
47
|
setLoading(true);
|
|
65
|
-
if (!email || !password) {
|
|
66
|
-
|
|
48
|
+
if (!form2.email || !form2.password) {
|
|
49
|
+
toast.error("Email and password are required", { toastId: "missing-creds", toastStyle: { background: "#000", color: "#fff" } });
|
|
67
50
|
setLoading(false);
|
|
68
51
|
return;
|
|
69
52
|
}
|
|
@@ -76,7 +59,7 @@ function SignIn({ onSuccess } = {}) {
|
|
|
76
59
|
"Content-Type": "application/json",
|
|
77
60
|
"x-publishable-key": publishableKey || ""
|
|
78
61
|
},
|
|
79
|
-
body: JSON.stringify({ email, password })
|
|
62
|
+
body: JSON.stringify({ email: form2.email, password: form2.password })
|
|
80
63
|
});
|
|
81
64
|
const ct = res.headers.get("content-type") || "";
|
|
82
65
|
let data = {};
|
|
@@ -108,7 +91,7 @@ function SignIn({ onSuccess } = {}) {
|
|
|
108
91
|
} catch (_) {
|
|
109
92
|
}
|
|
110
93
|
}
|
|
111
|
-
|
|
94
|
+
toast.success("Signed in. Redirecting...", { toastStyle: { background: "#000", color: "#fff" } });
|
|
112
95
|
if (redirect) {
|
|
113
96
|
redirectTimer.current = setTimeout(() => {
|
|
114
97
|
if (typeof redirectTo === "function") redirectTo(redirect);
|
|
@@ -116,7 +99,7 @@ function SignIn({ onSuccess } = {}) {
|
|
|
116
99
|
}, 250);
|
|
117
100
|
}
|
|
118
101
|
} catch (err) {
|
|
119
|
-
|
|
102
|
+
toast.error(err?.message || "Network error", { toastStyle: { background: "#000", color: "#fff" } });
|
|
120
103
|
console.error("Signin error:", err);
|
|
121
104
|
} finally {
|
|
122
105
|
setLoading(false);
|
|
@@ -130,9 +113,7 @@ function SignIn({ onSuccess } = {}) {
|
|
|
130
113
|
const callbackUrl = encodeURIComponent(`${typeof window !== "undefined" ? window.location.origin : ""}/signin`);
|
|
131
114
|
const sdkBase = baseUrl || (typeof window !== "undefined" ? window.location.origin.replace(/\/+$/, "") : "");
|
|
132
115
|
const startUrl = `${sdkBase.replace(/\/+$/, "")}/sdk/auth/start?rid=${rid}&source=${encodeURIComponent(provider)}&callbackUrl=${callbackUrl}`;
|
|
133
|
-
if (!publishableKey)
|
|
134
|
-
throw new Error("Missing publishable key (client side). Set NEXT_PUBLIC_FLOWLINK_PUBLISHABLE_KEY or provide publishableKey in provider.");
|
|
135
|
-
}
|
|
116
|
+
if (!publishableKey) throw new Error("Missing publishable key (client side).");
|
|
136
117
|
const res = await fetch(startUrl, {
|
|
137
118
|
method: "GET",
|
|
138
119
|
headers: { "x-publishable-key": publishableKey },
|
|
@@ -143,165 +124,208 @@ function SignIn({ onSuccess } = {}) {
|
|
|
143
124
|
if (!data?.oauthUrl) throw new Error("SDK start did not return oauthUrl");
|
|
144
125
|
if (typeof window !== "undefined") window.location.href = data.oauthUrl;
|
|
145
126
|
} catch (err) {
|
|
146
|
-
|
|
127
|
+
toast.error(err?.message || "OAuth start failed", { toastStyle: { background: "#000", color: "#fff" } });
|
|
147
128
|
console.error("OAuth start error:", err);
|
|
148
129
|
setLoadingOauth((prev) => ({ ...prev, [provider]: false }));
|
|
149
130
|
}
|
|
150
131
|
}
|
|
151
132
|
const handleGoogle = (e) => {
|
|
152
|
-
if (e
|
|
133
|
+
if (e?.preventDefault) e.preventDefault();
|
|
153
134
|
startOAuthFlow("google");
|
|
154
135
|
};
|
|
155
136
|
const handleGithub = (e) => {
|
|
156
|
-
if (e
|
|
137
|
+
if (e?.preventDefault) e.preventDefault();
|
|
157
138
|
startOAuthFlow("github");
|
|
158
139
|
};
|
|
159
|
-
return /* @__PURE__ */ React.createElement(
|
|
160
|
-
|
|
140
|
+
return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(
|
|
141
|
+
ToastContainer,
|
|
161
142
|
{
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
143
|
+
position: "top-right",
|
|
144
|
+
newestOnTop: true,
|
|
145
|
+
autoClose: 3e3,
|
|
146
|
+
hideProgressBar: false,
|
|
147
|
+
closeOnClick: true,
|
|
148
|
+
pauseOnHover: true,
|
|
149
|
+
draggable: true,
|
|
150
|
+
theme: "dark"
|
|
151
|
+
}
|
|
152
|
+
), /* @__PURE__ */ React.createElement("div", { style: page }, /* @__PURE__ */ React.createElement("div", { style: cardWrap }, /* @__PURE__ */ React.createElement("div", { style: card }, /* @__PURE__ */ React.createElement("div", { style: cardInner }, /* @__PURE__ */ React.createElement("div", { style: brand }, /* @__PURE__ */ React.createElement("div", { style: brandRow }, /* @__PURE__ */ React.createElement("div", { style: logoWrap }, /* @__PURE__ */ React.createElement(Image, { src: agency.logo, width: 30, height: 30, alt: agency.name, style: logoImgStyle })), /* @__PURE__ */ React.createElement("h1", { style: brandTitle }, agency.name)), /* @__PURE__ */ React.createElement("div", { style: brandText }, /* @__PURE__ */ React.createElement("div", { style: brandLead }, "Sign in to ", agency.name), /* @__PURE__ */ React.createElement("div", { style: brandSub }, "Welcome back! Let's get you signed in."))), /* @__PURE__ */ React.createElement("div", { style: oauthRow }, /* @__PURE__ */ React.createElement(
|
|
153
|
+
"button",
|
|
154
|
+
{
|
|
155
|
+
onClick: handleGoogle,
|
|
156
|
+
type: "button",
|
|
157
|
+
style: { ...oauthButton, ...oauthGoogle },
|
|
158
|
+
disabled: loading || loadingOauth.google,
|
|
159
|
+
"aria-disabled": loading || loadingOauth.google
|
|
175
160
|
},
|
|
176
|
-
/* @__PURE__ */ React.createElement("
|
|
177
|
-
/* @__PURE__ */ React.createElement("
|
|
178
|
-
)
|
|
161
|
+
/* @__PURE__ */ React.createElement("svg", { width: 18, style: { marginRight: 10 }, 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" })),
|
|
162
|
+
/* @__PURE__ */ React.createElement("span", null, loadingOauth.google ? "Loading..." : "Google")
|
|
163
|
+
), /* @__PURE__ */ React.createElement(
|
|
164
|
+
"button",
|
|
165
|
+
{
|
|
166
|
+
onClick: handleGithub,
|
|
167
|
+
type: "button",
|
|
168
|
+
style: { ...oauthButton, ...oauthGithub },
|
|
169
|
+
disabled: loading || loadingOauth.github,
|
|
170
|
+
"aria-disabled": loading || loadingOauth.github
|
|
171
|
+
},
|
|
172
|
+
/* @__PURE__ */ React.createElement("svg", { width: 18, style: { marginRight: 10 }, 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" })),
|
|
173
|
+
/* @__PURE__ */ React.createElement("span", null, loadingOauth.github ? "Loading..." : "Github")
|
|
174
|
+
)), /* @__PURE__ */ React.createElement("div", { style: dividerRow }, /* @__PURE__ */ React.createElement("div", { style: line }), /* @__PURE__ */ React.createElement("div", { style: orText }, "or"), /* @__PURE__ */ React.createElement("div", { style: line })), /* @__PURE__ */ React.createElement("form", { onSubmit: submit, style: form2 }, /* @__PURE__ */ React.createElement("label", { style: label }, /* @__PURE__ */ React.createElement("span", { style: labelSmall }, "Email address"), /* @__PURE__ */ React.createElement(
|
|
179
175
|
"input",
|
|
180
176
|
{
|
|
181
|
-
|
|
182
|
-
value: email,
|
|
183
|
-
onChange: (e) => setEmail(e.target.value),
|
|
177
|
+
id: "email",
|
|
184
178
|
type: "email",
|
|
185
|
-
|
|
179
|
+
onChange: handleChange,
|
|
180
|
+
value: form2.email,
|
|
181
|
+
required: true,
|
|
182
|
+
placeholder: "you@example.com",
|
|
183
|
+
style: input
|
|
186
184
|
}
|
|
187
|
-
), /* @__PURE__ */ React.createElement("label", { style: label }, "Password"), /* @__PURE__ */ React.createElement(
|
|
185
|
+
)), /* @__PURE__ */ React.createElement("label", { style: label }, /* @__PURE__ */ React.createElement("span", { style: labelSmall }, "Password"), /* @__PURE__ */ React.createElement(
|
|
188
186
|
"input",
|
|
189
187
|
{
|
|
190
|
-
|
|
188
|
+
id: "password",
|
|
191
189
|
type: "password",
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
required: true
|
|
190
|
+
onChange: handleChange,
|
|
191
|
+
value: form2.password,
|
|
192
|
+
required: true,
|
|
193
|
+
placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022",
|
|
194
|
+
style: input
|
|
195
195
|
}
|
|
196
|
-
)
|
|
196
|
+
)), /* @__PURE__ */ React.createElement(
|
|
197
|
+
"button",
|
|
198
|
+
{
|
|
199
|
+
type: "submit",
|
|
200
|
+
style: submitBtn,
|
|
201
|
+
"aria-disabled": loading,
|
|
202
|
+
disabled: loading
|
|
203
|
+
},
|
|
204
|
+
loading ? "Signing in..." : "Sign In"
|
|
205
|
+
))), /* @__PURE__ */ React.createElement("div", { style: cardFooter }, /* @__PURE__ */ React.createElement("div", { style: smallText }, /* @__PURE__ */ React.createElement("span", { style: muted }, "Don't have an account? "), /* @__PURE__ */ React.createElement(Link, { href: "/signup", style: link }, "Create one")), /* @__PURE__ */ React.createElement("div", { style: thinDivider }), /* @__PURE__ */ React.createElement("div", { style: { textAlign: "center", marginTop: 8 } }, /* @__PURE__ */ React.createElement("div", { style: securedText }, "Secured by auth")))))));
|
|
197
206
|
}
|
|
198
|
-
const
|
|
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",
|
|
207
|
+
const page = {
|
|
208
208
|
minHeight: "100vh",
|
|
209
|
-
|
|
209
|
+
display: "flex",
|
|
210
|
+
alignItems: "center",
|
|
211
|
+
justifyContent: "center",
|
|
212
|
+
padding: "24px",
|
|
213
|
+
background: "linear-gradient(to bottom, #0f1724, #0b1220 40%, #02040a 100%)",
|
|
214
|
+
color: "#fef3c7"
|
|
215
|
+
};
|
|
216
|
+
const cardWrap = {
|
|
217
|
+
width: "100%",
|
|
218
|
+
maxWidth: 420
|
|
210
219
|
};
|
|
211
|
-
const
|
|
220
|
+
const card = {
|
|
212
221
|
width: "100%",
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
222
|
+
borderRadius: 20,
|
|
223
|
+
background: "linear-gradient(180deg, rgba(15,17,20,0.88), rgba(6,8,12,0.92))",
|
|
224
|
+
border: "1px solid rgba(148,163,184,0.06)",
|
|
225
|
+
backdropFilter: "blur(8px)",
|
|
226
|
+
boxShadow: "0 18px 50px rgba(2,6,23,0.6)",
|
|
227
|
+
overflow: "hidden",
|
|
228
|
+
display: "flex",
|
|
229
|
+
flexDirection: "column"
|
|
230
|
+
};
|
|
231
|
+
const cardInner = {
|
|
232
|
+
padding: "28px",
|
|
233
|
+
display: "flex",
|
|
234
|
+
flexDirection: "column",
|
|
235
|
+
gap: 14,
|
|
236
|
+
background: "linear-gradient(180deg, rgba(12,14,18,0.02), rgba(0,0,0,0.02))",
|
|
237
|
+
borderRadius: "20px"
|
|
238
|
+
};
|
|
239
|
+
const brand = {
|
|
240
|
+
display: "flex",
|
|
241
|
+
flexDirection: "column",
|
|
242
|
+
gap: 8,
|
|
243
|
+
alignItems: "center",
|
|
244
|
+
textAlign: "center"
|
|
245
|
+
};
|
|
246
|
+
const brandRow = {
|
|
247
|
+
display: "flex",
|
|
248
|
+
alignItems: "center",
|
|
249
|
+
gap: 10,
|
|
250
|
+
justifyContent: "center"
|
|
251
|
+
};
|
|
252
|
+
const logoWrap = {
|
|
253
|
+
width: 30,
|
|
254
|
+
height: 30,
|
|
255
|
+
borderRadius: 999,
|
|
256
|
+
overflow: "hidden",
|
|
257
|
+
display: "flex",
|
|
258
|
+
alignItems: "center",
|
|
259
|
+
justifyContent: "center",
|
|
260
|
+
background: "linear-gradient(135deg,#2b313a,#0f1724)"
|
|
261
|
+
};
|
|
262
|
+
const logoImgStyle = { borderRadius: 999, objectFit: "cover" };
|
|
263
|
+
const brandTitle = { margin: 0, fontSize: 20, fontWeight: 500, color: "#fff" };
|
|
264
|
+
const brandLead = { fontSize: 15, color: "#f8fafc" };
|
|
265
|
+
const brandSub = { fontSize: 13, color: "rgba(255,255,255,0.7)", marginTop: 2 };
|
|
266
|
+
const brandText = { display: "flex", flexDirection: "column", gap: 2, alignItems: "center" };
|
|
267
|
+
const oauthRow = { display: "flex", gap: 10, marginTop: 10 };
|
|
268
|
+
const oauthButton = {
|
|
269
|
+
flex: 1,
|
|
270
|
+
display: "inline-flex",
|
|
271
|
+
alignItems: "center",
|
|
272
|
+
justifyContent: "center",
|
|
273
|
+
padding: "10px 12px",
|
|
274
|
+
borderRadius: 12,
|
|
275
|
+
border: "1px solid rgba(148,163,184,0.06)",
|
|
276
|
+
fontSize: 14,
|
|
277
|
+
cursor: "pointer",
|
|
278
|
+
userSelect: "none",
|
|
279
|
+
gap: 8,
|
|
280
|
+
minHeight: 40,
|
|
281
|
+
background: "transparent",
|
|
220
282
|
color: "#fff"
|
|
221
283
|
};
|
|
222
|
-
const
|
|
223
|
-
const
|
|
224
|
-
const
|
|
284
|
+
const oauthGoogle = { background: "rgba(255,255,255,0.02)" };
|
|
285
|
+
const oauthGithub = { background: "rgba(255,255,255,0.01)" };
|
|
286
|
+
const dividerRow = { display: "flex", alignItems: "center", gap: 12, marginTop: 14 };
|
|
287
|
+
const line = { flex: 1, height: 1, background: "rgba(148,163,184,0.04)" };
|
|
288
|
+
const orText = { fontSize: 13, color: "rgba(255,255,255,0.65)", padding: "0 8px" };
|
|
289
|
+
const form = { display: "flex", flexDirection: "column", gap: 12, width: "100%", marginTop: 4 };
|
|
290
|
+
const label = { display: "flex", flexDirection: "column", gap: 6 };
|
|
291
|
+
const labelSmall = { fontSize: 13, color: "rgba(255,255,255,0.75)" };
|
|
225
292
|
const input = {
|
|
226
293
|
width: "100%",
|
|
227
294
|
padding: "10px 12px",
|
|
228
|
-
|
|
229
|
-
borderRadius: 10,
|
|
230
|
-
border: "1px solid rgba(148,163,184,0.10)",
|
|
295
|
+
borderRadius: 12,
|
|
231
296
|
background: "rgba(255,255,255,0.02)",
|
|
232
|
-
color: "#
|
|
297
|
+
color: "#fef3c7",
|
|
298
|
+
border: "1px solid rgba(148,163,184,0.06)",
|
|
299
|
+
fontSize: 14,
|
|
300
|
+
outline: "none",
|
|
233
301
|
boxSizing: "border-box"
|
|
234
302
|
};
|
|
235
|
-
const
|
|
303
|
+
const submitBtn = {
|
|
304
|
+
marginTop: 6,
|
|
236
305
|
width: "100%",
|
|
237
306
|
padding: "10px 12px",
|
|
238
|
-
borderRadius:
|
|
239
|
-
background: "linear-gradient(
|
|
240
|
-
color: "#0b1220",
|
|
307
|
+
borderRadius: 12,
|
|
308
|
+
background: "linear-gradient(180deg,#2563eb,#60a5fa)",
|
|
241
309
|
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
310
|
color: "#fff",
|
|
251
|
-
|
|
311
|
+
fontWeight: 600,
|
|
252
312
|
cursor: "pointer",
|
|
313
|
+
fontSize: 15,
|
|
253
314
|
display: "inline-flex",
|
|
254
315
|
alignItems: "center",
|
|
255
316
|
justifyContent: "center",
|
|
256
|
-
|
|
317
|
+
minHeight: 44
|
|
257
318
|
};
|
|
258
|
-
const
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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
|
|
319
|
+
const cardFooter = {
|
|
320
|
+
padding: "18px",
|
|
321
|
+
borderTop: "1px solid rgba(148,163,184,0.03)",
|
|
322
|
+
background: "transparent"
|
|
304
323
|
};
|
|
324
|
+
const smallText = { textAlign: "center", fontSize: 13 };
|
|
325
|
+
const muted = { color: "rgba(255,255,255,0.75)" };
|
|
326
|
+
const link = { color: "#60a5fa", textDecoration: "none", fontWeight: 600 };
|
|
327
|
+
const thinDivider = { height: 1, background: "rgba(148,163,184,0.04)", margin: "12px 0" };
|
|
328
|
+
const securedText = { color: "rgba(255,255,255,0.9)", fontWeight: 600 };
|
|
305
329
|
export {
|
|
306
330
|
SignIn as default
|
|
307
331
|
};
|
package/package.json
CHANGED
package/src/SignIn.jsx
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
|
+
// src/signin.jsx
|
|
1
2
|
'use client'
|
|
2
3
|
import React, { useEffect, useRef, useState } from 'react'
|
|
4
|
+
import Image from 'next/image'
|
|
3
5
|
import Link from 'next/link'
|
|
6
|
+
import { ToastContainer, toast } from 'react-toastify'
|
|
7
|
+
import 'react-toastify/dist/ReactToastify.css'
|
|
4
8
|
import { useAuth } from './provider.js'
|
|
5
9
|
|
|
6
|
-
export default function SignIn({ onSuccess } = {}) {
|
|
10
|
+
export default function SignIn({ agency = { name: 'chest', logo: '/logo.png' }, onSuccess } = {}) {
|
|
7
11
|
const {
|
|
8
12
|
publishableKey,
|
|
9
13
|
baseUrl,
|
|
@@ -16,50 +20,24 @@ export default function SignIn({ onSuccess } = {}) {
|
|
|
16
20
|
setUser
|
|
17
21
|
} = (typeof useAuth === 'function' ? useAuth() : {}) || {}
|
|
18
22
|
|
|
19
|
-
const [
|
|
20
|
-
const [password, setPassword] = useState('')
|
|
23
|
+
const [form, setForm] = useState({ email: '', password: '' })
|
|
21
24
|
const [loading, setLoading] = useState(false)
|
|
22
25
|
const [loadingOauth, setLoadingOauth] = useState({ google: false, github: false })
|
|
23
|
-
|
|
24
26
|
const redirectTimer = useRef(null)
|
|
25
|
-
const toastId = useRef(0)
|
|
26
|
-
const [toasts, setToasts] = useState([])
|
|
27
27
|
|
|
28
|
+
// soft-disable pinch-zoom on mobile while mounted (SDK-safe)
|
|
28
29
|
useEffect(() => {
|
|
29
|
-
// soft-disable pinch-zoom on mobile while mounted
|
|
30
30
|
const meta = document.createElement('meta')
|
|
31
31
|
meta.name = 'viewport'
|
|
32
32
|
meta.content = 'width=device-width, initial-scale=1, maximum-scale=1'
|
|
33
33
|
document.head.appendChild(meta)
|
|
34
|
-
|
|
35
34
|
return () => {
|
|
36
35
|
if (redirectTimer.current) clearTimeout(redirectTimer.current)
|
|
37
36
|
const existing = document.querySelector('meta[name="viewport"]')
|
|
38
37
|
if (existing && existing.content === meta.content) document.head.removeChild(existing)
|
|
39
|
-
// clear timers for toasts
|
|
40
|
-
toasts.forEach(t => { if (t._timer) clearTimeout(t._timer) })
|
|
41
38
|
}
|
|
42
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
43
39
|
}, [])
|
|
44
40
|
|
|
45
|
-
// Toast helpers (black background)
|
|
46
|
-
function showToast(type, message, ms = 5000) {
|
|
47
|
-
const id = ++toastId.current
|
|
48
|
-
const t = { id, type, message, _timer: null }
|
|
49
|
-
setToasts(prev => [t, ...prev].slice(0, 6))
|
|
50
|
-
const timer = setTimeout(() => {
|
|
51
|
-
setToasts(prev => prev.filter(x => x.id !== id))
|
|
52
|
-
}, ms)
|
|
53
|
-
t._timer = timer
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function removeToast(id) {
|
|
57
|
-
setToasts(prev => {
|
|
58
|
-
prev.forEach(t => { if (t.id === id && t._timer) clearTimeout(t._timer) })
|
|
59
|
-
return prev.filter(x => x.id !== id)
|
|
60
|
-
})
|
|
61
|
-
}
|
|
62
|
-
|
|
63
41
|
if (loadingUser) return null
|
|
64
42
|
|
|
65
43
|
if (user && redirect) {
|
|
@@ -68,13 +46,17 @@ export default function SignIn({ onSuccess } = {}) {
|
|
|
68
46
|
return null
|
|
69
47
|
}
|
|
70
48
|
|
|
49
|
+
function handleChange(e) {
|
|
50
|
+
setForm(prev => ({ ...prev, [e.target.id]: e.target.value }))
|
|
51
|
+
}
|
|
52
|
+
|
|
71
53
|
async function submit(e) {
|
|
72
54
|
e.preventDefault()
|
|
73
55
|
if (loading) return
|
|
74
56
|
setLoading(true)
|
|
75
57
|
|
|
76
|
-
if (!email || !password) {
|
|
77
|
-
|
|
58
|
+
if (!form.email || !form.password) {
|
|
59
|
+
toast.error('Email and password are required', { toastId: 'missing-creds', toastStyle: { background: '#000', color: '#fff' } })
|
|
78
60
|
setLoading(false)
|
|
79
61
|
return
|
|
80
62
|
}
|
|
@@ -89,7 +71,7 @@ export default function SignIn({ onSuccess } = {}) {
|
|
|
89
71
|
'Content-Type': 'application/json',
|
|
90
72
|
'x-publishable-key': publishableKey || ''
|
|
91
73
|
},
|
|
92
|
-
body: JSON.stringify({ email, password })
|
|
74
|
+
body: JSON.stringify({ email: form.email, password: form.password })
|
|
93
75
|
})
|
|
94
76
|
|
|
95
77
|
const ct = res.headers.get('content-type') || ''
|
|
@@ -121,7 +103,8 @@ export default function SignIn({ onSuccess } = {}) {
|
|
|
121
103
|
try { onSuccess(data) } catch (_) {}
|
|
122
104
|
}
|
|
123
105
|
|
|
124
|
-
|
|
106
|
+
toast.success('Signed in. Redirecting...', { toastStyle: { background: '#000', color: '#fff' } })
|
|
107
|
+
|
|
125
108
|
if (redirect) {
|
|
126
109
|
redirectTimer.current = setTimeout(() => {
|
|
127
110
|
if (typeof redirectTo === 'function') redirectTo(redirect)
|
|
@@ -129,7 +112,7 @@ export default function SignIn({ onSuccess } = {}) {
|
|
|
129
112
|
}, 250)
|
|
130
113
|
}
|
|
131
114
|
} catch (err) {
|
|
132
|
-
|
|
115
|
+
toast.error(err?.message || 'Network error', { toastStyle: { background: '#000', color: '#fff' } })
|
|
133
116
|
console.error('Signin error:', err)
|
|
134
117
|
} finally {
|
|
135
118
|
setLoading(false)
|
|
@@ -137,9 +120,9 @@ export default function SignIn({ onSuccess } = {}) {
|
|
|
137
120
|
}
|
|
138
121
|
|
|
139
122
|
async function startOAuthFlow(provider) {
|
|
140
|
-
// prevent double start
|
|
141
123
|
if (loading || loadingOauth[provider]) return
|
|
142
124
|
setLoadingOauth(prev => ({ ...prev, [provider]: true }))
|
|
125
|
+
|
|
143
126
|
try {
|
|
144
127
|
const rid =
|
|
145
128
|
(typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function')
|
|
@@ -150,9 +133,7 @@ export default function SignIn({ onSuccess } = {}) {
|
|
|
150
133
|
const sdkBase = baseUrl || (typeof window !== 'undefined' ? window.location.origin.replace(/\/+$/, '') : '')
|
|
151
134
|
const startUrl = `${sdkBase.replace(/\/+$/, '')}/sdk/auth/start?rid=${rid}&source=${encodeURIComponent(provider)}&callbackUrl=${callbackUrl}`
|
|
152
135
|
|
|
153
|
-
if (!publishableKey)
|
|
154
|
-
throw new Error('Missing publishable key (client side). Set NEXT_PUBLIC_FLOWLINK_PUBLISHABLE_KEY or provide publishableKey in provider.')
|
|
155
|
-
}
|
|
136
|
+
if (!publishableKey) throw new Error('Missing publishable key (client side).')
|
|
156
137
|
|
|
157
138
|
const res = await fetch(startUrl, {
|
|
158
139
|
method: 'GET',
|
|
@@ -164,212 +145,280 @@ export default function SignIn({ onSuccess } = {}) {
|
|
|
164
145
|
if (!res.ok) throw new Error(data?.error || `OAuth start failed (${res.status})`)
|
|
165
146
|
if (!data?.oauthUrl) throw new Error('SDK start did not return oauthUrl')
|
|
166
147
|
|
|
148
|
+
// navigate to provider (unloads page, so no need to unset loading)
|
|
167
149
|
if (typeof window !== 'undefined') window.location.href = data.oauthUrl
|
|
168
150
|
} catch (err) {
|
|
169
|
-
|
|
151
|
+
toast.error(err?.message || 'OAuth start failed', { toastStyle: { background: '#000', color: '#fff' } })
|
|
170
152
|
console.error('OAuth start error:', err)
|
|
171
153
|
setLoadingOauth(prev => ({ ...prev, [provider]: false }))
|
|
172
154
|
}
|
|
173
155
|
}
|
|
174
156
|
|
|
175
|
-
const handleGoogle = (e) => {
|
|
176
|
-
|
|
177
|
-
startOAuthFlow('google')
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
const handleGithub = (e) => {
|
|
181
|
-
if (e && typeof e.preventDefault === 'function') e.preventDefault()
|
|
182
|
-
startOAuthFlow('github')
|
|
183
|
-
}
|
|
157
|
+
const handleGoogle = (e) => { if (e?.preventDefault) e.preventDefault(); startOAuthFlow('google') }
|
|
158
|
+
const handleGithub = (e) => { if (e?.preventDefault) e.preventDefault(); startOAuthFlow('github') }
|
|
184
159
|
|
|
185
160
|
return (
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
<
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
161
|
+
<>
|
|
162
|
+
<ToastContainer
|
|
163
|
+
position="top-right"
|
|
164
|
+
newestOnTop
|
|
165
|
+
autoClose={3000}
|
|
166
|
+
hideProgressBar={false}
|
|
167
|
+
closeOnClick
|
|
168
|
+
pauseOnHover
|
|
169
|
+
draggable
|
|
170
|
+
theme="dark"
|
|
171
|
+
/>
|
|
172
|
+
|
|
173
|
+
<div style={page}>
|
|
174
|
+
<div style={cardWrap}>
|
|
175
|
+
<div style={card}>
|
|
176
|
+
<div style={cardInner}>
|
|
177
|
+
<div style={brand}>
|
|
178
|
+
<div style={brandRow}>
|
|
179
|
+
<div style={logoWrap}>
|
|
180
|
+
<Image src={agency.logo} width={30} height={30} alt={agency.name} style={logoImgStyle} />
|
|
181
|
+
</div>
|
|
182
|
+
<h1 style={brandTitle}>{agency.name}</h1>
|
|
183
|
+
</div>
|
|
184
|
+
<div style={brandText}>
|
|
185
|
+
<div style={brandLead}>Sign in to {agency.name}</div>
|
|
186
|
+
<div style={brandSub}>Welcome back! Let's get you signed in.</div>
|
|
187
|
+
</div>
|
|
188
|
+
</div>
|
|
189
|
+
|
|
190
|
+
<div style={oauthRow}>
|
|
191
|
+
<button
|
|
192
|
+
onClick={handleGoogle}
|
|
193
|
+
type="button"
|
|
194
|
+
style={{ ...oauthButton, ...oauthGoogle }}
|
|
195
|
+
disabled={loading || loadingOauth.google}
|
|
196
|
+
aria-disabled={loading || loadingOauth.google}
|
|
197
|
+
>
|
|
198
|
+
<svg width={18} style={{ marginRight: 10 }} viewBox="-3 0 262 262" xmlns="http://www.w3.org/2000/svg" fill="#000000" aria-hidden>
|
|
199
|
+
<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"></path>
|
|
200
|
+
<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"></path>
|
|
201
|
+
<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"></path>
|
|
202
|
+
<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"></path>
|
|
203
|
+
</svg>
|
|
204
|
+
<span>{loadingOauth.google ? 'Loading...' : 'Google'}</span>
|
|
205
|
+
</button>
|
|
206
|
+
|
|
207
|
+
<button
|
|
208
|
+
onClick={handleGithub}
|
|
209
|
+
type="button"
|
|
210
|
+
style={{ ...oauthButton, ...oauthGithub }}
|
|
211
|
+
disabled={loading || loadingOauth.github}
|
|
212
|
+
aria-disabled={loading || loadingOauth.github}
|
|
213
|
+
>
|
|
214
|
+
<svg width={18} style={{ marginRight: 10 }} xmlns="http://www.w3.org/2000/svg" fill="white" viewBox="0 0 20 20" aria-hidden>
|
|
215
|
+
<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" />
|
|
216
|
+
</svg>
|
|
217
|
+
<span>{loadingOauth.github ? 'Loading...' : 'Github'}</span>
|
|
218
|
+
</button>
|
|
219
|
+
</div>
|
|
220
|
+
|
|
221
|
+
<div style={dividerRow}>
|
|
222
|
+
<div style={line} />
|
|
223
|
+
<div style={orText}>or</div>
|
|
224
|
+
<div style={line} />
|
|
225
|
+
</div>
|
|
226
|
+
|
|
227
|
+
<form onSubmit={submit} style={form}>
|
|
228
|
+
<label style={label}>
|
|
229
|
+
<span style={labelSmall}>Email address</span>
|
|
230
|
+
<input
|
|
231
|
+
id="email"
|
|
232
|
+
type="email"
|
|
233
|
+
onChange={handleChange}
|
|
234
|
+
value={form.email}
|
|
235
|
+
required
|
|
236
|
+
placeholder="you@example.com"
|
|
237
|
+
style={input}
|
|
238
|
+
/>
|
|
239
|
+
</label>
|
|
240
|
+
|
|
241
|
+
<label style={label}>
|
|
242
|
+
<span style={labelSmall}>Password</span>
|
|
243
|
+
<input
|
|
244
|
+
id="password"
|
|
245
|
+
type="password"
|
|
246
|
+
onChange={handleChange}
|
|
247
|
+
value={form.password}
|
|
248
|
+
required
|
|
249
|
+
placeholder="••••••••"
|
|
250
|
+
style={input}
|
|
251
|
+
/>
|
|
252
|
+
</label>
|
|
253
|
+
|
|
254
|
+
<button
|
|
255
|
+
type="submit"
|
|
256
|
+
style={submitBtn}
|
|
257
|
+
aria-disabled={loading}
|
|
258
|
+
disabled={loading}
|
|
259
|
+
>
|
|
260
|
+
{loading ? 'Signing in...' : 'Sign In'}
|
|
261
|
+
</button>
|
|
262
|
+
</form>
|
|
263
|
+
</div>
|
|
264
|
+
|
|
265
|
+
<div style={cardFooter}>
|
|
266
|
+
<div style={smallText}>
|
|
267
|
+
<span style={muted}>Don't have an account? </span>
|
|
268
|
+
<Link href="/signup" style={link}>Create one</Link>
|
|
269
|
+
</div>
|
|
270
|
+
|
|
271
|
+
<div style={thinDivider} />
|
|
272
|
+
|
|
273
|
+
<div style={{ textAlign: 'center', marginTop: 8 }}>
|
|
274
|
+
<div style={securedText}>Secured by auth</div>
|
|
275
|
+
</div>
|
|
276
|
+
</div>
|
|
255
277
|
</div>
|
|
256
|
-
</
|
|
278
|
+
</div>
|
|
257
279
|
</div>
|
|
258
|
-
|
|
280
|
+
</>
|
|
259
281
|
)
|
|
260
282
|
}
|
|
261
283
|
|
|
262
|
-
/* styles */
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
inset: 0,
|
|
266
|
-
display: 'block', // allow page scroll
|
|
267
|
-
padding: 20,
|
|
268
|
-
background: 'linear-gradient(180deg, rgba(2,6,23,0.22), rgba(2,6,23,0.32))',
|
|
269
|
-
backdropFilter: 'blur(6px)',
|
|
270
|
-
overflowY: 'auto',
|
|
271
|
-
WebkitOverflowScrolling: 'touch',
|
|
284
|
+
/* ---------- styles (JS objects) to match the provided UI exactly ---------- */
|
|
285
|
+
|
|
286
|
+
const page = {
|
|
272
287
|
minHeight: '100vh',
|
|
273
|
-
|
|
288
|
+
display: 'flex',
|
|
289
|
+
alignItems: 'center',
|
|
290
|
+
justifyContent: 'center',
|
|
291
|
+
padding: '24px',
|
|
292
|
+
background: 'linear-gradient(to bottom, #0f1724, #0b1220 40%, #02040a 100%)',
|
|
293
|
+
color: '#fef3c7'
|
|
274
294
|
}
|
|
275
295
|
|
|
276
|
-
const
|
|
296
|
+
const cardWrap = {
|
|
277
297
|
width: '100%',
|
|
278
|
-
maxWidth:
|
|
279
|
-
margin: '40px auto',
|
|
280
|
-
borderRadius: 14,
|
|
281
|
-
background: 'linear-gradient(180deg, rgba(15,19,24,0.85), rgba(10,12,16,0.85))',
|
|
282
|
-
border: '1px solid rgba(99,102,106,0.12)',
|
|
283
|
-
boxShadow: '0 18px 48px rgba(2,6,23,0.55), inset 0 1px 0 rgba(255,255,255,0.02)',
|
|
284
|
-
padding: 22,
|
|
285
|
-
color: '#fff'
|
|
298
|
+
maxWidth: 420
|
|
286
299
|
}
|
|
287
300
|
|
|
288
|
-
const
|
|
289
|
-
const subtitle = { marginTop: 6, marginBottom: 14, color: '#cbd5e1', fontSize: 13 }
|
|
290
|
-
const label = { display: 'block', color: '#cbd5e1', fontSize: 13, marginTop: 8 }
|
|
291
|
-
const input = {
|
|
292
|
-
width: '100%',
|
|
293
|
-
padding: '10px 12px',
|
|
294
|
-
marginTop: 6,
|
|
295
|
-
borderRadius: 10,
|
|
296
|
-
border: '1px solid rgba(148,163,184,0.10)',
|
|
297
|
-
background: 'rgba(255,255,255,0.02)',
|
|
298
|
-
color: '#e6e6e6',
|
|
299
|
-
boxSizing: 'border-box'
|
|
300
|
-
}
|
|
301
|
-
const button = {
|
|
301
|
+
const card = {
|
|
302
302
|
width: '100%',
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
303
|
+
borderRadius: 20,
|
|
304
|
+
background: 'linear-gradient(180deg, rgba(15,17,20,0.88), rgba(6,8,12,0.92))',
|
|
305
|
+
border: '1px solid rgba(148,163,184,0.06)',
|
|
306
|
+
backdropFilter: 'blur(8px)',
|
|
307
|
+
boxShadow: '0 18px 50px rgba(2,6,23,0.6)',
|
|
308
|
+
overflow: 'hidden',
|
|
309
|
+
display: 'flex',
|
|
310
|
+
flexDirection: 'column'
|
|
310
311
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
padding: '
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
display: 'inline-flex',
|
|
320
|
-
alignItems: 'center',
|
|
321
|
-
justifyContent: 'center',
|
|
322
|
-
gap: 8
|
|
312
|
+
|
|
313
|
+
const cardInner = {
|
|
314
|
+
padding: '28px',
|
|
315
|
+
display: 'flex',
|
|
316
|
+
flexDirection: 'column',
|
|
317
|
+
gap: 14,
|
|
318
|
+
background: 'linear-gradient(180deg, rgba(12,14,18,0.02), rgba(0,0,0,0.02))',
|
|
319
|
+
borderRadius: '20px'
|
|
323
320
|
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
color: '#fff',
|
|
330
|
-
border: '1px solid rgba(148,163,184,0.08)',
|
|
331
|
-
cursor: 'pointer',
|
|
332
|
-
display: 'inline-flex',
|
|
321
|
+
|
|
322
|
+
const brand = {
|
|
323
|
+
display: 'flex',
|
|
324
|
+
flexDirection: 'column',
|
|
325
|
+
gap: 8,
|
|
333
326
|
alignItems: 'center',
|
|
334
|
-
|
|
335
|
-
gap: 8
|
|
327
|
+
textAlign: 'center'
|
|
336
328
|
}
|
|
337
329
|
|
|
338
|
-
|
|
339
|
-
const toastContainer = {
|
|
340
|
-
position: 'fixed',
|
|
341
|
-
top: 18,
|
|
342
|
-
right: 18,
|
|
343
|
-
width: 360,
|
|
344
|
-
maxWidth: 'calc(100% - 36px)',
|
|
330
|
+
const brandRow = {
|
|
345
331
|
display: 'flex',
|
|
346
|
-
|
|
332
|
+
alignItems: 'center',
|
|
347
333
|
gap: 10,
|
|
348
|
-
|
|
334
|
+
justifyContent: 'center'
|
|
349
335
|
}
|
|
350
336
|
|
|
351
|
-
const
|
|
337
|
+
const logoWrap = {
|
|
338
|
+
width: 30,
|
|
339
|
+
height: 30,
|
|
340
|
+
borderRadius: 999,
|
|
341
|
+
overflow: 'hidden',
|
|
352
342
|
display: 'flex',
|
|
353
|
-
gap: 10,
|
|
354
343
|
alignItems: 'center',
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
boxShadow: '0 8px 20px rgba(2,6,23,0.6)',
|
|
358
|
-
color: '#fff',
|
|
359
|
-
fontSize: 13,
|
|
360
|
-
minWidth: 120
|
|
344
|
+
justifyContent: 'center',
|
|
345
|
+
background: 'linear-gradient(135deg,#2b313a,#0f1724)'
|
|
361
346
|
}
|
|
362
347
|
|
|
363
|
-
const
|
|
364
|
-
const toastSuccess = { background: '#000', border: '1px solid rgba(255,255,255,0.06)' }
|
|
365
|
-
const toastInfo = { background: '#000', border: '1px solid rgba(255,255,255,0.06)' }
|
|
348
|
+
const logoImgStyle = { borderRadius: 999, objectFit: 'cover' }
|
|
366
349
|
|
|
367
|
-
const
|
|
368
|
-
|
|
350
|
+
const brandTitle = { margin: 0, fontSize: 20, fontWeight: 500, color: '#fff' }
|
|
351
|
+
const brandLead = { fontSize: 15, color: '#f8fafc' }
|
|
352
|
+
const brandSub = { fontSize: 13, color: 'rgba(255,255,255,0.7)', marginTop: 2 }
|
|
353
|
+
const brandText = { display: 'flex', flexDirection: 'column', gap: 2, alignItems: 'center' }
|
|
354
|
+
|
|
355
|
+
const oauthRow = { display: 'flex', gap: 10, marginTop: 10 }
|
|
356
|
+
|
|
357
|
+
const oauthButton = {
|
|
358
|
+
flex: 1,
|
|
359
|
+
display: 'inline-flex',
|
|
360
|
+
alignItems: 'center',
|
|
361
|
+
justifyContent: 'center',
|
|
362
|
+
padding: '10px 12px',
|
|
363
|
+
borderRadius: 12,
|
|
364
|
+
border: '1px solid rgba(148,163,184,0.06)',
|
|
365
|
+
fontSize: 14,
|
|
366
|
+
cursor: 'pointer',
|
|
367
|
+
userSelect: 'none',
|
|
368
|
+
gap: 8,
|
|
369
|
+
minHeight: 40,
|
|
369
370
|
background: 'transparent',
|
|
371
|
+
color: '#fff'
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
const oauthGoogle = { background: 'rgba(255,255,255,0.02)' }
|
|
375
|
+
const oauthGithub = { background: 'rgba(255,255,255,0.01)' }
|
|
376
|
+
|
|
377
|
+
const dividerRow = { display: 'flex', alignItems: 'center', gap: 12, marginTop: 14 }
|
|
378
|
+
const line = { flex: 1, height: 1, background: 'rgba(148,163,184,0.04)' }
|
|
379
|
+
const orText = { fontSize: 13, color: 'rgba(255,255,255,0.65)', padding: '0 8px' }
|
|
380
|
+
|
|
381
|
+
const form = { display: 'flex', flexDirection: 'column', gap: 12, width: '100%', marginTop: 4 }
|
|
382
|
+
const label = { display: 'flex', flexDirection: 'column', gap: 6 }
|
|
383
|
+
const labelSmall = { fontSize: 13, color: 'rgba(255,255,255,0.75)' }
|
|
384
|
+
|
|
385
|
+
const input = {
|
|
386
|
+
width: '100%',
|
|
387
|
+
padding: '10px 12px',
|
|
388
|
+
borderRadius: 12,
|
|
389
|
+
background: 'rgba(255,255,255,0.02)',
|
|
390
|
+
color: '#fef3c7',
|
|
391
|
+
border: '1px solid rgba(148,163,184,0.06)',
|
|
392
|
+
fontSize: 14,
|
|
393
|
+
outline: 'none',
|
|
394
|
+
boxSizing: 'border-box'
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const submitBtn = {
|
|
398
|
+
marginTop: 6,
|
|
399
|
+
width: '100%',
|
|
400
|
+
padding: '10px 12px',
|
|
401
|
+
borderRadius: 12,
|
|
402
|
+
background: 'linear-gradient(180deg,#2563eb,#60a5fa)',
|
|
370
403
|
border: 'none',
|
|
371
|
-
color: '
|
|
404
|
+
color: '#fff',
|
|
405
|
+
fontWeight: 600,
|
|
372
406
|
cursor: 'pointer',
|
|
373
|
-
fontSize:
|
|
374
|
-
|
|
407
|
+
fontSize: 15,
|
|
408
|
+
display: 'inline-flex',
|
|
409
|
+
alignItems: 'center',
|
|
410
|
+
justifyContent: 'center',
|
|
411
|
+
minHeight: 44
|
|
375
412
|
}
|
|
413
|
+
|
|
414
|
+
const cardFooter = {
|
|
415
|
+
padding: '18px',
|
|
416
|
+
borderTop: '1px solid rgba(148,163,184,0.03)',
|
|
417
|
+
background: 'transparent'
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
const smallText = { textAlign: 'center', fontSize: 13 }
|
|
421
|
+
const muted = { color: 'rgba(255,255,255,0.75)' }
|
|
422
|
+
const link = { color: '#60a5fa', textDecoration: 'none', fontWeight: 600 }
|
|
423
|
+
const thinDivider = { height: 1, background: 'rgba(148,163,184,0.04)', margin: '12px 0' }
|
|
424
|
+
const securedText = { color: 'rgba(255,255,255,0.9)', fontWeight: 600 }
|