flowlink-auth 2.8.8 → 2.8.9
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 +195 -119
- package/package.json +1 -1
- package/src/SignIn.jsx +298 -230
package/dist/SignIn.js
CHANGED
|
@@ -3,11 +3,7 @@ import React, { useEffect, useRef, useState } from "react";
|
|
|
3
3
|
import Image from "next/image";
|
|
4
4
|
import Link from "next/link";
|
|
5
5
|
import { useAuth } from "./provider.js";
|
|
6
|
-
function SignIn({
|
|
7
|
-
agency = { name: "chest", logo: "/logo.png" },
|
|
8
|
-
onSuccess,
|
|
9
|
-
onError
|
|
10
|
-
} = {}) {
|
|
6
|
+
function SignIn({ agency = { name: "MyApp", logo: "" }, onSuccess, onError } = {}) {
|
|
11
7
|
const {
|
|
12
8
|
publishableKey,
|
|
13
9
|
baseUrl,
|
|
@@ -19,7 +15,8 @@ function SignIn({
|
|
|
19
15
|
fetchMe,
|
|
20
16
|
setUser
|
|
21
17
|
} = (typeof useAuth === "function" ? useAuth() : {}) || {};
|
|
22
|
-
const [
|
|
18
|
+
const [email, setEmail] = useState("");
|
|
19
|
+
const [password, setPassword] = useState("");
|
|
23
20
|
const [loading, setLoading] = useState(false);
|
|
24
21
|
const [loadingOauth, setLoadingOauth] = useState({ google: false, github: false });
|
|
25
22
|
const redirectTimer = useRef(null);
|
|
@@ -39,7 +36,7 @@ function SignIn({
|
|
|
39
36
|
});
|
|
40
37
|
};
|
|
41
38
|
}, []);
|
|
42
|
-
function
|
|
39
|
+
function showToast(type, message, ms = 4e3) {
|
|
43
40
|
if (type === "error" && typeof onError === "function") {
|
|
44
41
|
try {
|
|
45
42
|
onError(message);
|
|
@@ -61,7 +58,7 @@ function SignIn({
|
|
|
61
58
|
}, ms);
|
|
62
59
|
t._timer = timer;
|
|
63
60
|
}
|
|
64
|
-
function
|
|
61
|
+
function removeToast(id) {
|
|
65
62
|
setToasts((prev) => {
|
|
66
63
|
prev.forEach((t) => {
|
|
67
64
|
if (t.id === id && t._timer) clearTimeout(t._timer);
|
|
@@ -75,15 +72,12 @@ function SignIn({
|
|
|
75
72
|
else if (typeof window !== "undefined") window.location.assign(redirect);
|
|
76
73
|
return null;
|
|
77
74
|
}
|
|
78
|
-
function handleChange(e) {
|
|
79
|
-
setForm((prev) => ({ ...prev, [e.target.id]: e.target.value }));
|
|
80
|
-
}
|
|
81
75
|
async function submit(e) {
|
|
82
76
|
e.preventDefault();
|
|
83
77
|
if (loading) return;
|
|
84
78
|
setLoading(true);
|
|
85
|
-
if (!
|
|
86
|
-
|
|
79
|
+
if (!email.includes("@") || !password) {
|
|
80
|
+
showToast("error", "Email and password are required");
|
|
87
81
|
setLoading(false);
|
|
88
82
|
return;
|
|
89
83
|
}
|
|
@@ -96,7 +90,7 @@ function SignIn({
|
|
|
96
90
|
"Content-Type": "application/json",
|
|
97
91
|
"x-publishable-key": publishableKey || ""
|
|
98
92
|
},
|
|
99
|
-
body: JSON.stringify({ email:
|
|
93
|
+
body: JSON.stringify({ email: email.trim(), password })
|
|
100
94
|
});
|
|
101
95
|
const ct = res.headers.get("content-type") || "";
|
|
102
96
|
let data = {};
|
|
@@ -122,22 +116,16 @@ function SignIn({
|
|
|
122
116
|
} else if (typeof fetchMe === "function") {
|
|
123
117
|
await fetchMe();
|
|
124
118
|
}
|
|
125
|
-
|
|
126
|
-
if (typeof onSuccess === "function") {
|
|
127
|
-
try {
|
|
128
|
-
onSuccess(data);
|
|
129
|
-
} catch (_) {
|
|
130
|
-
}
|
|
131
|
-
}
|
|
119
|
+
showToast("success", "Signed in. Redirecting...");
|
|
132
120
|
if (redirect) {
|
|
133
121
|
redirectTimer.current = setTimeout(() => {
|
|
134
122
|
if (typeof redirectTo === "function") redirectTo(redirect);
|
|
135
123
|
else if (typeof window !== "undefined") window.location.assign(redirect);
|
|
136
|
-
},
|
|
124
|
+
}, 300);
|
|
137
125
|
}
|
|
138
126
|
} catch (err) {
|
|
139
|
-
const
|
|
140
|
-
|
|
127
|
+
const msg = err?.message ?? "Network error";
|
|
128
|
+
showToast("error", msg);
|
|
141
129
|
console.error("Signin error:", err);
|
|
142
130
|
} finally {
|
|
143
131
|
setLoading(false);
|
|
@@ -154,15 +142,14 @@ function SignIn({
|
|
|
154
142
|
if (!publishableKey) throw new Error("Missing publishable key (client side).");
|
|
155
143
|
const res = await fetch(startUrl, {
|
|
156
144
|
method: "GET",
|
|
157
|
-
headers: { "x-publishable-key": publishableKey }
|
|
158
|
-
mode: "cors"
|
|
145
|
+
headers: { "x-publishable-key": publishableKey }
|
|
159
146
|
});
|
|
160
147
|
const data = await res.json().catch(() => null);
|
|
161
148
|
if (!res.ok) throw new Error(data?.error || `OAuth start failed (${res.status})`);
|
|
162
149
|
if (!data?.oauthUrl) throw new Error("SDK start did not return oauthUrl");
|
|
163
150
|
if (typeof window !== "undefined") window.location.href = data.oauthUrl;
|
|
164
151
|
} catch (err) {
|
|
165
|
-
|
|
152
|
+
showToast("error", err?.message || "OAuth start failed");
|
|
166
153
|
console.error("OAuth start error:", err);
|
|
167
154
|
setLoadingOauth((prev) => ({ ...prev, [provider]: false }));
|
|
168
155
|
}
|
|
@@ -175,7 +162,7 @@ function SignIn({
|
|
|
175
162
|
if (e?.preventDefault) e.preventDefault();
|
|
176
163
|
startOAuthFlow("github");
|
|
177
164
|
};
|
|
178
|
-
return /* @__PURE__ */ React.createElement(
|
|
165
|
+
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(
|
|
179
166
|
"div",
|
|
180
167
|
{
|
|
181
168
|
key: t.id,
|
|
@@ -188,13 +175,13 @@ function SignIn({
|
|
|
188
175
|
if (t._timer) clearTimeout(t._timer);
|
|
189
176
|
},
|
|
190
177
|
onMouseLeave: () => {
|
|
191
|
-
const timer = setTimeout(() =>
|
|
178
|
+
const timer = setTimeout(() => removeToast(t.id), 3e3);
|
|
192
179
|
setToasts((prev) => prev.map((x) => x.id === t.id ? { ...x, _timer: timer } : x));
|
|
193
180
|
}
|
|
194
181
|
},
|
|
195
182
|
/* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }, t.message),
|
|
196
|
-
/* @__PURE__ */ React.createElement("button", { "aria-label": "Dismiss", onClick: () =>
|
|
197
|
-
))), /* @__PURE__ */ React.createElement("div", { style:
|
|
183
|
+
/* @__PURE__ */ React.createElement("button", { "aria-label": "Dismiss", onClick: () => removeToast(t.id), style: toastCloseBtn }, "\u2715")
|
|
184
|
+
))), /* @__PURE__ */ React.createElement("div", { style: modal }, /* @__PURE__ */ React.createElement("div", { style: modalInner }, /* @__PURE__ */ React.createElement("header", { style: header }, /* @__PURE__ */ React.createElement("div", { style: brandRow }, /* @__PURE__ */ React.createElement("div", { style: logo }, /* @__PURE__ */ React.createElement("div", { style: logoCircle, "aria-hidden": true })), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("h1", { style: title }, "Sign in to ", agency?.name || "App"), /* @__PURE__ */ React.createElement("div", { style: subtitle }, "Welcome back \u2014 enter your credentials.")))), /* @__PURE__ */ React.createElement("section", { style: oauthSection }, /* @__PURE__ */ React.createElement(
|
|
198
185
|
"button",
|
|
199
186
|
{
|
|
200
187
|
onClick: handleGoogle,
|
|
@@ -204,7 +191,7 @@ function SignIn({
|
|
|
204
191
|
"aria-disabled": loading || loadingOauth.google
|
|
205
192
|
},
|
|
206
193
|
/* @__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")
|
|
194
|
+
/* @__PURE__ */ React.createElement("span", null, loadingOauth.google ? "Loading..." : "Continue with Google")
|
|
208
195
|
), /* @__PURE__ */ React.createElement(
|
|
209
196
|
"button",
|
|
210
197
|
{
|
|
@@ -215,142 +202,185 @@ function SignIn({
|
|
|
215
202
|
"aria-disabled": loading || loadingOauth.github
|
|
216
203
|
},
|
|
217
204
|
/* @__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..." : "
|
|
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:
|
|
205
|
+
/* @__PURE__ */ React.createElement("span", null, loadingOauth.github ? "Loading..." : "Continue with GitHub")
|
|
206
|
+
)), /* @__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: form }, /* @__PURE__ */ React.createElement("label", { style: label, htmlFor: "email" }, /* @__PURE__ */ React.createElement("span", { style: labelText }, "Email address"), /* @__PURE__ */ React.createElement(
|
|
220
207
|
"input",
|
|
221
208
|
{
|
|
222
209
|
id: "email",
|
|
223
210
|
type: "email",
|
|
224
|
-
|
|
225
|
-
|
|
211
|
+
value: email,
|
|
212
|
+
onChange: (e) => setEmail(e.target.value),
|
|
226
213
|
required: true,
|
|
227
214
|
placeholder: "you@example.com",
|
|
228
|
-
style: input
|
|
215
|
+
style: input,
|
|
216
|
+
autoComplete: "email"
|
|
229
217
|
}
|
|
230
|
-
)), /* @__PURE__ */ React.createElement("label", { style: label }, /* @__PURE__ */ React.createElement("span", { style:
|
|
218
|
+
)), /* @__PURE__ */ React.createElement("label", { style: label, htmlFor: "password" }, /* @__PURE__ */ React.createElement("span", { style: labelText }, "Password"), /* @__PURE__ */ React.createElement(
|
|
231
219
|
"input",
|
|
232
220
|
{
|
|
233
221
|
id: "password",
|
|
234
222
|
type: "password",
|
|
235
|
-
|
|
236
|
-
|
|
223
|
+
value: password,
|
|
224
|
+
onChange: (e) => setPassword(e.target.value),
|
|
237
225
|
required: true,
|
|
238
226
|
placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022",
|
|
239
|
-
style: input
|
|
227
|
+
style: input,
|
|
228
|
+
autoComplete: "current-password"
|
|
240
229
|
}
|
|
241
230
|
)), /* @__PURE__ */ React.createElement(
|
|
242
231
|
"button",
|
|
243
232
|
{
|
|
244
233
|
type: "submit",
|
|
245
|
-
style:
|
|
246
|
-
|
|
247
|
-
disabled: loading
|
|
234
|
+
style: submitButton,
|
|
235
|
+
disabled: loading,
|
|
236
|
+
"aria-disabled": loading
|
|
248
237
|
},
|
|
249
238
|
loading ? "Signing in..." : "Sign In"
|
|
250
|
-
))), /* @__PURE__ */ React.createElement("div", { style:
|
|
239
|
+
))), /* @__PURE__ */ React.createElement("div", { style: modalFooter }, /* @__PURE__ */ React.createElement("div", { style: belowRow }, /* @__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: dividerThin }), /* @__PURE__ */ React.createElement("div", { style: secured }, /* @__PURE__ */ React.createElement("div", { style: securedText }, "Secured by auth")))));
|
|
251
240
|
}
|
|
252
|
-
const
|
|
241
|
+
const overlay = {
|
|
242
|
+
position: "fixed",
|
|
243
|
+
inset: 0,
|
|
244
|
+
display: "block",
|
|
245
|
+
// page scroll model
|
|
246
|
+
padding: 20,
|
|
247
|
+
background: "linear-gradient(180deg, rgba(2,6,23,0.22), rgba(2,6,23,0.32))",
|
|
248
|
+
backdropFilter: "blur(6px)",
|
|
249
|
+
overflowY: "auto",
|
|
250
|
+
WebkitOverflowScrolling: "touch",
|
|
253
251
|
minHeight: "100vh",
|
|
254
|
-
|
|
255
|
-
alignItems: "center",
|
|
256
|
-
justifyContent: "center",
|
|
257
|
-
padding: "24px",
|
|
258
|
-
background: "linear-gradient(to bottom, #0f1724, #0b1220 40%, #02040a 100%)",
|
|
259
|
-
color: "#fef3c7"
|
|
252
|
+
zIndex: 9999
|
|
260
253
|
};
|
|
261
|
-
const
|
|
254
|
+
const modal = {
|
|
262
255
|
width: "100%",
|
|
263
|
-
maxWidth:
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
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)",
|
|
256
|
+
maxWidth: 560,
|
|
257
|
+
margin: "40px auto",
|
|
258
|
+
borderRadius: 14,
|
|
259
|
+
background: "linear-gradient(180deg, rgba(15,19,24,0.6), rgba(10,12,16,0.6))",
|
|
260
|
+
border: "1px solid rgba(99,102,106,0.12)",
|
|
261
|
+
boxShadow: "0 20px 50px rgba(2,6,23,0.55), inset 0 1px 0 rgba(255,255,255,0.02)",
|
|
272
262
|
overflow: "hidden",
|
|
273
263
|
display: "flex",
|
|
274
|
-
flexDirection: "column"
|
|
264
|
+
flexDirection: "column",
|
|
265
|
+
maxHeight: "calc(100vh - 80px)"
|
|
275
266
|
};
|
|
276
|
-
const
|
|
277
|
-
padding:
|
|
267
|
+
const modalInner = {
|
|
268
|
+
padding: 18,
|
|
278
269
|
display: "flex",
|
|
279
270
|
flexDirection: "column",
|
|
280
|
-
gap:
|
|
281
|
-
|
|
282
|
-
borderRadius: "20px"
|
|
271
|
+
gap: 12,
|
|
272
|
+
overflowY: "auto"
|
|
283
273
|
};
|
|
284
|
-
const
|
|
274
|
+
const header = {
|
|
275
|
+
paddingBottom: 6,
|
|
276
|
+
borderBottom: "1px solid rgba(148,163,184,0.04)"
|
|
277
|
+
};
|
|
278
|
+
const brandRow = {
|
|
285
279
|
display: "flex",
|
|
286
|
-
flexDirection: "column",
|
|
287
|
-
gap: 8,
|
|
288
280
|
alignItems: "center",
|
|
289
|
-
|
|
281
|
+
gap: 12
|
|
290
282
|
};
|
|
291
|
-
const
|
|
283
|
+
const logo = {
|
|
284
|
+
width: 44,
|
|
285
|
+
height: 44,
|
|
292
286
|
display: "flex",
|
|
293
287
|
alignItems: "center",
|
|
294
|
-
gap: 10,
|
|
295
288
|
justifyContent: "center"
|
|
296
289
|
};
|
|
297
|
-
const
|
|
298
|
-
width:
|
|
299
|
-
height:
|
|
290
|
+
const logoCircle = {
|
|
291
|
+
width: 36,
|
|
292
|
+
height: 36,
|
|
300
293
|
borderRadius: 999,
|
|
301
|
-
|
|
294
|
+
background: "linear-gradient(135deg,#2f3438,#11151a)",
|
|
295
|
+
boxShadow: "0 4px 12px rgba(2,6,23,0.6)"
|
|
296
|
+
};
|
|
297
|
+
const title = {
|
|
298
|
+
margin: 0,
|
|
299
|
+
fontSize: 18,
|
|
300
|
+
fontWeight: 600,
|
|
301
|
+
color: "#e6e6e6"
|
|
302
|
+
};
|
|
303
|
+
const subtitle = {
|
|
304
|
+
fontSize: 13,
|
|
305
|
+
color: "rgba(230,230,230,0.72)",
|
|
306
|
+
marginTop: 4
|
|
307
|
+
};
|
|
308
|
+
const oauthSection = {
|
|
309
|
+
marginTop: 8,
|
|
302
310
|
display: "flex",
|
|
303
|
-
|
|
304
|
-
|
|
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 };
|
|
311
|
+
gap: 8
|
|
312
|
+
};
|
|
313
313
|
const oauthButton = {
|
|
314
314
|
flex: 1,
|
|
315
315
|
display: "inline-flex",
|
|
316
316
|
alignItems: "center",
|
|
317
317
|
justifyContent: "center",
|
|
318
318
|
padding: "10px 12px",
|
|
319
|
-
borderRadius:
|
|
320
|
-
border: "1px solid rgba(148,163,184,0.
|
|
319
|
+
borderRadius: 10,
|
|
320
|
+
border: "1px solid rgba(148,163,184,0.08)",
|
|
321
321
|
fontSize: 14,
|
|
322
322
|
cursor: "pointer",
|
|
323
323
|
userSelect: "none",
|
|
324
324
|
gap: 8,
|
|
325
|
-
minHeight: 40
|
|
326
|
-
|
|
325
|
+
minHeight: 40
|
|
326
|
+
};
|
|
327
|
+
const oauthGoogle = {
|
|
328
|
+
background: "linear-gradient(180deg, rgba(255,255,255,0.03), rgba(255,255,255,0.01))",
|
|
329
|
+
color: "#fff"
|
|
330
|
+
};
|
|
331
|
+
const oauthGithub = {
|
|
332
|
+
background: "linear-gradient(180deg, rgba(255,255,255,0.02), rgba(255,255,255,0.00))",
|
|
327
333
|
color: "#fff"
|
|
328
334
|
};
|
|
329
|
-
const
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
const
|
|
336
|
-
|
|
335
|
+
const dividerRow = {
|
|
336
|
+
display: "flex",
|
|
337
|
+
alignItems: "center",
|
|
338
|
+
gap: 10,
|
|
339
|
+
marginTop: 12
|
|
340
|
+
};
|
|
341
|
+
const line = {
|
|
342
|
+
flex: 1,
|
|
343
|
+
height: 1,
|
|
344
|
+
background: "rgba(148,163,184,0.06)"
|
|
345
|
+
};
|
|
346
|
+
const orText = {
|
|
347
|
+
fontSize: 13,
|
|
348
|
+
color: "rgba(230,230,230,0.65)",
|
|
349
|
+
padding: "0 8px"
|
|
350
|
+
};
|
|
351
|
+
const form = {
|
|
352
|
+
display: "flex",
|
|
353
|
+
flexDirection: "column",
|
|
354
|
+
gap: 10,
|
|
355
|
+
marginTop: 6
|
|
356
|
+
};
|
|
357
|
+
const label = {
|
|
358
|
+
display: "flex",
|
|
359
|
+
flexDirection: "column",
|
|
360
|
+
gap: 6
|
|
361
|
+
};
|
|
362
|
+
const labelText = {
|
|
363
|
+
fontSize: 13,
|
|
364
|
+
color: "rgba(230,230,230,0.68)"
|
|
365
|
+
};
|
|
337
366
|
const input = {
|
|
338
367
|
width: "100%",
|
|
339
368
|
padding: "10px 12px",
|
|
340
|
-
borderRadius:
|
|
369
|
+
borderRadius: 10,
|
|
341
370
|
background: "rgba(255,255,255,0.02)",
|
|
342
|
-
color: "#
|
|
343
|
-
border: "1px solid rgba(148,163,184,0.
|
|
371
|
+
color: "#e6e6e6",
|
|
372
|
+
border: "1px solid rgba(148,163,184,0.10)",
|
|
344
373
|
fontSize: 14,
|
|
345
374
|
outline: "none",
|
|
346
|
-
boxSizing: "border-box"
|
|
375
|
+
boxSizing: "border-box",
|
|
376
|
+
transition: "box-shadow 120ms, border-color 120ms"
|
|
347
377
|
};
|
|
348
|
-
const
|
|
378
|
+
const submitButton = {
|
|
349
379
|
marginTop: 6,
|
|
350
380
|
width: "100%",
|
|
351
381
|
padding: "10px 12px",
|
|
352
|
-
borderRadius:
|
|
353
|
-
background: "linear-gradient(180deg,#
|
|
382
|
+
borderRadius: 10,
|
|
383
|
+
background: "linear-gradient(180deg,#475569,#94a3b8)",
|
|
354
384
|
border: "none",
|
|
355
385
|
color: "#fff",
|
|
356
386
|
fontWeight: 600,
|
|
@@ -361,16 +391,43 @@ const submitBtn = {
|
|
|
361
391
|
justifyContent: "center",
|
|
362
392
|
minHeight: 44
|
|
363
393
|
};
|
|
364
|
-
const
|
|
365
|
-
padding: "18px",
|
|
394
|
+
const modalFooter = {
|
|
395
|
+
padding: "12px 18px 18px 18px",
|
|
366
396
|
borderTop: "1px solid rgba(148,163,184,0.03)",
|
|
367
|
-
|
|
397
|
+
display: "flex",
|
|
398
|
+
flexDirection: "column",
|
|
399
|
+
gap: 8
|
|
400
|
+
};
|
|
401
|
+
const belowRow = {
|
|
402
|
+
textAlign: "center",
|
|
403
|
+
display: "flex",
|
|
404
|
+
justifyContent: "center",
|
|
405
|
+
gap: 8,
|
|
406
|
+
alignItems: "center"
|
|
407
|
+
};
|
|
408
|
+
const muted = {
|
|
409
|
+
color: "rgba(230,230,230,0.66)",
|
|
410
|
+
fontSize: 13
|
|
411
|
+
};
|
|
412
|
+
const link = {
|
|
413
|
+
color: "#9fb0d9",
|
|
414
|
+
textDecoration: "none",
|
|
415
|
+
fontWeight: 600
|
|
416
|
+
};
|
|
417
|
+
const dividerThin = {
|
|
418
|
+
height: 1,
|
|
419
|
+
background: "rgba(148,163,184,0.04)",
|
|
420
|
+
marginTop: 6,
|
|
421
|
+
marginBottom: 6
|
|
422
|
+
};
|
|
423
|
+
const secured = {
|
|
424
|
+
textAlign: "center"
|
|
425
|
+
};
|
|
426
|
+
const securedText = {
|
|
427
|
+
color: "rgba(230,230,230,0.9)",
|
|
428
|
+
fontSize: 13,
|
|
429
|
+
fontWeight: 600
|
|
368
430
|
};
|
|
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 };
|
|
374
431
|
const toastContainer = {
|
|
375
432
|
position: "fixed",
|
|
376
433
|
top: 18,
|
|
@@ -390,11 +447,30 @@ const toastBase = {
|
|
|
390
447
|
borderRadius: 10,
|
|
391
448
|
boxShadow: "0 8px 20px rgba(2,6,23,0.6)",
|
|
392
449
|
color: "#fff",
|
|
393
|
-
fontSize: 13
|
|
450
|
+
fontSize: 13,
|
|
451
|
+
minWidth: 120
|
|
452
|
+
};
|
|
453
|
+
const toastError = {
|
|
454
|
+
background: "#000000",
|
|
455
|
+
border: "1px solid rgba(255,255,255,0.06)"
|
|
456
|
+
};
|
|
457
|
+
const toastSuccess = {
|
|
458
|
+
background: "#000000",
|
|
459
|
+
border: "1px solid rgba(255,255,255,0.06)"
|
|
460
|
+
};
|
|
461
|
+
const toastInfo = {
|
|
462
|
+
background: "#000000",
|
|
463
|
+
border: "1px solid rgba(255,255,255,0.06)"
|
|
464
|
+
};
|
|
465
|
+
const toastCloseBtn = {
|
|
466
|
+
marginLeft: 8,
|
|
467
|
+
background: "transparent",
|
|
468
|
+
border: "none",
|
|
469
|
+
color: "rgba(255,255,255,0.7)",
|
|
470
|
+
cursor: "pointer",
|
|
471
|
+
fontSize: 14,
|
|
472
|
+
lineHeight: 1
|
|
394
473
|
};
|
|
395
|
-
const toastError = { background: "#000", border: "1px solid rgba(255,255,255,0.06)" };
|
|
396
|
-
const toastSuccess = { background: "#000", border: "1px solid rgba(255,255,255,0.06)" };
|
|
397
|
-
const toastCloseBtn = { marginLeft: 8, background: "transparent", border: "none", color: "rgba(255,255,255,0.7)", cursor: "pointer", fontSize: 14, lineHeight: 1 };
|
|
398
474
|
export {
|
|
399
475
|
SignIn as default
|
|
400
476
|
};
|
package/package.json
CHANGED
package/src/SignIn.jsx
CHANGED
|
@@ -5,22 +5,7 @@ import Image from 'next/image'
|
|
|
5
5
|
import Link from 'next/link'
|
|
6
6
|
import { useAuth } from './provider.js'
|
|
7
7
|
|
|
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
|
-
} = {}) {
|
|
8
|
+
export default function SignIn({ agency = { name: 'MyApp', logo: '' }, onSuccess, onError } = {}) {
|
|
24
9
|
const {
|
|
25
10
|
publishableKey,
|
|
26
11
|
baseUrl,
|
|
@@ -33,17 +18,18 @@ export default function SignIn({
|
|
|
33
18
|
setUser
|
|
34
19
|
} = (typeof useAuth === 'function' ? useAuth() : {}) || {}
|
|
35
20
|
|
|
36
|
-
const [
|
|
21
|
+
const [email, setEmail] = useState('')
|
|
22
|
+
const [password, setPassword] = useState('')
|
|
37
23
|
const [loading, setLoading] = useState(false)
|
|
38
24
|
const [loadingOauth, setLoadingOauth] = useState({ google: false, github: false })
|
|
39
25
|
const redirectTimer = useRef(null)
|
|
40
26
|
|
|
41
|
-
// local
|
|
27
|
+
// local black toast system (same pattern as signup)
|
|
42
28
|
const toastId = useRef(0)
|
|
43
29
|
const [toasts, setToasts] = useState([])
|
|
44
30
|
|
|
45
31
|
useEffect(() => {
|
|
46
|
-
//
|
|
32
|
+
// soft-disable pinch-zoom on mobile while mounted (SDK-safe)
|
|
47
33
|
const meta = document.createElement('meta')
|
|
48
34
|
meta.name = 'viewport'
|
|
49
35
|
meta.content = 'width=device-width, initial-scale=1, maximum-scale=1'
|
|
@@ -59,16 +45,15 @@ export default function SignIn({
|
|
|
59
45
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
60
46
|
}, [])
|
|
61
47
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
// if host provided onError/onSuccess, prefer that
|
|
48
|
+
function showToast(type, message, ms = 4000) {
|
|
49
|
+
// allow host to handle errors/success if they provided callbacks
|
|
65
50
|
if (type === 'error' && typeof onError === 'function') {
|
|
66
51
|
try { onError(message) } catch (_) {}
|
|
67
52
|
return
|
|
68
53
|
}
|
|
69
54
|
if (type === 'success' && typeof onSuccess === 'function') {
|
|
70
55
|
try { onSuccess(message) } catch (_) {}
|
|
71
|
-
// still show local toast for feedback
|
|
56
|
+
// still show local toast for feedback
|
|
72
57
|
}
|
|
73
58
|
|
|
74
59
|
const id = ++toastId.current
|
|
@@ -80,7 +65,7 @@ export default function SignIn({
|
|
|
80
65
|
t._timer = timer
|
|
81
66
|
}
|
|
82
67
|
|
|
83
|
-
function
|
|
68
|
+
function removeToast(id) {
|
|
84
69
|
setToasts(prev => {
|
|
85
70
|
prev.forEach(t => { if (t.id === id && t._timer) clearTimeout(t._timer) })
|
|
86
71
|
return prev.filter(x => x.id !== id)
|
|
@@ -95,18 +80,13 @@ export default function SignIn({
|
|
|
95
80
|
return null
|
|
96
81
|
}
|
|
97
82
|
|
|
98
|
-
function handleChange(e) {
|
|
99
|
-
setForm(prev => ({ ...prev, [e.target.id]: e.target.value }))
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// ---------- submit login ----------
|
|
103
83
|
async function submit(e) {
|
|
104
84
|
e.preventDefault()
|
|
105
85
|
if (loading) return
|
|
106
86
|
setLoading(true)
|
|
107
87
|
|
|
108
|
-
if (!
|
|
109
|
-
|
|
88
|
+
if (!email.includes('@') || !password) {
|
|
89
|
+
showToast('error', 'Email and password are required')
|
|
110
90
|
setLoading(false)
|
|
111
91
|
return
|
|
112
92
|
}
|
|
@@ -121,7 +101,7 @@ export default function SignIn({
|
|
|
121
101
|
'Content-Type': 'application/json',
|
|
122
102
|
'x-publishable-key': publishableKey || ''
|
|
123
103
|
},
|
|
124
|
-
body: JSON.stringify({ email:
|
|
104
|
+
body: JSON.stringify({ email: email.trim(), password })
|
|
125
105
|
})
|
|
126
106
|
|
|
127
107
|
const ct = res.headers.get('content-type') || ''
|
|
@@ -149,28 +129,22 @@ export default function SignIn({
|
|
|
149
129
|
await fetchMe()
|
|
150
130
|
}
|
|
151
131
|
|
|
152
|
-
|
|
153
|
-
showLocalToast('success', 'Signed in. Redirecting...')
|
|
154
|
-
if (typeof onSuccess === 'function') {
|
|
155
|
-
try { onSuccess(data) } catch (_) {}
|
|
156
|
-
}
|
|
157
|
-
|
|
132
|
+
showToast('success', 'Signed in. Redirecting...')
|
|
158
133
|
if (redirect) {
|
|
159
134
|
redirectTimer.current = setTimeout(() => {
|
|
160
135
|
if (typeof redirectTo === 'function') redirectTo(redirect)
|
|
161
136
|
else if (typeof window !== 'undefined') window.location.assign(redirect)
|
|
162
|
-
},
|
|
137
|
+
}, 300)
|
|
163
138
|
}
|
|
164
139
|
} catch (err) {
|
|
165
|
-
const
|
|
166
|
-
|
|
140
|
+
const msg = err?.message ?? 'Network error'
|
|
141
|
+
showToast('error', msg)
|
|
167
142
|
console.error('Signin error:', err)
|
|
168
143
|
} finally {
|
|
169
144
|
setLoading(false)
|
|
170
145
|
}
|
|
171
146
|
}
|
|
172
147
|
|
|
173
|
-
// ---------- OAuth start ----------
|
|
174
148
|
async function startOAuthFlow(provider) {
|
|
175
149
|
if (loading || loadingOauth[provider]) return
|
|
176
150
|
setLoadingOauth(prev => ({ ...prev, [provider]: true }))
|
|
@@ -189,18 +163,16 @@ export default function SignIn({
|
|
|
189
163
|
|
|
190
164
|
const res = await fetch(startUrl, {
|
|
191
165
|
method: 'GET',
|
|
192
|
-
headers: { 'x-publishable-key': publishableKey }
|
|
193
|
-
mode: 'cors'
|
|
166
|
+
headers: { 'x-publishable-key': publishableKey }
|
|
194
167
|
})
|
|
195
168
|
|
|
196
169
|
const data = await res.json().catch(() => null)
|
|
197
170
|
if (!res.ok) throw new Error(data?.error || `OAuth start failed (${res.status})`)
|
|
198
171
|
if (!data?.oauthUrl) throw new Error('SDK start did not return oauthUrl')
|
|
199
172
|
|
|
200
|
-
// navigate to provider (page unloads)
|
|
201
173
|
if (typeof window !== 'undefined') window.location.href = data.oauthUrl
|
|
202
174
|
} catch (err) {
|
|
203
|
-
|
|
175
|
+
showToast('error', err?.message || 'OAuth start failed')
|
|
204
176
|
console.error('OAuth start error:', err)
|
|
205
177
|
setLoadingOauth(prev => ({ ...prev, [provider]: false }))
|
|
206
178
|
}
|
|
@@ -209,10 +181,9 @@ export default function SignIn({
|
|
|
209
181
|
const handleGoogle = (e) => { if (e?.preventDefault) e.preventDefault(); startOAuthFlow('google') }
|
|
210
182
|
const handleGithub = (e) => { if (e?.preventDefault) e.preventDefault(); startOAuthFlow('github') }
|
|
211
183
|
|
|
212
|
-
// ---------- render ----------
|
|
213
184
|
return (
|
|
214
|
-
|
|
215
|
-
{/*
|
|
185
|
+
<div style={overlay}>
|
|
186
|
+
{/* local toast container (black) */}
|
|
216
187
|
<div style={toastContainer} aria-live="polite" aria-atomic="true">
|
|
217
188
|
{toasts.map(t => (
|
|
218
189
|
<div
|
|
@@ -224,199 +195,205 @@ export default function SignIn({
|
|
|
224
195
|
}}
|
|
225
196
|
onMouseEnter={() => { if (t._timer) clearTimeout(t._timer) }}
|
|
226
197
|
onMouseLeave={() => {
|
|
227
|
-
const timer = setTimeout(() =>
|
|
198
|
+
const timer = setTimeout(() => removeToast(t.id), 3000)
|
|
228
199
|
setToasts(prev => prev.map(x => x.id === t.id ? { ...x, _timer: timer } : x))
|
|
229
200
|
}}
|
|
230
201
|
>
|
|
231
202
|
<div style={{ flex: 1 }}>{t.message}</div>
|
|
232
|
-
<button aria-label="Dismiss" onClick={() =>
|
|
203
|
+
<button aria-label="Dismiss" onClick={() => removeToast(t.id)} style={toastCloseBtn}>✕</button>
|
|
233
204
|
</div>
|
|
234
205
|
))}
|
|
235
206
|
</div>
|
|
236
207
|
|
|
237
|
-
<div style={
|
|
238
|
-
<div style={
|
|
239
|
-
<
|
|
240
|
-
<div style={
|
|
241
|
-
<div style={
|
|
242
|
-
<div style={
|
|
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>
|
|
208
|
+
<div style={modal}>
|
|
209
|
+
<div style={modalInner}>
|
|
210
|
+
<header style={header}>
|
|
211
|
+
<div style={brandRow}>
|
|
212
|
+
<div style={logo}>
|
|
213
|
+
<div style={logoCircle} aria-hidden />
|
|
252
214
|
</div>
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
<
|
|
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} />
|
|
215
|
+
<div>
|
|
216
|
+
<h1 style={title}>Sign in to {agency?.name || 'App'}</h1>
|
|
217
|
+
<div style={subtitle}>Welcome back — enter your credentials.</div>
|
|
289
218
|
</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
219
|
</div>
|
|
220
|
+
</header>
|
|
221
|
+
|
|
222
|
+
<section style={oauthSection}>
|
|
223
|
+
<button
|
|
224
|
+
onClick={handleGoogle}
|
|
225
|
+
type="button"
|
|
226
|
+
style={{ ...oauthButton, ...oauthGoogle }}
|
|
227
|
+
disabled={loading || loadingOauth.google}
|
|
228
|
+
aria-disabled={loading || loadingOauth.google}
|
|
229
|
+
>
|
|
230
|
+
<svg width={18} style={{ marginRight: 10 }} viewBox="-3 0 262 262" xmlns="http://www.w3.org/2000/svg" fill="#000000" aria-hidden>
|
|
231
|
+
<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>
|
|
232
|
+
<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>
|
|
233
|
+
<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>
|
|
234
|
+
<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>
|
|
235
|
+
</svg>
|
|
236
|
+
<span>{loadingOauth.google ? 'Loading...' : 'Continue with Google'}</span>
|
|
237
|
+
</button>
|
|
238
|
+
|
|
239
|
+
<button
|
|
240
|
+
onClick={handleGithub}
|
|
241
|
+
type="button"
|
|
242
|
+
style={{ ...oauthButton, ...oauthGithub }}
|
|
243
|
+
disabled={loading || loadingOauth.github}
|
|
244
|
+
aria-disabled={loading || loadingOauth.github}
|
|
245
|
+
>
|
|
246
|
+
<svg width={18} style={{ marginRight: 10 }} xmlns="http://www.w3.org/2000/svg" fill="white" viewBox="0 0 20 20" aria-hidden>
|
|
247
|
+
<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" />
|
|
248
|
+
</svg>
|
|
249
|
+
<span>{loadingOauth.github ? 'Loading...' : 'Continue with GitHub'}</span>
|
|
250
|
+
</button>
|
|
251
|
+
</section>
|
|
252
|
+
|
|
253
|
+
<div style={dividerRow}>
|
|
254
|
+
<div style={line} />
|
|
255
|
+
<div style={orText}>or</div>
|
|
256
|
+
<div style={line} />
|
|
257
|
+
</div>
|
|
328
258
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
259
|
+
<form onSubmit={submit} style={form}>
|
|
260
|
+
<label style={label} htmlFor="email">
|
|
261
|
+
<span style={labelText}>Email address</span>
|
|
262
|
+
<input
|
|
263
|
+
id="email"
|
|
264
|
+
type="email"
|
|
265
|
+
value={email}
|
|
266
|
+
onChange={e => setEmail(e.target.value)}
|
|
267
|
+
required
|
|
268
|
+
placeholder="you@example.com"
|
|
269
|
+
style={input}
|
|
270
|
+
autoComplete="email"
|
|
271
|
+
/>
|
|
272
|
+
</label>
|
|
273
|
+
|
|
274
|
+
<label style={label} htmlFor="password">
|
|
275
|
+
<span style={labelText}>Password</span>
|
|
276
|
+
<input
|
|
277
|
+
id="password"
|
|
278
|
+
type="password"
|
|
279
|
+
value={password}
|
|
280
|
+
onChange={e => setPassword(e.target.value)}
|
|
281
|
+
required
|
|
282
|
+
placeholder="••••••••"
|
|
283
|
+
style={input}
|
|
284
|
+
autoComplete="current-password"
|
|
285
|
+
/>
|
|
286
|
+
</label>
|
|
287
|
+
|
|
288
|
+
<button
|
|
289
|
+
type="submit"
|
|
290
|
+
style={submitButton}
|
|
291
|
+
disabled={loading}
|
|
292
|
+
aria-disabled={loading}
|
|
293
|
+
>
|
|
294
|
+
{loading ? 'Signing in...' : 'Sign In'}
|
|
295
|
+
</button>
|
|
296
|
+
</form>
|
|
297
|
+
</div>
|
|
336
298
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
</
|
|
299
|
+
<div style={modalFooter}>
|
|
300
|
+
<div style={belowRow}>
|
|
301
|
+
<span style={muted}>Don't have an account? </span>
|
|
302
|
+
<Link href="/signup" style={link}>Create one</Link>
|
|
303
|
+
</div>
|
|
304
|
+
<div style={dividerThin} />
|
|
305
|
+
<div style={secured}>
|
|
306
|
+
<div style={securedText}>Secured by auth</div>
|
|
341
307
|
</div>
|
|
342
308
|
</div>
|
|
343
309
|
</div>
|
|
344
|
-
|
|
310
|
+
</div>
|
|
345
311
|
)
|
|
346
312
|
}
|
|
347
313
|
|
|
348
|
-
/*
|
|
349
|
-
|
|
350
|
-
|
|
314
|
+
/* styles (JS objects) - neutral palette, translucent overlay so underlying content shows */
|
|
315
|
+
const overlay = {
|
|
316
|
+
position: 'fixed',
|
|
317
|
+
inset: 0,
|
|
318
|
+
display: 'block', // page scroll model
|
|
319
|
+
padding: 20,
|
|
320
|
+
background: 'linear-gradient(180deg, rgba(2,6,23,0.22), rgba(2,6,23,0.32))',
|
|
321
|
+
backdropFilter: 'blur(6px)',
|
|
322
|
+
overflowY: 'auto',
|
|
323
|
+
WebkitOverflowScrolling: 'touch',
|
|
351
324
|
minHeight: '100vh',
|
|
352
|
-
|
|
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
|
|
325
|
+
zIndex: 9999
|
|
363
326
|
}
|
|
364
327
|
|
|
365
|
-
const
|
|
328
|
+
const modal = {
|
|
366
329
|
width: '100%',
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
330
|
+
maxWidth: 560,
|
|
331
|
+
margin: '40px auto',
|
|
332
|
+
borderRadius: 14,
|
|
333
|
+
background: 'linear-gradient(180deg, rgba(15,19,24,0.6), rgba(10,12,16,0.6))',
|
|
334
|
+
border: '1px solid rgba(99,102,106,0.12)',
|
|
335
|
+
boxShadow: '0 20px 50px rgba(2,6,23,0.55), inset 0 1px 0 rgba(255,255,255,0.02)',
|
|
372
336
|
overflow: 'hidden',
|
|
373
337
|
display: 'flex',
|
|
374
|
-
flexDirection: 'column'
|
|
338
|
+
flexDirection: 'column',
|
|
339
|
+
maxHeight: 'calc(100vh - 80px)'
|
|
375
340
|
}
|
|
376
341
|
|
|
377
|
-
const
|
|
378
|
-
padding:
|
|
342
|
+
const modalInner = {
|
|
343
|
+
padding: 18,
|
|
379
344
|
display: 'flex',
|
|
380
345
|
flexDirection: 'column',
|
|
381
|
-
gap:
|
|
382
|
-
|
|
383
|
-
|
|
346
|
+
gap: 12,
|
|
347
|
+
overflowY: 'auto'
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/* header / brand */
|
|
351
|
+
const header = {
|
|
352
|
+
paddingBottom: 6,
|
|
353
|
+
borderBottom: '1px solid rgba(148,163,184,0.04)'
|
|
384
354
|
}
|
|
385
355
|
|
|
386
|
-
const
|
|
356
|
+
const brandRow = {
|
|
387
357
|
display: 'flex',
|
|
388
|
-
flexDirection: 'column',
|
|
389
|
-
gap: 8,
|
|
390
358
|
alignItems: 'center',
|
|
391
|
-
|
|
359
|
+
gap: 12
|
|
392
360
|
}
|
|
393
361
|
|
|
394
|
-
const
|
|
362
|
+
const logo = {
|
|
363
|
+
width: 44,
|
|
364
|
+
height: 44,
|
|
395
365
|
display: 'flex',
|
|
396
366
|
alignItems: 'center',
|
|
397
|
-
gap: 10,
|
|
398
367
|
justifyContent: 'center'
|
|
399
368
|
}
|
|
400
369
|
|
|
401
|
-
const
|
|
402
|
-
width:
|
|
403
|
-
height:
|
|
370
|
+
const logoCircle = {
|
|
371
|
+
width: 36,
|
|
372
|
+
height: 36,
|
|
404
373
|
borderRadius: 999,
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
alignItems: 'center',
|
|
408
|
-
justifyContent: 'center',
|
|
409
|
-
background: 'linear-gradient(135deg,#2b313a,#0f1724)'
|
|
374
|
+
background: 'linear-gradient(135deg,#2f3438,#11151a)',
|
|
375
|
+
boxShadow: '0 4px 12px rgba(2,6,23,0.6)'
|
|
410
376
|
}
|
|
411
377
|
|
|
412
|
-
const
|
|
378
|
+
const title = {
|
|
379
|
+
margin: 0,
|
|
380
|
+
fontSize: 18,
|
|
381
|
+
fontWeight: 600,
|
|
382
|
+
color: '#e6e6e6'
|
|
383
|
+
}
|
|
413
384
|
|
|
414
|
-
const
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
385
|
+
const subtitle = {
|
|
386
|
+
fontSize: 13,
|
|
387
|
+
color: 'rgba(230,230,230,0.72)',
|
|
388
|
+
marginTop: 4
|
|
389
|
+
}
|
|
418
390
|
|
|
419
|
-
|
|
391
|
+
/* oauth row */
|
|
392
|
+
const oauthSection = {
|
|
393
|
+
marginTop: 8,
|
|
394
|
+
display: 'flex',
|
|
395
|
+
gap: 8
|
|
396
|
+
}
|
|
420
397
|
|
|
421
398
|
const oauthButton = {
|
|
422
399
|
flex: 1,
|
|
@@ -424,46 +401,83 @@ const oauthButton = {
|
|
|
424
401
|
alignItems: 'center',
|
|
425
402
|
justifyContent: 'center',
|
|
426
403
|
padding: '10px 12px',
|
|
427
|
-
borderRadius:
|
|
428
|
-
border: '1px solid rgba(148,163,184,0.
|
|
404
|
+
borderRadius: 10,
|
|
405
|
+
border: '1px solid rgba(148,163,184,0.08)',
|
|
429
406
|
fontSize: 14,
|
|
430
407
|
cursor: 'pointer',
|
|
431
408
|
userSelect: 'none',
|
|
432
409
|
gap: 8,
|
|
433
|
-
minHeight: 40
|
|
434
|
-
|
|
410
|
+
minHeight: 40
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const oauthGoogle = {
|
|
414
|
+
background: 'linear-gradient(180deg, rgba(255,255,255,0.03), rgba(255,255,255,0.01))',
|
|
435
415
|
color: '#fff'
|
|
436
416
|
}
|
|
437
417
|
|
|
438
|
-
const
|
|
439
|
-
|
|
418
|
+
const oauthGithub = {
|
|
419
|
+
background: 'linear-gradient(180deg, rgba(255,255,255,0.02), rgba(255,255,255,0.00))',
|
|
420
|
+
color: '#fff'
|
|
421
|
+
}
|
|
440
422
|
|
|
441
|
-
|
|
442
|
-
const
|
|
443
|
-
|
|
423
|
+
/* divider row */
|
|
424
|
+
const dividerRow = {
|
|
425
|
+
display: 'flex',
|
|
426
|
+
alignItems: 'center',
|
|
427
|
+
gap: 10,
|
|
428
|
+
marginTop: 12
|
|
429
|
+
}
|
|
444
430
|
|
|
445
|
-
const
|
|
446
|
-
|
|
447
|
-
|
|
431
|
+
const line = {
|
|
432
|
+
flex: 1,
|
|
433
|
+
height: 1,
|
|
434
|
+
background: 'rgba(148,163,184,0.06)'
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const orText = {
|
|
438
|
+
fontSize: 13,
|
|
439
|
+
color: 'rgba(230,230,230,0.65)',
|
|
440
|
+
padding: '0 8px'
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/* form */
|
|
444
|
+
const form = {
|
|
445
|
+
display: 'flex',
|
|
446
|
+
flexDirection: 'column',
|
|
447
|
+
gap: 10,
|
|
448
|
+
marginTop: 6
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
const label = {
|
|
452
|
+
display: 'flex',
|
|
453
|
+
flexDirection: 'column',
|
|
454
|
+
gap: 6
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
const labelText = {
|
|
458
|
+
fontSize: 13,
|
|
459
|
+
color: 'rgba(230,230,230,0.68)'
|
|
460
|
+
}
|
|
448
461
|
|
|
449
462
|
const input = {
|
|
450
463
|
width: '100%',
|
|
451
464
|
padding: '10px 12px',
|
|
452
|
-
borderRadius:
|
|
465
|
+
borderRadius: 10,
|
|
453
466
|
background: 'rgba(255,255,255,0.02)',
|
|
454
|
-
color: '#
|
|
455
|
-
border: '1px solid rgba(148,163,184,0.
|
|
467
|
+
color: '#e6e6e6',
|
|
468
|
+
border: '1px solid rgba(148,163,184,0.10)',
|
|
456
469
|
fontSize: 14,
|
|
457
470
|
outline: 'none',
|
|
458
|
-
boxSizing: 'border-box'
|
|
471
|
+
boxSizing: 'border-box',
|
|
472
|
+
transition: 'box-shadow 120ms, border-color 120ms'
|
|
459
473
|
}
|
|
460
474
|
|
|
461
|
-
const
|
|
475
|
+
const submitButton = {
|
|
462
476
|
marginTop: 6,
|
|
463
477
|
width: '100%',
|
|
464
478
|
padding: '10px 12px',
|
|
465
|
-
borderRadius:
|
|
466
|
-
background: 'linear-gradient(180deg,#
|
|
479
|
+
borderRadius: 10,
|
|
480
|
+
background: 'linear-gradient(180deg,#475569,#94a3b8)',
|
|
467
481
|
border: 'none',
|
|
468
482
|
color: '#fff',
|
|
469
483
|
fontWeight: 600,
|
|
@@ -475,20 +489,52 @@ const submitBtn = {
|
|
|
475
489
|
minHeight: 44
|
|
476
490
|
}
|
|
477
491
|
|
|
478
|
-
/*
|
|
479
|
-
const
|
|
480
|
-
padding: '18px',
|
|
492
|
+
/* modal footer */
|
|
493
|
+
const modalFooter = {
|
|
494
|
+
padding: '12px 18px 18px 18px',
|
|
481
495
|
borderTop: '1px solid rgba(148,163,184,0.03)',
|
|
482
|
-
|
|
496
|
+
display: 'flex',
|
|
497
|
+
flexDirection: 'column',
|
|
498
|
+
gap: 8
|
|
483
499
|
}
|
|
484
500
|
|
|
485
|
-
const
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
501
|
+
const belowRow = {
|
|
502
|
+
textAlign: 'center',
|
|
503
|
+
display: 'flex',
|
|
504
|
+
justifyContent: 'center',
|
|
505
|
+
gap: 8,
|
|
506
|
+
alignItems: 'center'
|
|
507
|
+
}
|
|
490
508
|
|
|
491
|
-
|
|
509
|
+
const muted = {
|
|
510
|
+
color: 'rgba(230,230,230,0.66)',
|
|
511
|
+
fontSize: 13
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
const link = {
|
|
515
|
+
color: '#9fb0d9',
|
|
516
|
+
textDecoration: 'none',
|
|
517
|
+
fontWeight: 600
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
const dividerThin = {
|
|
521
|
+
height: 1,
|
|
522
|
+
background: 'rgba(148,163,184,0.04)',
|
|
523
|
+
marginTop: 6,
|
|
524
|
+
marginBottom: 6
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
const secured = {
|
|
528
|
+
textAlign: 'center'
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
const securedText = {
|
|
532
|
+
color: 'rgba(230,230,230,0.9)',
|
|
533
|
+
fontSize: 13,
|
|
534
|
+
fontWeight: 600
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/* toasts - black background as requested */
|
|
492
538
|
const toastContainer = {
|
|
493
539
|
position: 'fixed',
|
|
494
540
|
top: 18,
|
|
@@ -500,6 +546,7 @@ const toastContainer = {
|
|
|
500
546
|
gap: 10,
|
|
501
547
|
zIndex: 60000
|
|
502
548
|
}
|
|
549
|
+
|
|
503
550
|
const toastBase = {
|
|
504
551
|
display: 'flex',
|
|
505
552
|
gap: 10,
|
|
@@ -508,8 +555,29 @@ const toastBase = {
|
|
|
508
555
|
borderRadius: 10,
|
|
509
556
|
boxShadow: '0 8px 20px rgba(2,6,23,0.6)',
|
|
510
557
|
color: '#fff',
|
|
511
|
-
fontSize: 13
|
|
558
|
+
fontSize: 13,
|
|
559
|
+
minWidth: 120
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
const toastError = {
|
|
563
|
+
background: '#000000',
|
|
564
|
+
border: '1px solid rgba(255,255,255,0.06)'
|
|
565
|
+
}
|
|
566
|
+
const toastSuccess = {
|
|
567
|
+
background: '#000000',
|
|
568
|
+
border: '1px solid rgba(255,255,255,0.06)'
|
|
569
|
+
}
|
|
570
|
+
const toastInfo = {
|
|
571
|
+
background: '#000000',
|
|
572
|
+
border: '1px solid rgba(255,255,255,0.06)'
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
const toastCloseBtn = {
|
|
576
|
+
marginLeft: 8,
|
|
577
|
+
background: 'transparent',
|
|
578
|
+
border: 'none',
|
|
579
|
+
color: 'rgba(255,255,255,0.7)',
|
|
580
|
+
cursor: 'pointer',
|
|
581
|
+
fontSize: 14,
|
|
582
|
+
lineHeight: 1
|
|
512
583
|
}
|
|
513
|
-
const toastError = { background: '#000', border: '1px solid rgba(255,255,255,0.06)' }
|
|
514
|
-
const toastSuccess = { background: '#000', border: '1px solid rgba(255,255,255,0.06)' }
|
|
515
|
-
const toastCloseBtn = { marginLeft: 8, background: 'transparent', border: 'none', color: 'rgba(255,255,255,0.7)', cursor: 'pointer', fontSize: 14, lineHeight: 1 }
|