flowlink-auth 2.8.6 → 2.8.8

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 CHANGED
@@ -2,10 +2,12 @@
2
2
  import React, { useEffect, useRef, useState } from "react";
3
3
  import Image from "next/image";
4
4
  import Link from "next/link";
5
- import { ToastContainer, toast } from "react-toastify";
6
- import "react-toastify/dist/ReactToastify.css";
7
5
  import { useAuth } from "./provider.js";
8
- function SignIn({ agency = { name: "chest", logo: "/logo.png" }, onSuccess } = {}) {
6
+ function SignIn({
7
+ agency = { name: "chest", logo: "/logo.png" },
8
+ onSuccess,
9
+ onError
10
+ } = {}) {
9
11
  const {
10
12
  publishableKey,
11
13
  baseUrl,
@@ -21,6 +23,8 @@ function SignIn({ agency = { name: "chest", logo: "/logo.png" }, onSuccess } = {
21
23
  const [loading, setLoading] = useState(false);
22
24
  const [loadingOauth, setLoadingOauth] = useState({ google: false, github: false });
23
25
  const redirectTimer = useRef(null);
26
+ const toastId = useRef(0);
27
+ const [toasts, setToasts] = useState([]);
24
28
  useEffect(() => {
25
29
  const meta = document.createElement("meta");
26
30
  meta.name = "viewport";
@@ -30,8 +34,41 @@ function SignIn({ agency = { name: "chest", logo: "/logo.png" }, onSuccess } = {
30
34
  if (redirectTimer.current) clearTimeout(redirectTimer.current);
31
35
  const existing = document.querySelector('meta[name="viewport"]');
32
36
  if (existing && existing.content === meta.content) document.head.removeChild(existing);
37
+ toasts.forEach((t) => {
38
+ if (t._timer) clearTimeout(t._timer);
39
+ });
33
40
  };
34
41
  }, []);
42
+ function showLocalToast(type, message, ms = 3500) {
43
+ if (type === "error" && typeof onError === "function") {
44
+ try {
45
+ onError(message);
46
+ } catch (_) {
47
+ }
48
+ return;
49
+ }
50
+ if (type === "success" && typeof onSuccess === "function") {
51
+ try {
52
+ onSuccess(message);
53
+ } catch (_) {
54
+ }
55
+ }
56
+ const id = ++toastId.current;
57
+ const t = { id, type, message, _timer: null };
58
+ setToasts((prev) => [t, ...prev].slice(0, 6));
59
+ const timer = setTimeout(() => {
60
+ setToasts((prev) => prev.filter((x) => x.id !== id));
61
+ }, ms);
62
+ t._timer = timer;
63
+ }
64
+ function removeLocalToast(id) {
65
+ setToasts((prev) => {
66
+ prev.forEach((t) => {
67
+ if (t.id === id && t._timer) clearTimeout(t._timer);
68
+ });
69
+ return prev.filter((x) => x.id !== id);
70
+ });
71
+ }
35
72
  if (loadingUser) return null;
36
73
  if (user && redirect) {
37
74
  if (typeof redirectTo === "function") redirectTo(redirect);
@@ -46,7 +83,7 @@ function SignIn({ agency = { name: "chest", logo: "/logo.png" }, onSuccess } = {
46
83
  if (loading) return;
47
84
  setLoading(true);
48
85
  if (!form2.email || !form2.password) {
49
- toast.error("Email and password are required", { toastId: "missing-creds", toastStyle: { background: "#000", color: "#fff" } });
86
+ showLocalToast("error", "Email and password are required");
50
87
  setLoading(false);
51
88
  return;
52
89
  }
@@ -85,13 +122,13 @@ function SignIn({ agency = { name: "chest", logo: "/logo.png" }, onSuccess } = {
85
122
  } else if (typeof fetchMe === "function") {
86
123
  await fetchMe();
87
124
  }
88
- if (onSuccess) {
125
+ showLocalToast("success", "Signed in. Redirecting...");
126
+ if (typeof onSuccess === "function") {
89
127
  try {
90
128
  onSuccess(data);
91
129
  } catch (_) {
92
130
  }
93
131
  }
94
- toast.success("Signed in. Redirecting...", { toastStyle: { background: "#000", color: "#fff" } });
95
132
  if (redirect) {
96
133
  redirectTimer.current = setTimeout(() => {
97
134
  if (typeof redirectTo === "function") redirectTo(redirect);
@@ -99,7 +136,8 @@ function SignIn({ agency = { name: "chest", logo: "/logo.png" }, onSuccess } = {
99
136
  }, 250);
100
137
  }
101
138
  } catch (err) {
102
- toast.error(err?.message || "Network error", { toastStyle: { background: "#000", color: "#fff" } });
139
+ const message = err?.message || "Network error";
140
+ showLocalToast("error", message);
103
141
  console.error("Signin error:", err);
104
142
  } finally {
105
143
  setLoading(false);
@@ -124,7 +162,7 @@ function SignIn({ agency = { name: "chest", logo: "/logo.png" }, onSuccess } = {
124
162
  if (!data?.oauthUrl) throw new Error("SDK start did not return oauthUrl");
125
163
  if (typeof window !== "undefined") window.location.href = data.oauthUrl;
126
164
  } catch (err) {
127
- toast.error(err?.message || "OAuth start failed", { toastStyle: { background: "#000", color: "#fff" } });
165
+ showLocalToast("error", err?.message || "OAuth start failed");
128
166
  console.error("OAuth start error:", err);
129
167
  setLoadingOauth((prev) => ({ ...prev, [provider]: false }));
130
168
  }
@@ -137,19 +175,26 @@ function SignIn({ agency = { name: "chest", logo: "/logo.png" }, onSuccess } = {
137
175
  if (e?.preventDefault) e.preventDefault();
138
176
  startOAuthFlow("github");
139
177
  };
140
- return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(
141
- ToastContainer,
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(
179
+ "div",
142
180
  {
143
- position: "top-right",
144
- newestOnTop: true,
145
- autoClose: 3e3,
146
- hideProgressBar: false,
147
- closeOnClick: true,
148
- pauseOnHover: true,
149
- draggable: true,
150
- theme: "dark"
151
- }
152
- ), /* @__PURE__ */ React.createElement("div", { style: page }, /* @__PURE__ */ React.createElement("div", { style: cardWrap }, /* @__PURE__ */ React.createElement("div", { style: card }, /* @__PURE__ */ React.createElement("div", { style: cardInner }, /* @__PURE__ */ React.createElement("div", { style: brand }, /* @__PURE__ */ React.createElement("div", { style: brandRow }, /* @__PURE__ */ React.createElement("div", { style: logoWrap }, /* @__PURE__ */ React.createElement(Image, { src: agency.logo, width: 30, height: 30, alt: agency.name, style: logoImgStyle })), /* @__PURE__ */ React.createElement("h1", { style: brandTitle }, agency.name)), /* @__PURE__ */ React.createElement("div", { style: brandText }, /* @__PURE__ */ React.createElement("div", { style: brandLead }, "Sign in to ", agency.name), /* @__PURE__ */ React.createElement("div", { style: brandSub }, "Welcome back! Let's get you signed in."))), /* @__PURE__ */ React.createElement("div", { style: oauthRow }, /* @__PURE__ */ React.createElement(
181
+ key: t.id,
182
+ role: "status",
183
+ style: {
184
+ ...toastBase,
185
+ ...t.type === "error" ? toastError : toastSuccess
186
+ },
187
+ onMouseEnter: () => {
188
+ if (t._timer) clearTimeout(t._timer);
189
+ },
190
+ onMouseLeave: () => {
191
+ const timer = setTimeout(() => removeLocalToast(t.id), 2500);
192
+ setToasts((prev) => prev.map((x) => x.id === t.id ? { ...x, _timer: timer } : x));
193
+ }
194
+ },
195
+ /* @__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(
153
198
  "button",
154
199
  {
155
200
  onClick: handleGoogle,
@@ -326,6 +371,30 @@ const muted = { color: "rgba(255,255,255,0.75)" };
326
371
  const link = { color: "#60a5fa", textDecoration: "none", fontWeight: 600 };
327
372
  const thinDivider = { height: 1, background: "rgba(148,163,184,0.04)", margin: "12px 0" };
328
373
  const securedText = { color: "rgba(255,255,255,0.9)", fontWeight: 600 };
374
+ const toastContainer = {
375
+ position: "fixed",
376
+ top: 18,
377
+ right: 18,
378
+ width: 360,
379
+ maxWidth: "calc(100% - 36px)",
380
+ display: "flex",
381
+ flexDirection: "column",
382
+ gap: 10,
383
+ zIndex: 6e4
384
+ };
385
+ const toastBase = {
386
+ display: "flex",
387
+ gap: 10,
388
+ alignItems: "center",
389
+ padding: "10px 12px",
390
+ borderRadius: 10,
391
+ boxShadow: "0 8px 20px rgba(2,6,23,0.6)",
392
+ color: "#fff",
393
+ fontSize: 13
394
+ };
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 };
329
398
  export {
330
399
  SignIn as default
331
400
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flowlink-auth",
3
- "version": "2.8.6",
3
+ "version": "2.8.8",
4
4
  "description": "Custom auth library",
5
5
  "main": "dist/index.js",
6
6
  "types": "src/index.d.ts",
package/src/SignIn.jsx CHANGED
@@ -3,11 +3,24 @@
3
3
  import React, { useEffect, useRef, useState } from 'react'
4
4
  import Image from 'next/image'
5
5
  import Link from 'next/link'
6
- import { ToastContainer, toast } from 'react-toastify'
7
- import 'react-toastify/dist/ReactToastify.css'
8
6
  import { useAuth } from './provider.js'
9
7
 
10
- export default function SignIn({ agency = { name: 'chest', logo: '/logo.png' }, onSuccess } = {}) {
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
+ } = {}) {
11
24
  const {
12
25
  publishableKey,
13
26
  baseUrl,
@@ -25,19 +38,55 @@ export default function SignIn({ agency = { name: 'chest', logo: '/logo.png' },
25
38
  const [loadingOauth, setLoadingOauth] = useState({ google: false, github: false })
26
39
  const redirectTimer = useRef(null)
27
40
 
28
- // soft-disable pinch-zoom on mobile while mounted (SDK-safe)
41
+ // local (SDK) toasts black background — used only if host doesn't provide onError/onSuccess
42
+ const toastId = useRef(0)
43
+ const [toasts, setToasts] = useState([])
44
+
29
45
  useEffect(() => {
46
+ // Soft-disable pinch-zoom on mobile while mounted (SDK-safe)
30
47
  const meta = document.createElement('meta')
31
48
  meta.name = 'viewport'
32
49
  meta.content = 'width=device-width, initial-scale=1, maximum-scale=1'
33
50
  document.head.appendChild(meta)
51
+
34
52
  return () => {
35
53
  if (redirectTimer.current) clearTimeout(redirectTimer.current)
36
54
  const existing = document.querySelector('meta[name="viewport"]')
37
55
  if (existing && existing.content === meta.content) document.head.removeChild(existing)
56
+ // clear toast timers
57
+ toasts.forEach(t => { if (t._timer) clearTimeout(t._timer) })
38
58
  }
59
+ // eslint-disable-next-line react-hooks/exhaustive-deps
39
60
  }, [])
40
61
 
62
+ // ---------- toast helpers ----------
63
+ function showLocalToast(type, message, ms = 3500) {
64
+ // if host provided onError/onSuccess, prefer that
65
+ if (type === 'error' && typeof onError === 'function') {
66
+ try { onError(message) } catch (_) {}
67
+ return
68
+ }
69
+ if (type === 'success' && typeof onSuccess === 'function') {
70
+ try { onSuccess(message) } catch (_) {}
71
+ // still show local toast for feedback (optional)
72
+ }
73
+
74
+ const id = ++toastId.current
75
+ const t = { id, type, message, _timer: null }
76
+ setToasts(prev => [t, ...prev].slice(0, 6))
77
+ const timer = setTimeout(() => {
78
+ setToasts(prev => prev.filter(x => x.id !== id))
79
+ }, ms)
80
+ t._timer = timer
81
+ }
82
+
83
+ function removeLocalToast(id) {
84
+ setToasts(prev => {
85
+ prev.forEach(t => { if (t.id === id && t._timer) clearTimeout(t._timer) })
86
+ return prev.filter(x => x.id !== id)
87
+ })
88
+ }
89
+
41
90
  if (loadingUser) return null
42
91
 
43
92
  if (user && redirect) {
@@ -50,13 +99,14 @@ export default function SignIn({ agency = { name: 'chest', logo: '/logo.png' },
50
99
  setForm(prev => ({ ...prev, [e.target.id]: e.target.value }))
51
100
  }
52
101
 
102
+ // ---------- submit login ----------
53
103
  async function submit(e) {
54
104
  e.preventDefault()
55
105
  if (loading) return
56
106
  setLoading(true)
57
107
 
58
108
  if (!form.email || !form.password) {
59
- toast.error('Email and password are required', { toastId: 'missing-creds', toastStyle: { background: '#000', color: '#fff' } })
109
+ showLocalToast('error', 'Email and password are required')
60
110
  setLoading(false)
61
111
  return
62
112
  }
@@ -99,12 +149,12 @@ export default function SignIn({ agency = { name: 'chest', logo: '/logo.png' },
99
149
  await fetchMe()
100
150
  }
101
151
 
102
- if (onSuccess) {
152
+ // callback & local success toast (host will receive onSuccess if provided)
153
+ showLocalToast('success', 'Signed in. Redirecting...')
154
+ if (typeof onSuccess === 'function') {
103
155
  try { onSuccess(data) } catch (_) {}
104
156
  }
105
157
 
106
- toast.success('Signed in. Redirecting...', { toastStyle: { background: '#000', color: '#fff' } })
107
-
108
158
  if (redirect) {
109
159
  redirectTimer.current = setTimeout(() => {
110
160
  if (typeof redirectTo === 'function') redirectTo(redirect)
@@ -112,13 +162,15 @@ export default function SignIn({ agency = { name: 'chest', logo: '/logo.png' },
112
162
  }, 250)
113
163
  }
114
164
  } catch (err) {
115
- toast.error(err?.message || 'Network error', { toastStyle: { background: '#000', color: '#fff' } })
165
+ const message = err?.message || 'Network error'
166
+ showLocalToast('error', message)
116
167
  console.error('Signin error:', err)
117
168
  } finally {
118
169
  setLoading(false)
119
170
  }
120
171
  }
121
172
 
173
+ // ---------- OAuth start ----------
122
174
  async function startOAuthFlow(provider) {
123
175
  if (loading || loadingOauth[provider]) return
124
176
  setLoadingOauth(prev => ({ ...prev, [provider]: true }))
@@ -145,10 +197,10 @@ export default function SignIn({ agency = { name: 'chest', logo: '/logo.png' },
145
197
  if (!res.ok) throw new Error(data?.error || `OAuth start failed (${res.status})`)
146
198
  if (!data?.oauthUrl) throw new Error('SDK start did not return oauthUrl')
147
199
 
148
- // navigate to provider (unloads page, so no need to unset loading)
200
+ // navigate to provider (page unloads)
149
201
  if (typeof window !== 'undefined') window.location.href = data.oauthUrl
150
202
  } catch (err) {
151
- toast.error(err?.message || 'OAuth start failed', { toastStyle: { background: '#000', color: '#fff' } })
203
+ showLocalToast('error', err?.message || 'OAuth start failed')
152
204
  console.error('OAuth start error:', err)
153
205
  setLoadingOauth(prev => ({ ...prev, [provider]: false }))
154
206
  }
@@ -157,18 +209,30 @@ export default function SignIn({ agency = { name: 'chest', logo: '/logo.png' },
157
209
  const handleGoogle = (e) => { if (e?.preventDefault) e.preventDefault(); startOAuthFlow('google') }
158
210
  const handleGithub = (e) => { if (e?.preventDefault) e.preventDefault(); startOAuthFlow('github') }
159
211
 
212
+ // ---------- render ----------
160
213
  return (
161
214
  <>
162
- <ToastContainer
163
- position="top-right"
164
- newestOnTop
165
- autoClose={3000}
166
- hideProgressBar={false}
167
- closeOnClick
168
- pauseOnHover
169
- draggable
170
- theme="dark"
171
- />
215
+ {/* Local toast container (black style) */}
216
+ <div style={toastContainer} aria-live="polite" aria-atomic="true">
217
+ {toasts.map(t => (
218
+ <div
219
+ key={t.id}
220
+ role="status"
221
+ style={{
222
+ ...toastBase,
223
+ ...(t.type === 'error' ? toastError : toastSuccess)
224
+ }}
225
+ onMouseEnter={() => { if (t._timer) clearTimeout(t._timer) }}
226
+ onMouseLeave={() => {
227
+ const timer = setTimeout(() => removeLocalToast(t.id), 2500)
228
+ setToasts(prev => prev.map(x => x.id === t.id ? { ...x, _timer: timer } : x))
229
+ }}
230
+ >
231
+ <div style={{ flex: 1 }}>{t.message}</div>
232
+ <button aria-label="Dismiss" onClick={() => removeLocalToast(t.id)} style={toastCloseBtn}>✕</button>
233
+ </div>
234
+ ))}
235
+ </div>
172
236
 
173
237
  <div style={page}>
174
238
  <div style={cardWrap}>
@@ -411,6 +475,7 @@ const submitBtn = {
411
475
  minHeight: 44
412
476
  }
413
477
 
478
+ /* card footer */
414
479
  const cardFooter = {
415
480
  padding: '18px',
416
481
  borderTop: '1px solid rgba(148,163,184,0.03)',
@@ -422,3 +487,29 @@ const muted = { color: 'rgba(255,255,255,0.75)' }
422
487
  const link = { color: '#60a5fa', textDecoration: 'none', fontWeight: 600 }
423
488
  const thinDivider = { height: 1, background: 'rgba(148,163,184,0.04)', margin: '12px 0' }
424
489
  const securedText = { color: 'rgba(255,255,255,0.9)', fontWeight: 600 }
490
+
491
+ /* local toast styles */
492
+ const toastContainer = {
493
+ position: 'fixed',
494
+ top: 18,
495
+ right: 18,
496
+ width: 360,
497
+ maxWidth: 'calc(100% - 36px)',
498
+ display: 'flex',
499
+ flexDirection: 'column',
500
+ gap: 10,
501
+ zIndex: 60000
502
+ }
503
+ const toastBase = {
504
+ display: 'flex',
505
+ gap: 10,
506
+ alignItems: 'center',
507
+ padding: '10px 12px',
508
+ borderRadius: 10,
509
+ boxShadow: '0 8px 20px rgba(2,6,23,0.6)',
510
+ color: '#fff',
511
+ fontSize: 13
512
+ }
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 }