flowlink-auth 2.8.6 → 2.8.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/SignIn.js +89 -20
- package/package.json +1 -1
- package/src/SignIn.jsx +112 -21
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({
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
200
|
+
// navigate to provider (page unloads)
|
|
149
201
|
if (typeof window !== 'undefined') window.location.href = data.oauthUrl
|
|
150
202
|
} catch (err) {
|
|
151
|
-
|
|
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
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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 }
|