flowlink-auth 2.8.7 → 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.
Files changed (3) hide show
  1. package/dist/SignIn.js +195 -119
  2. package/package.json +1 -1
  3. 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 [form2, setForm] = useState({ email: "", password: "" });
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 showLocalToast(type, message, ms = 3500) {
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 removeLocalToast(id) {
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 (!form2.email || !form2.password) {
86
- showLocalToast("error", "Email and password are required");
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: form2.email, password: form2.password })
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
- showLocalToast("success", "Signed in. Redirecting...");
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
- }, 250);
124
+ }, 300);
137
125
  }
138
126
  } catch (err) {
139
- const message = err?.message || "Network error";
140
- showLocalToast("error", message);
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
- showLocalToast("error", err?.message || "OAuth start failed");
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(React.Fragment, null, /* @__PURE__ */ React.createElement("div", { style: toastContainer, "aria-live": "polite", "aria-atomic": "true" }, toasts.map((t) => /* @__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(() => removeLocalToast(t.id), 2500);
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: () => 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(
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..." : "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(
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
- onChange: handleChange,
225
- value: form2.email,
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: labelSmall }, "Password"), /* @__PURE__ */ React.createElement(
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
- onChange: handleChange,
236
- value: form2.password,
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: submitBtn,
246
- "aria-disabled": loading,
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: 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")))))));
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 page = {
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
- 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"
252
+ zIndex: 9999
260
253
  };
261
- const cardWrap = {
254
+ const modal = {
262
255
  width: "100%",
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)",
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 cardInner = {
277
- padding: "28px",
267
+ const modalInner = {
268
+ padding: 18,
278
269
  display: "flex",
279
270
  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"
271
+ gap: 12,
272
+ overflowY: "auto"
283
273
  };
284
- const brand = {
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
- textAlign: "center"
281
+ gap: 12
290
282
  };
291
- const brandRow = {
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 logoWrap = {
298
- width: 30,
299
- height: 30,
290
+ const logoCircle = {
291
+ width: 36,
292
+ height: 36,
300
293
  borderRadius: 999,
301
- overflow: "hidden",
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
- 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 };
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: 12,
320
- border: "1px solid rgba(148,163,184,0.06)",
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
- background: "transparent",
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 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)" };
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: 12,
369
+ borderRadius: 10,
341
370
  background: "rgba(255,255,255,0.02)",
342
- color: "#fef3c7",
343
- border: "1px solid rgba(148,163,184,0.06)",
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 submitBtn = {
378
+ const submitButton = {
349
379
  marginTop: 6,
350
380
  width: "100%",
351
381
  padding: "10px 12px",
352
- borderRadius: 12,
353
- background: "linear-gradient(180deg,#2563eb,#60a5fa)",
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 cardFooter = {
365
- padding: "18px",
394
+ const modalFooter = {
395
+ padding: "12px 18px 18px 18px",
366
396
  borderTop: "1px solid rgba(148,163,184,0.03)",
367
- background: "transparent"
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flowlink-auth",
3
- "version": "2.8.7",
3
+ "version": "2.8.9",
4
4
  "description": "Custom auth library",
5
5
  "main": "dist/index.js",
6
6
  "types": "src/index.d.ts",
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 [form, setForm] = useState({ email: '', password: '' })
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 (SDK) toasts — black background used only if host doesn't provide onError/onSuccess
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
- // Soft-disable pinch-zoom on mobile while mounted (SDK-safe)
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
- // ---------- toast helpers ----------
63
- function showLocalToast(type, message, ms = 3500) {
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 (optional)
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 removeLocalToast(id) {
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 (!form.email || !form.password) {
109
- showLocalToast('error', 'Email and password are required')
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: form.email, password: form.password })
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
- // callback & local success toast (host will receive onSuccess if provided)
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
- }, 250)
137
+ }, 300)
163
138
  }
164
139
  } catch (err) {
165
- const message = err?.message || 'Network error'
166
- showLocalToast('error', message)
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
- showLocalToast('error', err?.message || 'OAuth start failed')
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
- {/* Local toast container (black style) */}
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(() => removeLocalToast(t.id), 2500)
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={() => removeLocalToast(t.id)} style={toastCloseBtn}>✕</button>
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={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>
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
- <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} />
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
- <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} />
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
- <div style={{ textAlign: 'center', marginTop: 8 }}>
338
- <div style={securedText}>Secured by auth</div>
339
- </div>
340
- </div>
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
- /* ---------- styles (JS objects) to match the provided UI exactly ---------- */
349
-
350
- const page = {
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
- 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
325
+ zIndex: 9999
363
326
  }
364
327
 
365
- const card = {
328
+ const modal = {
366
329
  width: '100%',
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)',
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 cardInner = {
378
- padding: '28px',
342
+ const modalInner = {
343
+ padding: 18,
379
344
  display: 'flex',
380
345
  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'
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 brand = {
356
+ const brandRow = {
387
357
  display: 'flex',
388
- flexDirection: 'column',
389
- gap: 8,
390
358
  alignItems: 'center',
391
- textAlign: 'center'
359
+ gap: 12
392
360
  }
393
361
 
394
- const brandRow = {
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 logoWrap = {
402
- width: 30,
403
- height: 30,
370
+ const logoCircle = {
371
+ width: 36,
372
+ height: 36,
404
373
  borderRadius: 999,
405
- overflow: 'hidden',
406
- display: 'flex',
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 logoImgStyle = { borderRadius: 999, objectFit: 'cover' }
378
+ const title = {
379
+ margin: 0,
380
+ fontSize: 18,
381
+ fontWeight: 600,
382
+ color: '#e6e6e6'
383
+ }
413
384
 
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' }
385
+ const subtitle = {
386
+ fontSize: 13,
387
+ color: 'rgba(230,230,230,0.72)',
388
+ marginTop: 4
389
+ }
418
390
 
419
- const oauthRow = { display: 'flex', gap: 10, marginTop: 10 }
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: 12,
428
- border: '1px solid rgba(148,163,184,0.06)',
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
- background: 'transparent',
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 oauthGoogle = { background: 'rgba(255,255,255,0.02)' }
439
- const oauthGithub = { background: 'rgba(255,255,255,0.01)' }
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
- 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' }
423
+ /* divider row */
424
+ const dividerRow = {
425
+ display: 'flex',
426
+ alignItems: 'center',
427
+ gap: 10,
428
+ marginTop: 12
429
+ }
444
430
 
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)' }
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: 12,
465
+ borderRadius: 10,
453
466
  background: 'rgba(255,255,255,0.02)',
454
- color: '#fef3c7',
455
- border: '1px solid rgba(148,163,184,0.06)',
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 submitBtn = {
475
+ const submitButton = {
462
476
  marginTop: 6,
463
477
  width: '100%',
464
478
  padding: '10px 12px',
465
- borderRadius: 12,
466
- background: 'linear-gradient(180deg,#2563eb,#60a5fa)',
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
- /* card footer */
479
- const cardFooter = {
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
- background: 'transparent'
496
+ display: 'flex',
497
+ flexDirection: 'column',
498
+ gap: 8
483
499
  }
484
500
 
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 }
501
+ const belowRow = {
502
+ textAlign: 'center',
503
+ display: 'flex',
504
+ justifyContent: 'center',
505
+ gap: 8,
506
+ alignItems: 'center'
507
+ }
490
508
 
491
- /* local toast styles */
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 }