flowlink-auth 2.8.5 → 2.8.7
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 +189 -96
- package/package.json +1 -1
- package/src/SignIn.jsx +290 -150
package/dist/SignIn.js
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
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";
|
|
4
5
|
import { useAuth } from "./provider.js";
|
|
5
|
-
function SignIn({
|
|
6
|
+
function SignIn({
|
|
7
|
+
agency = { name: "chest", logo: "/logo.png" },
|
|
8
|
+
onSuccess,
|
|
9
|
+
onError
|
|
10
|
+
} = {}) {
|
|
6
11
|
const {
|
|
7
12
|
publishableKey,
|
|
8
13
|
baseUrl,
|
|
@@ -14,8 +19,7 @@ function SignIn({ onSuccess } = {}) {
|
|
|
14
19
|
fetchMe,
|
|
15
20
|
setUser
|
|
16
21
|
} = (typeof useAuth === "function" ? useAuth() : {}) || {};
|
|
17
|
-
const [
|
|
18
|
-
const [password, setPassword] = useState("");
|
|
22
|
+
const [form2, setForm] = useState({ email: "", password: "" });
|
|
19
23
|
const [loading, setLoading] = useState(false);
|
|
20
24
|
const [loadingOauth, setLoadingOauth] = useState({ google: false, github: false });
|
|
21
25
|
const redirectTimer = useRef(null);
|
|
@@ -35,7 +39,20 @@ function SignIn({ onSuccess } = {}) {
|
|
|
35
39
|
});
|
|
36
40
|
};
|
|
37
41
|
}, []);
|
|
38
|
-
function
|
|
42
|
+
function showLocalToast(type, message, ms = 3500) {
|
|
43
|
+
if (type === "error" && typeof onError === "function") {
|
|
44
|
+
try {
|
|
45
|
+
onError(message);
|
|
46
|
+
} catch (_) {
|
|
47
|
+
}
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (type === "success" && typeof onSuccess === "function") {
|
|
51
|
+
try {
|
|
52
|
+
onSuccess(message);
|
|
53
|
+
} catch (_) {
|
|
54
|
+
}
|
|
55
|
+
}
|
|
39
56
|
const id = ++toastId.current;
|
|
40
57
|
const t = { id, type, message, _timer: null };
|
|
41
58
|
setToasts((prev) => [t, ...prev].slice(0, 6));
|
|
@@ -44,7 +61,7 @@ function SignIn({ onSuccess } = {}) {
|
|
|
44
61
|
}, ms);
|
|
45
62
|
t._timer = timer;
|
|
46
63
|
}
|
|
47
|
-
function
|
|
64
|
+
function removeLocalToast(id) {
|
|
48
65
|
setToasts((prev) => {
|
|
49
66
|
prev.forEach((t) => {
|
|
50
67
|
if (t.id === id && t._timer) clearTimeout(t._timer);
|
|
@@ -58,12 +75,15 @@ function SignIn({ onSuccess } = {}) {
|
|
|
58
75
|
else if (typeof window !== "undefined") window.location.assign(redirect);
|
|
59
76
|
return null;
|
|
60
77
|
}
|
|
78
|
+
function handleChange(e) {
|
|
79
|
+
setForm((prev) => ({ ...prev, [e.target.id]: e.target.value }));
|
|
80
|
+
}
|
|
61
81
|
async function submit(e) {
|
|
62
82
|
e.preventDefault();
|
|
63
83
|
if (loading) return;
|
|
64
84
|
setLoading(true);
|
|
65
|
-
if (!email || !password) {
|
|
66
|
-
|
|
85
|
+
if (!form2.email || !form2.password) {
|
|
86
|
+
showLocalToast("error", "Email and password are required");
|
|
67
87
|
setLoading(false);
|
|
68
88
|
return;
|
|
69
89
|
}
|
|
@@ -76,7 +96,7 @@ function SignIn({ onSuccess } = {}) {
|
|
|
76
96
|
"Content-Type": "application/json",
|
|
77
97
|
"x-publishable-key": publishableKey || ""
|
|
78
98
|
},
|
|
79
|
-
body: JSON.stringify({ email, password })
|
|
99
|
+
body: JSON.stringify({ email: form2.email, password: form2.password })
|
|
80
100
|
});
|
|
81
101
|
const ct = res.headers.get("content-type") || "";
|
|
82
102
|
let data = {};
|
|
@@ -102,13 +122,13 @@ function SignIn({ onSuccess } = {}) {
|
|
|
102
122
|
} else if (typeof fetchMe === "function") {
|
|
103
123
|
await fetchMe();
|
|
104
124
|
}
|
|
105
|
-
|
|
125
|
+
showLocalToast("success", "Signed in. Redirecting...");
|
|
126
|
+
if (typeof onSuccess === "function") {
|
|
106
127
|
try {
|
|
107
128
|
onSuccess(data);
|
|
108
129
|
} catch (_) {
|
|
109
130
|
}
|
|
110
131
|
}
|
|
111
|
-
showToast("success", "Signed in. Redirecting...");
|
|
112
132
|
if (redirect) {
|
|
113
133
|
redirectTimer.current = setTimeout(() => {
|
|
114
134
|
if (typeof redirectTo === "function") redirectTo(redirect);
|
|
@@ -116,7 +136,8 @@ function SignIn({ onSuccess } = {}) {
|
|
|
116
136
|
}, 250);
|
|
117
137
|
}
|
|
118
138
|
} catch (err) {
|
|
119
|
-
|
|
139
|
+
const message = err?.message || "Network error";
|
|
140
|
+
showLocalToast("error", message);
|
|
120
141
|
console.error("Signin error:", err);
|
|
121
142
|
} finally {
|
|
122
143
|
setLoading(false);
|
|
@@ -130,9 +151,7 @@ function SignIn({ onSuccess } = {}) {
|
|
|
130
151
|
const callbackUrl = encodeURIComponent(`${typeof window !== "undefined" ? window.location.origin : ""}/signin`);
|
|
131
152
|
const sdkBase = baseUrl || (typeof window !== "undefined" ? window.location.origin.replace(/\/+$/, "") : "");
|
|
132
153
|
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
|
-
}
|
|
154
|
+
if (!publishableKey) throw new Error("Missing publishable key (client side).");
|
|
136
155
|
const res = await fetch(startUrl, {
|
|
137
156
|
method: "GET",
|
|
138
157
|
headers: { "x-publishable-key": publishableKey },
|
|
@@ -143,131 +162,215 @@ function SignIn({ onSuccess } = {}) {
|
|
|
143
162
|
if (!data?.oauthUrl) throw new Error("SDK start did not return oauthUrl");
|
|
144
163
|
if (typeof window !== "undefined") window.location.href = data.oauthUrl;
|
|
145
164
|
} catch (err) {
|
|
146
|
-
|
|
165
|
+
showLocalToast("error", err?.message || "OAuth start failed");
|
|
147
166
|
console.error("OAuth start error:", err);
|
|
148
167
|
setLoadingOauth((prev) => ({ ...prev, [provider]: false }));
|
|
149
168
|
}
|
|
150
169
|
}
|
|
151
170
|
const handleGoogle = (e) => {
|
|
152
|
-
if (e
|
|
171
|
+
if (e?.preventDefault) e.preventDefault();
|
|
153
172
|
startOAuthFlow("google");
|
|
154
173
|
};
|
|
155
174
|
const handleGithub = (e) => {
|
|
156
|
-
if (e
|
|
175
|
+
if (e?.preventDefault) e.preventDefault();
|
|
157
176
|
startOAuthFlow("github");
|
|
158
177
|
};
|
|
159
|
-
return /* @__PURE__ */ React.createElement(
|
|
178
|
+
return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", { style: toastContainer, "aria-live": "polite", "aria-atomic": "true" }, toasts.map((t) => /* @__PURE__ */ React.createElement(
|
|
160
179
|
"div",
|
|
161
180
|
{
|
|
162
181
|
key: t.id,
|
|
163
182
|
role: "status",
|
|
164
183
|
style: {
|
|
165
184
|
...toastBase,
|
|
166
|
-
...t.type === "error" ? toastError :
|
|
185
|
+
...t.type === "error" ? toastError : toastSuccess
|
|
167
186
|
},
|
|
168
187
|
onMouseEnter: () => {
|
|
169
188
|
if (t._timer) clearTimeout(t._timer);
|
|
170
189
|
},
|
|
171
190
|
onMouseLeave: () => {
|
|
172
|
-
const timer = setTimeout(() =>
|
|
191
|
+
const timer = setTimeout(() => removeLocalToast(t.id), 2500);
|
|
173
192
|
setToasts((prev) => prev.map((x) => x.id === t.id ? { ...x, _timer: timer } : x));
|
|
174
193
|
}
|
|
175
194
|
},
|
|
176
195
|
/* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }, t.message),
|
|
177
|
-
/* @__PURE__ */ React.createElement("button", { "aria-label": "Dismiss", onClick: () =>
|
|
178
|
-
))), /* @__PURE__ */ React.createElement("div", { style:
|
|
196
|
+
/* @__PURE__ */ React.createElement("button", { "aria-label": "Dismiss", onClick: () => removeLocalToast(t.id), style: toastCloseBtn }, "\u2715")
|
|
197
|
+
))), /* @__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(
|
|
198
|
+
"button",
|
|
199
|
+
{
|
|
200
|
+
onClick: handleGoogle,
|
|
201
|
+
type: "button",
|
|
202
|
+
style: { ...oauthButton, ...oauthGoogle },
|
|
203
|
+
disabled: loading || loadingOauth.google,
|
|
204
|
+
"aria-disabled": loading || loadingOauth.google
|
|
205
|
+
},
|
|
206
|
+
/* @__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" })),
|
|
207
|
+
/* @__PURE__ */ React.createElement("span", null, loadingOauth.google ? "Loading..." : "Google")
|
|
208
|
+
), /* @__PURE__ */ React.createElement(
|
|
209
|
+
"button",
|
|
210
|
+
{
|
|
211
|
+
onClick: handleGithub,
|
|
212
|
+
type: "button",
|
|
213
|
+
style: { ...oauthButton, ...oauthGithub },
|
|
214
|
+
disabled: loading || loadingOauth.github,
|
|
215
|
+
"aria-disabled": loading || loadingOauth.github
|
|
216
|
+
},
|
|
217
|
+
/* @__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" })),
|
|
218
|
+
/* @__PURE__ */ React.createElement("span", null, loadingOauth.github ? "Loading..." : "Github")
|
|
219
|
+
)), /* @__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
220
|
"input",
|
|
180
221
|
{
|
|
181
|
-
|
|
182
|
-
value: email,
|
|
183
|
-
onChange: (e) => setEmail(e.target.value),
|
|
222
|
+
id: "email",
|
|
184
223
|
type: "email",
|
|
185
|
-
|
|
224
|
+
onChange: handleChange,
|
|
225
|
+
value: form2.email,
|
|
226
|
+
required: true,
|
|
227
|
+
placeholder: "you@example.com",
|
|
228
|
+
style: input
|
|
186
229
|
}
|
|
187
|
-
), /* @__PURE__ */ React.createElement("label", { style: label }, "Password"), /* @__PURE__ */ React.createElement(
|
|
230
|
+
)), /* @__PURE__ */ React.createElement("label", { style: label }, /* @__PURE__ */ React.createElement("span", { style: labelSmall }, "Password"), /* @__PURE__ */ React.createElement(
|
|
188
231
|
"input",
|
|
189
232
|
{
|
|
190
|
-
|
|
233
|
+
id: "password",
|
|
191
234
|
type: "password",
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
required: true
|
|
235
|
+
onChange: handleChange,
|
|
236
|
+
value: form2.password,
|
|
237
|
+
required: true,
|
|
238
|
+
placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022",
|
|
239
|
+
style: input
|
|
195
240
|
}
|
|
196
|
-
)
|
|
241
|
+
)), /* @__PURE__ */ React.createElement(
|
|
242
|
+
"button",
|
|
243
|
+
{
|
|
244
|
+
type: "submit",
|
|
245
|
+
style: submitBtn,
|
|
246
|
+
"aria-disabled": loading,
|
|
247
|
+
disabled: loading
|
|
248
|
+
},
|
|
249
|
+
loading ? "Signing in..." : "Sign In"
|
|
250
|
+
))), /* @__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
251
|
}
|
|
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",
|
|
252
|
+
const page = {
|
|
208
253
|
minHeight: "100vh",
|
|
209
|
-
|
|
254
|
+
display: "flex",
|
|
255
|
+
alignItems: "center",
|
|
256
|
+
justifyContent: "center",
|
|
257
|
+
padding: "24px",
|
|
258
|
+
background: "linear-gradient(to bottom, #0f1724, #0b1220 40%, #02040a 100%)",
|
|
259
|
+
color: "#fef3c7"
|
|
210
260
|
};
|
|
211
|
-
const
|
|
261
|
+
const cardWrap = {
|
|
212
262
|
width: "100%",
|
|
213
|
-
maxWidth:
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
263
|
+
maxWidth: 420
|
|
264
|
+
};
|
|
265
|
+
const card = {
|
|
266
|
+
width: "100%",
|
|
267
|
+
borderRadius: 20,
|
|
268
|
+
background: "linear-gradient(180deg, rgba(15,17,20,0.88), rgba(6,8,12,0.92))",
|
|
269
|
+
border: "1px solid rgba(148,163,184,0.06)",
|
|
270
|
+
backdropFilter: "blur(8px)",
|
|
271
|
+
boxShadow: "0 18px 50px rgba(2,6,23,0.6)",
|
|
272
|
+
overflow: "hidden",
|
|
273
|
+
display: "flex",
|
|
274
|
+
flexDirection: "column"
|
|
275
|
+
};
|
|
276
|
+
const cardInner = {
|
|
277
|
+
padding: "28px",
|
|
278
|
+
display: "flex",
|
|
279
|
+
flexDirection: "column",
|
|
280
|
+
gap: 14,
|
|
281
|
+
background: "linear-gradient(180deg, rgba(12,14,18,0.02), rgba(0,0,0,0.02))",
|
|
282
|
+
borderRadius: "20px"
|
|
283
|
+
};
|
|
284
|
+
const brand = {
|
|
285
|
+
display: "flex",
|
|
286
|
+
flexDirection: "column",
|
|
287
|
+
gap: 8,
|
|
288
|
+
alignItems: "center",
|
|
289
|
+
textAlign: "center"
|
|
290
|
+
};
|
|
291
|
+
const brandRow = {
|
|
292
|
+
display: "flex",
|
|
293
|
+
alignItems: "center",
|
|
294
|
+
gap: 10,
|
|
295
|
+
justifyContent: "center"
|
|
296
|
+
};
|
|
297
|
+
const logoWrap = {
|
|
298
|
+
width: 30,
|
|
299
|
+
height: 30,
|
|
300
|
+
borderRadius: 999,
|
|
301
|
+
overflow: "hidden",
|
|
302
|
+
display: "flex",
|
|
303
|
+
alignItems: "center",
|
|
304
|
+
justifyContent: "center",
|
|
305
|
+
background: "linear-gradient(135deg,#2b313a,#0f1724)"
|
|
306
|
+
};
|
|
307
|
+
const logoImgStyle = { borderRadius: 999, objectFit: "cover" };
|
|
308
|
+
const brandTitle = { margin: 0, fontSize: 20, fontWeight: 500, color: "#fff" };
|
|
309
|
+
const brandLead = { fontSize: 15, color: "#f8fafc" };
|
|
310
|
+
const brandSub = { fontSize: 13, color: "rgba(255,255,255,0.7)", marginTop: 2 };
|
|
311
|
+
const brandText = { display: "flex", flexDirection: "column", gap: 2, alignItems: "center" };
|
|
312
|
+
const oauthRow = { display: "flex", gap: 10, marginTop: 10 };
|
|
313
|
+
const oauthButton = {
|
|
314
|
+
flex: 1,
|
|
315
|
+
display: "inline-flex",
|
|
316
|
+
alignItems: "center",
|
|
317
|
+
justifyContent: "center",
|
|
318
|
+
padding: "10px 12px",
|
|
319
|
+
borderRadius: 12,
|
|
320
|
+
border: "1px solid rgba(148,163,184,0.06)",
|
|
321
|
+
fontSize: 14,
|
|
322
|
+
cursor: "pointer",
|
|
323
|
+
userSelect: "none",
|
|
324
|
+
gap: 8,
|
|
325
|
+
minHeight: 40,
|
|
326
|
+
background: "transparent",
|
|
220
327
|
color: "#fff"
|
|
221
328
|
};
|
|
222
|
-
const
|
|
223
|
-
const
|
|
224
|
-
const
|
|
329
|
+
const oauthGoogle = { background: "rgba(255,255,255,0.02)" };
|
|
330
|
+
const oauthGithub = { background: "rgba(255,255,255,0.01)" };
|
|
331
|
+
const dividerRow = { display: "flex", alignItems: "center", gap: 12, marginTop: 14 };
|
|
332
|
+
const line = { flex: 1, height: 1, background: "rgba(148,163,184,0.04)" };
|
|
333
|
+
const orText = { fontSize: 13, color: "rgba(255,255,255,0.65)", padding: "0 8px" };
|
|
334
|
+
const form = { display: "flex", flexDirection: "column", gap: 12, width: "100%", marginTop: 4 };
|
|
335
|
+
const label = { display: "flex", flexDirection: "column", gap: 6 };
|
|
336
|
+
const labelSmall = { fontSize: 13, color: "rgba(255,255,255,0.75)" };
|
|
225
337
|
const input = {
|
|
226
338
|
width: "100%",
|
|
227
339
|
padding: "10px 12px",
|
|
228
|
-
|
|
229
|
-
borderRadius: 10,
|
|
230
|
-
border: "1px solid rgba(148,163,184,0.10)",
|
|
340
|
+
borderRadius: 12,
|
|
231
341
|
background: "rgba(255,255,255,0.02)",
|
|
232
|
-
color: "#
|
|
342
|
+
color: "#fef3c7",
|
|
343
|
+
border: "1px solid rgba(148,163,184,0.06)",
|
|
344
|
+
fontSize: 14,
|
|
345
|
+
outline: "none",
|
|
233
346
|
boxSizing: "border-box"
|
|
234
347
|
};
|
|
235
|
-
const
|
|
348
|
+
const submitBtn = {
|
|
349
|
+
marginTop: 6,
|
|
236
350
|
width: "100%",
|
|
237
351
|
padding: "10px 12px",
|
|
238
|
-
borderRadius:
|
|
239
|
-
background: "linear-gradient(
|
|
240
|
-
color: "#0b1220",
|
|
352
|
+
borderRadius: 12,
|
|
353
|
+
background: "linear-gradient(180deg,#2563eb,#60a5fa)",
|
|
241
354
|
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
355
|
color: "#fff",
|
|
251
|
-
|
|
356
|
+
fontWeight: 600,
|
|
252
357
|
cursor: "pointer",
|
|
358
|
+
fontSize: 15,
|
|
253
359
|
display: "inline-flex",
|
|
254
360
|
alignItems: "center",
|
|
255
361
|
justifyContent: "center",
|
|
256
|
-
|
|
362
|
+
minHeight: 44
|
|
257
363
|
};
|
|
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
|
|
364
|
+
const cardFooter = {
|
|
365
|
+
padding: "18px",
|
|
366
|
+
borderTop: "1px solid rgba(148,163,184,0.03)",
|
|
367
|
+
background: "transparent"
|
|
270
368
|
};
|
|
369
|
+
const smallText = { textAlign: "center", fontSize: 13 };
|
|
370
|
+
const muted = { color: "rgba(255,255,255,0.75)" };
|
|
371
|
+
const link = { color: "#60a5fa", textDecoration: "none", fontWeight: 600 };
|
|
372
|
+
const thinDivider = { height: 1, background: "rgba(148,163,184,0.04)", margin: "12px 0" };
|
|
373
|
+
const securedText = { color: "rgba(255,255,255,0.9)", fontWeight: 600 };
|
|
271
374
|
const toastContainer = {
|
|
272
375
|
position: "fixed",
|
|
273
376
|
top: 18,
|
|
@@ -287,21 +390,11 @@ const toastBase = {
|
|
|
287
390
|
borderRadius: 10,
|
|
288
391
|
boxShadow: "0 8px 20px rgba(2,6,23,0.6)",
|
|
289
392
|
color: "#fff",
|
|
290
|
-
fontSize: 13
|
|
291
|
-
minWidth: 120
|
|
393
|
+
fontSize: 13
|
|
292
394
|
};
|
|
293
395
|
const toastError = { background: "#000", border: "1px solid rgba(255,255,255,0.06)" };
|
|
294
396
|
const toastSuccess = { background: "#000", border: "1px solid rgba(255,255,255,0.06)" };
|
|
295
|
-
const
|
|
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
|
-
};
|
|
397
|
+
const toastCloseBtn = { marginLeft: 8, background: "transparent", border: "none", color: "rgba(255,255,255,0.7)", cursor: "pointer", fontSize: 14, lineHeight: 1 };
|
|
305
398
|
export {
|
|
306
399
|
SignIn as default
|
|
307
400
|
};
|
package/package.json
CHANGED
package/src/SignIn.jsx
CHANGED
|
@@ -1,9 +1,26 @@
|
|
|
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'
|
|
4
6
|
import { useAuth } from './provider.js'
|
|
5
7
|
|
|
6
|
-
|
|
8
|
+
/**
|
|
9
|
+
* SignIn component (SDK-friendly)
|
|
10
|
+
* - Does NOT depend on react-toastify or any external UI lib
|
|
11
|
+
* - Uses a small local black toast system (same pattern as signup)
|
|
12
|
+
* - Exposes optional onSuccess/onError callbacks for host apps
|
|
13
|
+
*
|
|
14
|
+
* Props:
|
|
15
|
+
* - agency: { name, logo } (optional)
|
|
16
|
+
* - onSuccess: function(data) optional callback
|
|
17
|
+
* - onError: function(message) optional callback
|
|
18
|
+
*/
|
|
19
|
+
export default function SignIn({
|
|
20
|
+
agency = { name: 'chest', logo: '/logo.png' },
|
|
21
|
+
onSuccess,
|
|
22
|
+
onError
|
|
23
|
+
} = {}) {
|
|
7
24
|
const {
|
|
8
25
|
publishableKey,
|
|
9
26
|
baseUrl,
|
|
@@ -16,17 +33,17 @@ export default function SignIn({ onSuccess } = {}) {
|
|
|
16
33
|
setUser
|
|
17
34
|
} = (typeof useAuth === 'function' ? useAuth() : {}) || {}
|
|
18
35
|
|
|
19
|
-
const [
|
|
20
|
-
const [password, setPassword] = useState('')
|
|
36
|
+
const [form, setForm] = useState({ email: '', password: '' })
|
|
21
37
|
const [loading, setLoading] = useState(false)
|
|
22
38
|
const [loadingOauth, setLoadingOauth] = useState({ google: false, github: false })
|
|
23
|
-
|
|
24
39
|
const redirectTimer = useRef(null)
|
|
40
|
+
|
|
41
|
+
// local (SDK) toasts — black background — used only if host doesn't provide onError/onSuccess
|
|
25
42
|
const toastId = useRef(0)
|
|
26
43
|
const [toasts, setToasts] = useState([])
|
|
27
44
|
|
|
28
45
|
useEffect(() => {
|
|
29
|
-
//
|
|
46
|
+
// Soft-disable pinch-zoom on mobile while mounted (SDK-safe)
|
|
30
47
|
const meta = document.createElement('meta')
|
|
31
48
|
meta.name = 'viewport'
|
|
32
49
|
meta.content = 'width=device-width, initial-scale=1, maximum-scale=1'
|
|
@@ -36,14 +53,24 @@ export default function SignIn({ onSuccess } = {}) {
|
|
|
36
53
|
if (redirectTimer.current) clearTimeout(redirectTimer.current)
|
|
37
54
|
const existing = document.querySelector('meta[name="viewport"]')
|
|
38
55
|
if (existing && existing.content === meta.content) document.head.removeChild(existing)
|
|
39
|
-
// clear timers
|
|
56
|
+
// clear toast timers
|
|
40
57
|
toasts.forEach(t => { if (t._timer) clearTimeout(t._timer) })
|
|
41
58
|
}
|
|
42
59
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
43
60
|
}, [])
|
|
44
61
|
|
|
45
|
-
//
|
|
46
|
-
function
|
|
62
|
+
// ---------- toast helpers ----------
|
|
63
|
+
function showLocalToast(type, message, ms = 3500) {
|
|
64
|
+
// if host provided onError/onSuccess, prefer that
|
|
65
|
+
if (type === 'error' && typeof onError === 'function') {
|
|
66
|
+
try { onError(message) } catch (_) {}
|
|
67
|
+
return
|
|
68
|
+
}
|
|
69
|
+
if (type === 'success' && typeof onSuccess === 'function') {
|
|
70
|
+
try { onSuccess(message) } catch (_) {}
|
|
71
|
+
// still show local toast for feedback (optional)
|
|
72
|
+
}
|
|
73
|
+
|
|
47
74
|
const id = ++toastId.current
|
|
48
75
|
const t = { id, type, message, _timer: null }
|
|
49
76
|
setToasts(prev => [t, ...prev].slice(0, 6))
|
|
@@ -53,7 +80,7 @@ export default function SignIn({ onSuccess } = {}) {
|
|
|
53
80
|
t._timer = timer
|
|
54
81
|
}
|
|
55
82
|
|
|
56
|
-
function
|
|
83
|
+
function removeLocalToast(id) {
|
|
57
84
|
setToasts(prev => {
|
|
58
85
|
prev.forEach(t => { if (t.id === id && t._timer) clearTimeout(t._timer) })
|
|
59
86
|
return prev.filter(x => x.id !== id)
|
|
@@ -68,13 +95,18 @@ export default function SignIn({ onSuccess } = {}) {
|
|
|
68
95
|
return null
|
|
69
96
|
}
|
|
70
97
|
|
|
98
|
+
function handleChange(e) {
|
|
99
|
+
setForm(prev => ({ ...prev, [e.target.id]: e.target.value }))
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ---------- submit login ----------
|
|
71
103
|
async function submit(e) {
|
|
72
104
|
e.preventDefault()
|
|
73
105
|
if (loading) return
|
|
74
106
|
setLoading(true)
|
|
75
107
|
|
|
76
|
-
if (!email || !password) {
|
|
77
|
-
|
|
108
|
+
if (!form.email || !form.password) {
|
|
109
|
+
showLocalToast('error', 'Email and password are required')
|
|
78
110
|
setLoading(false)
|
|
79
111
|
return
|
|
80
112
|
}
|
|
@@ -89,7 +121,7 @@ export default function SignIn({ onSuccess } = {}) {
|
|
|
89
121
|
'Content-Type': 'application/json',
|
|
90
122
|
'x-publishable-key': publishableKey || ''
|
|
91
123
|
},
|
|
92
|
-
body: JSON.stringify({ email, password })
|
|
124
|
+
body: JSON.stringify({ email: form.email, password: form.password })
|
|
93
125
|
})
|
|
94
126
|
|
|
95
127
|
const ct = res.headers.get('content-type') || ''
|
|
@@ -117,11 +149,12 @@ export default function SignIn({ onSuccess } = {}) {
|
|
|
117
149
|
await fetchMe()
|
|
118
150
|
}
|
|
119
151
|
|
|
120
|
-
|
|
152
|
+
// callback & local success toast (host will receive onSuccess if provided)
|
|
153
|
+
showLocalToast('success', 'Signed in. Redirecting...')
|
|
154
|
+
if (typeof onSuccess === 'function') {
|
|
121
155
|
try { onSuccess(data) } catch (_) {}
|
|
122
156
|
}
|
|
123
157
|
|
|
124
|
-
showToast('success', 'Signed in. Redirecting...')
|
|
125
158
|
if (redirect) {
|
|
126
159
|
redirectTimer.current = setTimeout(() => {
|
|
127
160
|
if (typeof redirectTo === 'function') redirectTo(redirect)
|
|
@@ -129,17 +162,19 @@ export default function SignIn({ onSuccess } = {}) {
|
|
|
129
162
|
}, 250)
|
|
130
163
|
}
|
|
131
164
|
} catch (err) {
|
|
132
|
-
|
|
165
|
+
const message = err?.message || 'Network error'
|
|
166
|
+
showLocalToast('error', message)
|
|
133
167
|
console.error('Signin error:', err)
|
|
134
168
|
} finally {
|
|
135
169
|
setLoading(false)
|
|
136
170
|
}
|
|
137
171
|
}
|
|
138
172
|
|
|
173
|
+
// ---------- OAuth start ----------
|
|
139
174
|
async function startOAuthFlow(provider) {
|
|
140
|
-
// prevent double start
|
|
141
175
|
if (loading || loadingOauth[provider]) return
|
|
142
176
|
setLoadingOauth(prev => ({ ...prev, [provider]: true }))
|
|
177
|
+
|
|
143
178
|
try {
|
|
144
179
|
const rid =
|
|
145
180
|
(typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function')
|
|
@@ -150,9 +185,7 @@ export default function SignIn({ onSuccess } = {}) {
|
|
|
150
185
|
const sdkBase = baseUrl || (typeof window !== 'undefined' ? window.location.origin.replace(/\/+$/, '') : '')
|
|
151
186
|
const startUrl = `${sdkBase.replace(/\/+$/, '')}/sdk/auth/start?rid=${rid}&source=${encodeURIComponent(provider)}&callbackUrl=${callbackUrl}`
|
|
152
187
|
|
|
153
|
-
if (!publishableKey)
|
|
154
|
-
throw new Error('Missing publishable key (client side). Set NEXT_PUBLIC_FLOWLINK_PUBLISHABLE_KEY or provide publishableKey in provider.')
|
|
155
|
-
}
|
|
188
|
+
if (!publishableKey) throw new Error('Missing publishable key (client side).')
|
|
156
189
|
|
|
157
190
|
const res = await fetch(startUrl, {
|
|
158
191
|
method: 'GET',
|
|
@@ -164,27 +197,22 @@ export default function SignIn({ onSuccess } = {}) {
|
|
|
164
197
|
if (!res.ok) throw new Error(data?.error || `OAuth start failed (${res.status})`)
|
|
165
198
|
if (!data?.oauthUrl) throw new Error('SDK start did not return oauthUrl')
|
|
166
199
|
|
|
200
|
+
// navigate to provider (page unloads)
|
|
167
201
|
if (typeof window !== 'undefined') window.location.href = data.oauthUrl
|
|
168
202
|
} catch (err) {
|
|
169
|
-
|
|
203
|
+
showLocalToast('error', err?.message || 'OAuth start failed')
|
|
170
204
|
console.error('OAuth start error:', err)
|
|
171
205
|
setLoadingOauth(prev => ({ ...prev, [provider]: false }))
|
|
172
206
|
}
|
|
173
207
|
}
|
|
174
208
|
|
|
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
|
-
}
|
|
209
|
+
const handleGoogle = (e) => { if (e?.preventDefault) e.preventDefault(); startOAuthFlow('google') }
|
|
210
|
+
const handleGithub = (e) => { if (e?.preventDefault) e.preventDefault(); startOAuthFlow('github') }
|
|
184
211
|
|
|
212
|
+
// ---------- render ----------
|
|
185
213
|
return (
|
|
186
|
-
|
|
187
|
-
{/*
|
|
214
|
+
<>
|
|
215
|
+
{/* Local toast container (black style) */}
|
|
188
216
|
<div style={toastContainer} aria-live="polite" aria-atomic="true">
|
|
189
217
|
{toasts.map(t => (
|
|
190
218
|
<div
|
|
@@ -192,150 +220,275 @@ export default function SignIn({ onSuccess } = {}) {
|
|
|
192
220
|
role="status"
|
|
193
221
|
style={{
|
|
194
222
|
...toastBase,
|
|
195
|
-
...(t.type === 'error' ? toastError :
|
|
223
|
+
...(t.type === 'error' ? toastError : toastSuccess)
|
|
196
224
|
}}
|
|
197
225
|
onMouseEnter={() => { if (t._timer) clearTimeout(t._timer) }}
|
|
198
226
|
onMouseLeave={() => {
|
|
199
|
-
const timer = setTimeout(() =>
|
|
227
|
+
const timer = setTimeout(() => removeLocalToast(t.id), 2500)
|
|
200
228
|
setToasts(prev => prev.map(x => x.id === t.id ? { ...x, _timer: timer } : x))
|
|
201
229
|
}}
|
|
202
230
|
>
|
|
203
231
|
<div style={{ flex: 1 }}>{t.message}</div>
|
|
204
|
-
<button aria-label="Dismiss" onClick={() =>
|
|
232
|
+
<button aria-label="Dismiss" onClick={() => removeLocalToast(t.id)} style={toastCloseBtn}>✕</button>
|
|
205
233
|
</div>
|
|
206
234
|
))}
|
|
207
235
|
</div>
|
|
208
236
|
|
|
209
|
-
<div style={
|
|
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
|
-
|
|
237
|
+
<div style={page}>
|
|
238
|
+
<div style={cardWrap}>
|
|
239
|
+
<div style={card}>
|
|
240
|
+
<div style={cardInner}>
|
|
241
|
+
<div style={brand}>
|
|
242
|
+
<div style={brandRow}>
|
|
243
|
+
<div style={logoWrap}>
|
|
244
|
+
<Image src={agency.logo} width={30} height={30} alt={agency.name} style={logoImgStyle} />
|
|
245
|
+
</div>
|
|
246
|
+
<h1 style={brandTitle}>{agency.name}</h1>
|
|
247
|
+
</div>
|
|
248
|
+
<div style={brandText}>
|
|
249
|
+
<div style={brandLead}>Sign in to {agency.name}</div>
|
|
250
|
+
<div style={brandSub}>Welcome back! Let's get you signed in.</div>
|
|
251
|
+
</div>
|
|
252
|
+
</div>
|
|
253
|
+
|
|
254
|
+
<div style={oauthRow}>
|
|
255
|
+
<button
|
|
256
|
+
onClick={handleGoogle}
|
|
257
|
+
type="button"
|
|
258
|
+
style={{ ...oauthButton, ...oauthGoogle }}
|
|
259
|
+
disabled={loading || loadingOauth.google}
|
|
260
|
+
aria-disabled={loading || loadingOauth.google}
|
|
261
|
+
>
|
|
262
|
+
<svg width={18} style={{ marginRight: 10 }} viewBox="-3 0 262 262" xmlns="http://www.w3.org/2000/svg" fill="#000000" aria-hidden>
|
|
263
|
+
<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>
|
|
264
|
+
<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>
|
|
265
|
+
<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>
|
|
266
|
+
<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>
|
|
267
|
+
</svg>
|
|
268
|
+
<span>{loadingOauth.google ? 'Loading...' : 'Google'}</span>
|
|
269
|
+
</button>
|
|
270
|
+
|
|
271
|
+
<button
|
|
272
|
+
onClick={handleGithub}
|
|
273
|
+
type="button"
|
|
274
|
+
style={{ ...oauthButton, ...oauthGithub }}
|
|
275
|
+
disabled={loading || loadingOauth.github}
|
|
276
|
+
aria-disabled={loading || loadingOauth.github}
|
|
277
|
+
>
|
|
278
|
+
<svg width={18} style={{ marginRight: 10 }} xmlns="http://www.w3.org/2000/svg" fill="white" viewBox="0 0 20 20" aria-hidden>
|
|
279
|
+
<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" />
|
|
280
|
+
</svg>
|
|
281
|
+
<span>{loadingOauth.github ? 'Loading...' : 'Github'}</span>
|
|
282
|
+
</button>
|
|
283
|
+
</div>
|
|
284
|
+
|
|
285
|
+
<div style={dividerRow}>
|
|
286
|
+
<div style={line} />
|
|
287
|
+
<div style={orText}>or</div>
|
|
288
|
+
<div style={line} />
|
|
289
|
+
</div>
|
|
290
|
+
|
|
291
|
+
<form onSubmit={submit} style={form}>
|
|
292
|
+
<label style={label}>
|
|
293
|
+
<span style={labelSmall}>Email address</span>
|
|
294
|
+
<input
|
|
295
|
+
id="email"
|
|
296
|
+
type="email"
|
|
297
|
+
onChange={handleChange}
|
|
298
|
+
value={form.email}
|
|
299
|
+
required
|
|
300
|
+
placeholder="you@example.com"
|
|
301
|
+
style={input}
|
|
302
|
+
/>
|
|
303
|
+
</label>
|
|
304
|
+
|
|
305
|
+
<label style={label}>
|
|
306
|
+
<span style={labelSmall}>Password</span>
|
|
307
|
+
<input
|
|
308
|
+
id="password"
|
|
309
|
+
type="password"
|
|
310
|
+
onChange={handleChange}
|
|
311
|
+
value={form.password}
|
|
312
|
+
required
|
|
313
|
+
placeholder="••••••••"
|
|
314
|
+
style={input}
|
|
315
|
+
/>
|
|
316
|
+
</label>
|
|
317
|
+
|
|
318
|
+
<button
|
|
319
|
+
type="submit"
|
|
320
|
+
style={submitBtn}
|
|
321
|
+
aria-disabled={loading}
|
|
322
|
+
disabled={loading}
|
|
323
|
+
>
|
|
324
|
+
{loading ? 'Signing in...' : 'Sign In'}
|
|
325
|
+
</button>
|
|
326
|
+
</form>
|
|
327
|
+
</div>
|
|
328
|
+
|
|
329
|
+
<div style={cardFooter}>
|
|
330
|
+
<div style={smallText}>
|
|
331
|
+
<span style={muted}>Don't have an account? </span>
|
|
332
|
+
<Link href="/signup" style={link}>Create one</Link>
|
|
333
|
+
</div>
|
|
334
|
+
|
|
335
|
+
<div style={thinDivider} />
|
|
336
|
+
|
|
337
|
+
<div style={{ textAlign: 'center', marginTop: 8 }}>
|
|
338
|
+
<div style={securedText}>Secured by auth</div>
|
|
339
|
+
</div>
|
|
340
|
+
</div>
|
|
255
341
|
</div>
|
|
256
|
-
</
|
|
342
|
+
</div>
|
|
257
343
|
</div>
|
|
258
|
-
|
|
344
|
+
</>
|
|
259
345
|
)
|
|
260
346
|
}
|
|
261
347
|
|
|
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',
|
|
348
|
+
/* ---------- styles (JS objects) to match the provided UI exactly ---------- */
|
|
349
|
+
|
|
350
|
+
const page = {
|
|
272
351
|
minHeight: '100vh',
|
|
273
|
-
|
|
352
|
+
display: 'flex',
|
|
353
|
+
alignItems: 'center',
|
|
354
|
+
justifyContent: 'center',
|
|
355
|
+
padding: '24px',
|
|
356
|
+
background: 'linear-gradient(to bottom, #0f1724, #0b1220 40%, #02040a 100%)',
|
|
357
|
+
color: '#fef3c7'
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const cardWrap = {
|
|
361
|
+
width: '100%',
|
|
362
|
+
maxWidth: 420
|
|
274
363
|
}
|
|
275
364
|
|
|
276
|
-
const
|
|
365
|
+
const card = {
|
|
277
366
|
width: '100%',
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
367
|
+
borderRadius: 20,
|
|
368
|
+
background: 'linear-gradient(180deg, rgba(15,17,20,0.88), rgba(6,8,12,0.92))',
|
|
369
|
+
border: '1px solid rgba(148,163,184,0.06)',
|
|
370
|
+
backdropFilter: 'blur(8px)',
|
|
371
|
+
boxShadow: '0 18px 50px rgba(2,6,23,0.6)',
|
|
372
|
+
overflow: 'hidden',
|
|
373
|
+
display: 'flex',
|
|
374
|
+
flexDirection: 'column'
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const cardInner = {
|
|
378
|
+
padding: '28px',
|
|
379
|
+
display: 'flex',
|
|
380
|
+
flexDirection: 'column',
|
|
381
|
+
gap: 14,
|
|
382
|
+
background: 'linear-gradient(180deg, rgba(12,14,18,0.02), rgba(0,0,0,0.02))',
|
|
383
|
+
borderRadius: '20px'
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
const brand = {
|
|
387
|
+
display: 'flex',
|
|
388
|
+
flexDirection: 'column',
|
|
389
|
+
gap: 8,
|
|
390
|
+
alignItems: 'center',
|
|
391
|
+
textAlign: 'center'
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const brandRow = {
|
|
395
|
+
display: 'flex',
|
|
396
|
+
alignItems: 'center',
|
|
397
|
+
gap: 10,
|
|
398
|
+
justifyContent: 'center'
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const logoWrap = {
|
|
402
|
+
width: 30,
|
|
403
|
+
height: 30,
|
|
404
|
+
borderRadius: 999,
|
|
405
|
+
overflow: 'hidden',
|
|
406
|
+
display: 'flex',
|
|
407
|
+
alignItems: 'center',
|
|
408
|
+
justifyContent: 'center',
|
|
409
|
+
background: 'linear-gradient(135deg,#2b313a,#0f1724)'
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
const logoImgStyle = { borderRadius: 999, objectFit: 'cover' }
|
|
413
|
+
|
|
414
|
+
const brandTitle = { margin: 0, fontSize: 20, fontWeight: 500, color: '#fff' }
|
|
415
|
+
const brandLead = { fontSize: 15, color: '#f8fafc' }
|
|
416
|
+
const brandSub = { fontSize: 13, color: 'rgba(255,255,255,0.7)', marginTop: 2 }
|
|
417
|
+
const brandText = { display: 'flex', flexDirection: 'column', gap: 2, alignItems: 'center' }
|
|
418
|
+
|
|
419
|
+
const oauthRow = { display: 'flex', gap: 10, marginTop: 10 }
|
|
420
|
+
|
|
421
|
+
const oauthButton = {
|
|
422
|
+
flex: 1,
|
|
423
|
+
display: 'inline-flex',
|
|
424
|
+
alignItems: 'center',
|
|
425
|
+
justifyContent: 'center',
|
|
426
|
+
padding: '10px 12px',
|
|
427
|
+
borderRadius: 12,
|
|
428
|
+
border: '1px solid rgba(148,163,184,0.06)',
|
|
429
|
+
fontSize: 14,
|
|
430
|
+
cursor: 'pointer',
|
|
431
|
+
userSelect: 'none',
|
|
432
|
+
gap: 8,
|
|
433
|
+
minHeight: 40,
|
|
434
|
+
background: 'transparent',
|
|
285
435
|
color: '#fff'
|
|
286
436
|
}
|
|
287
437
|
|
|
288
|
-
const
|
|
289
|
-
const
|
|
290
|
-
|
|
438
|
+
const oauthGoogle = { background: 'rgba(255,255,255,0.02)' }
|
|
439
|
+
const oauthGithub = { background: 'rgba(255,255,255,0.01)' }
|
|
440
|
+
|
|
441
|
+
const dividerRow = { display: 'flex', alignItems: 'center', gap: 12, marginTop: 14 }
|
|
442
|
+
const line = { flex: 1, height: 1, background: 'rgba(148,163,184,0.04)' }
|
|
443
|
+
const orText = { fontSize: 13, color: 'rgba(255,255,255,0.65)', padding: '0 8px' }
|
|
444
|
+
|
|
445
|
+
const form = { display: 'flex', flexDirection: 'column', gap: 12, width: '100%', marginTop: 4 }
|
|
446
|
+
const label = { display: 'flex', flexDirection: 'column', gap: 6 }
|
|
447
|
+
const labelSmall = { fontSize: 13, color: 'rgba(255,255,255,0.75)' }
|
|
448
|
+
|
|
291
449
|
const input = {
|
|
292
450
|
width: '100%',
|
|
293
451
|
padding: '10px 12px',
|
|
294
|
-
|
|
295
|
-
borderRadius: 10,
|
|
296
|
-
border: '1px solid rgba(148,163,184,0.10)',
|
|
452
|
+
borderRadius: 12,
|
|
297
453
|
background: 'rgba(255,255,255,0.02)',
|
|
298
|
-
color: '#
|
|
454
|
+
color: '#fef3c7',
|
|
455
|
+
border: '1px solid rgba(148,163,184,0.06)',
|
|
456
|
+
fontSize: 14,
|
|
457
|
+
outline: 'none',
|
|
299
458
|
boxSizing: 'border-box'
|
|
300
459
|
}
|
|
301
|
-
|
|
460
|
+
|
|
461
|
+
const submitBtn = {
|
|
462
|
+
marginTop: 6,
|
|
302
463
|
width: '100%',
|
|
303
464
|
padding: '10px 12px',
|
|
304
|
-
borderRadius:
|
|
305
|
-
background: 'linear-gradient(
|
|
306
|
-
color: '#0b1220',
|
|
465
|
+
borderRadius: 12,
|
|
466
|
+
background: 'linear-gradient(180deg,#2563eb,#60a5fa)',
|
|
307
467
|
border: 'none',
|
|
308
|
-
fontWeight: 700,
|
|
309
|
-
cursor: 'pointer'
|
|
310
|
-
}
|
|
311
|
-
const oauthButtonGoogle = {
|
|
312
|
-
flex: 1,
|
|
313
|
-
padding: '10px 12px',
|
|
314
|
-
borderRadius: 10,
|
|
315
|
-
background: 'linear-gradient(180deg, rgba(255,255,255,0.03), rgba(255,255,255,0.01))',
|
|
316
468
|
color: '#fff',
|
|
317
|
-
|
|
469
|
+
fontWeight: 600,
|
|
318
470
|
cursor: 'pointer',
|
|
471
|
+
fontSize: 15,
|
|
319
472
|
display: 'inline-flex',
|
|
320
473
|
alignItems: 'center',
|
|
321
474
|
justifyContent: 'center',
|
|
322
|
-
|
|
475
|
+
minHeight: 44
|
|
323
476
|
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
border: '1px solid rgba(148,163,184,0.08)',
|
|
331
|
-
cursor: 'pointer',
|
|
332
|
-
display: 'inline-flex',
|
|
333
|
-
alignItems: 'center',
|
|
334
|
-
justifyContent: 'center',
|
|
335
|
-
gap: 8
|
|
477
|
+
|
|
478
|
+
/* card footer */
|
|
479
|
+
const cardFooter = {
|
|
480
|
+
padding: '18px',
|
|
481
|
+
borderTop: '1px solid rgba(148,163,184,0.03)',
|
|
482
|
+
background: 'transparent'
|
|
336
483
|
}
|
|
337
484
|
|
|
338
|
-
|
|
485
|
+
const smallText = { textAlign: 'center', fontSize: 13 }
|
|
486
|
+
const muted = { color: 'rgba(255,255,255,0.75)' }
|
|
487
|
+
const link = { color: '#60a5fa', textDecoration: 'none', fontWeight: 600 }
|
|
488
|
+
const thinDivider = { height: 1, background: 'rgba(148,163,184,0.04)', margin: '12px 0' }
|
|
489
|
+
const securedText = { color: 'rgba(255,255,255,0.9)', fontWeight: 600 }
|
|
490
|
+
|
|
491
|
+
/* local toast styles */
|
|
339
492
|
const toastContainer = {
|
|
340
493
|
position: 'fixed',
|
|
341
494
|
top: 18,
|
|
@@ -347,7 +500,6 @@ const toastContainer = {
|
|
|
347
500
|
gap: 10,
|
|
348
501
|
zIndex: 60000
|
|
349
502
|
}
|
|
350
|
-
|
|
351
503
|
const toastBase = {
|
|
352
504
|
display: 'flex',
|
|
353
505
|
gap: 10,
|
|
@@ -356,20 +508,8 @@ const toastBase = {
|
|
|
356
508
|
borderRadius: 10,
|
|
357
509
|
boxShadow: '0 8px 20px rgba(2,6,23,0.6)',
|
|
358
510
|
color: '#fff',
|
|
359
|
-
fontSize: 13
|
|
360
|
-
minWidth: 120
|
|
511
|
+
fontSize: 13
|
|
361
512
|
}
|
|
362
|
-
|
|
363
513
|
const toastError = { background: '#000', border: '1px solid rgba(255,255,255,0.06)' }
|
|
364
514
|
const toastSuccess = { background: '#000', border: '1px solid rgba(255,255,255,0.06)' }
|
|
365
|
-
const
|
|
366
|
-
|
|
367
|
-
const toastCloseBtn = {
|
|
368
|
-
marginLeft: 8,
|
|
369
|
-
background: 'transparent',
|
|
370
|
-
border: 'none',
|
|
371
|
-
color: 'rgba(255,255,255,0.7)',
|
|
372
|
-
cursor: 'pointer',
|
|
373
|
-
fontSize: 14,
|
|
374
|
-
lineHeight: 1
|
|
375
|
-
}
|
|
515
|
+
const toastCloseBtn = { marginLeft: 8, background: 'transparent', border: 'none', color: 'rgba(255,255,255,0.7)', cursor: 'pointer', fontSize: 14, lineHeight: 1 }
|