@slyxup/ui 0.2.10 → 0.2.11
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/.turbo/turbo-build.log +4 -0
- package/LICENSE +21 -0
- package/README.md +23 -3
- package/dist/components/SignIn/SignIn.d.ts.map +1 -1
- package/dist/components/SignIn/SignIn.js +29 -3
- package/dist/components/SignIn/SignIn.js.map +1 -1
- package/dist/components/SignUp/SignUp.d.ts.map +1 -1
- package/dist/components/SignUp/SignUp.js +10 -2
- package/dist/components/SignUp/SignUp.js.map +1 -1
- package/dist/components/UserProfile/UserProfile.d.ts.map +1 -1
- package/dist/components/UserProfile/UserProfile.js +275 -65
- package/dist/components/UserProfile/UserProfile.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/paddle.d.ts +39 -0
- package/dist/lib/paddle.d.ts.map +1 -0
- package/dist/lib/paddle.js +94 -0
- package/dist/lib/paddle.js.map +1 -0
- package/dist/styles.d.ts +1 -1
- package/dist/styles.d.ts.map +1 -1
- package/dist/styles.js +10 -0
- package/dist/styles.js.map +1 -1
- package/package.json +10 -10
- package/src/components/SignIn/SignIn.tsx +112 -47
- package/src/components/SignUp/SignUp.tsx +38 -1
- package/src/components/UserProfile/UserProfile.tsx +590 -96
- package/src/index.ts +1 -0
- package/src/lib/paddle.ts +131 -0
- package/src/styles.ts +10 -0
|
@@ -23,14 +23,17 @@ export function SignIn({
|
|
|
23
23
|
onForgotPasswordClick,
|
|
24
24
|
}: SignInProps) {
|
|
25
25
|
injectStyles();
|
|
26
|
-
const { signIn, client } = useAuth() as unknown as {
|
|
26
|
+
const { signIn, completeSignIn, client } = useAuth() as unknown as {
|
|
27
27
|
signIn: ReturnType<typeof useAuth>['signIn'];
|
|
28
|
+
completeSignIn: ReturnType<typeof useAuth>['completeSignIn'];
|
|
28
29
|
client: { publishableKey?: string; apiUrl: string };
|
|
29
30
|
};
|
|
30
31
|
const [email, setEmail] = useState('');
|
|
31
32
|
const [password, setPassword] = useState('');
|
|
32
33
|
const [busy, setBusy] = useState(false);
|
|
33
34
|
const [error, setError] = useState<string | null>(null);
|
|
35
|
+
const [challengeToken, setChallengeToken] = useState<string | null>(null);
|
|
36
|
+
const [tfaCode, setTfaCode] = useState('');
|
|
34
37
|
const cardRef = useRef<HTMLDivElement>(null);
|
|
35
38
|
|
|
36
39
|
useEffect(() => {
|
|
@@ -45,7 +48,11 @@ export function SignIn({
|
|
|
45
48
|
setBusy(true);
|
|
46
49
|
setError(null);
|
|
47
50
|
try {
|
|
48
|
-
await signIn({ email, password });
|
|
51
|
+
const res = await signIn({ email, password });
|
|
52
|
+
if (res && 'challengeToken' in res) {
|
|
53
|
+
setChallengeToken(res.challengeToken);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
49
56
|
onSuccess?.();
|
|
50
57
|
} catch (err) {
|
|
51
58
|
setError(
|
|
@@ -58,6 +65,23 @@ export function SignIn({
|
|
|
58
65
|
}
|
|
59
66
|
}
|
|
60
67
|
|
|
68
|
+
async function onSubmit2FA(e: FormEvent) {
|
|
69
|
+
e.preventDefault();
|
|
70
|
+
if (!challengeToken) return;
|
|
71
|
+
setBusy(true);
|
|
72
|
+
setError(null);
|
|
73
|
+
try {
|
|
74
|
+
await completeSignIn({ challengeToken, code: tfaCode.trim() });
|
|
75
|
+
onSuccess?.();
|
|
76
|
+
} catch (err) {
|
|
77
|
+
setError(
|
|
78
|
+
err instanceof SlyxupError ? err.message : 'Invalid code. Try again.'
|
|
79
|
+
);
|
|
80
|
+
} finally {
|
|
81
|
+
setBusy(false);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
61
85
|
function oauth(provider: 'google' | 'github') {
|
|
62
86
|
const redirect = encodeURIComponent(window.location.href);
|
|
63
87
|
window.location.href = `${client.apiUrl}/v1/oauth/${provider}?redirect_url=${redirect}`;
|
|
@@ -113,53 +137,94 @@ export function SignIn({
|
|
|
113
137
|
</p>
|
|
114
138
|
)}
|
|
115
139
|
|
|
116
|
-
|
|
117
|
-
<
|
|
118
|
-
<
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
140
|
+
{challengeToken ? (
|
|
141
|
+
<form onSubmit={onSubmit2FA} noValidate={false}>
|
|
142
|
+
<div className="slx-field">
|
|
143
|
+
<label className="slx-label" htmlFor="slx-signin-2fa">
|
|
144
|
+
Authenticator code
|
|
145
|
+
</label>
|
|
146
|
+
<input
|
|
147
|
+
id="slx-signin-2fa"
|
|
148
|
+
className="slx-input"
|
|
149
|
+
type="text"
|
|
150
|
+
inputMode="numeric"
|
|
151
|
+
autoComplete="one-time-code"
|
|
152
|
+
maxLength={6}
|
|
153
|
+
pattern="[0-9]*"
|
|
154
|
+
placeholder="000000"
|
|
155
|
+
value={tfaCode}
|
|
156
|
+
onChange={(e) => setTfaCode(e.target.value.replace(/\D/g, ''))}
|
|
157
|
+
required
|
|
158
|
+
/>
|
|
159
|
+
<p className="slx-hint">
|
|
160
|
+
Enter the 6-digit code from your authenticator app.
|
|
161
|
+
</p>
|
|
162
|
+
</div>
|
|
163
|
+
<button className="slx-btn" type="submit" disabled={busy}>
|
|
164
|
+
{busy && <span className="slx-spinner" aria-hidden="true" />}
|
|
165
|
+
{busy ? 'Verifying…' : 'Verify code'}
|
|
166
|
+
</button>
|
|
167
|
+
<button
|
|
168
|
+
type="button"
|
|
169
|
+
className="slx-link"
|
|
170
|
+
style={{ marginTop: 8 }}
|
|
171
|
+
onClick={() => {
|
|
172
|
+
setChallengeToken(null);
|
|
173
|
+
setTfaCode('');
|
|
174
|
+
}}
|
|
175
|
+
>
|
|
176
|
+
← Back to sign in
|
|
177
|
+
</button>
|
|
178
|
+
</form>
|
|
179
|
+
) : (
|
|
180
|
+
<form onSubmit={onSubmit} noValidate={false}>
|
|
181
|
+
<div className="slx-field">
|
|
182
|
+
<label className="slx-label" htmlFor="slx-signin-email">
|
|
183
|
+
Email <span className="slx-hint">or username</span>
|
|
136
184
|
</label>
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
185
|
+
<input
|
|
186
|
+
id="slx-signin-email"
|
|
187
|
+
className="slx-input"
|
|
188
|
+
type="text"
|
|
189
|
+
autoComplete="username"
|
|
190
|
+
placeholder="you@example.com or yourname"
|
|
191
|
+
value={email}
|
|
192
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
193
|
+
required
|
|
194
|
+
/>
|
|
195
|
+
</div>
|
|
196
|
+
<div className="slx-field">
|
|
197
|
+
<div className="slx-row">
|
|
198
|
+
<label className="slx-label" htmlFor="slx-signin-password">
|
|
199
|
+
Password
|
|
200
|
+
</label>
|
|
201
|
+
{onForgotPasswordClick && (
|
|
202
|
+
<button
|
|
203
|
+
type="button"
|
|
204
|
+
className="slx-link slx-forgot"
|
|
205
|
+
onClick={onForgotPasswordClick}
|
|
206
|
+
>
|
|
207
|
+
Forgot password?
|
|
208
|
+
</button>
|
|
209
|
+
)}
|
|
210
|
+
</div>
|
|
211
|
+
<input
|
|
212
|
+
id="slx-signin-password"
|
|
213
|
+
className="slx-input"
|
|
214
|
+
type="password"
|
|
215
|
+
autoComplete="current-password"
|
|
216
|
+
placeholder="••••••••"
|
|
217
|
+
value={password}
|
|
218
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
219
|
+
required
|
|
220
|
+
/>
|
|
146
221
|
</div>
|
|
147
|
-
<
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
value={password}
|
|
154
|
-
onChange={(e) => setPassword(e.target.value)}
|
|
155
|
-
required
|
|
156
|
-
/>
|
|
157
|
-
</div>
|
|
158
|
-
<button className="slx-btn" type="submit" disabled={busy}>
|
|
159
|
-
{busy && <span className="slx-spinner" aria-hidden="true" />}
|
|
160
|
-
{busy ? 'Signing in…' : 'Sign in'}
|
|
161
|
-
</button>
|
|
162
|
-
</form>
|
|
222
|
+
<button className="slx-btn" type="submit" disabled={busy}>
|
|
223
|
+
{busy && <span className="slx-spinner" aria-hidden="true" />}
|
|
224
|
+
{busy ? 'Signing in…' : 'Sign in'}
|
|
225
|
+
</button>
|
|
226
|
+
</form>
|
|
227
|
+
)}
|
|
163
228
|
|
|
164
229
|
{onSignUpClick && (
|
|
165
230
|
<p className="slx-footer">
|
|
@@ -22,10 +22,12 @@ export function SignUp({
|
|
|
22
22
|
client: { publishableKey?: string; apiUrl: string };
|
|
23
23
|
};
|
|
24
24
|
const [firstName, setFirstName] = useState('');
|
|
25
|
+
const [username, setUsername] = useState('');
|
|
25
26
|
const [email, setEmail] = useState('');
|
|
26
27
|
const [password, setPassword] = useState('');
|
|
27
28
|
const [busy, setBusy] = useState(false);
|
|
28
29
|
const [error, setError] = useState<string | null>(null);
|
|
30
|
+
const [successEmail, setSuccessEmail] = useState<string | null>(null);
|
|
29
31
|
const cardRef = useRef<HTMLDivElement>(null);
|
|
30
32
|
|
|
31
33
|
useEffect(() => {
|
|
@@ -40,7 +42,13 @@ export function SignUp({
|
|
|
40
42
|
setBusy(true);
|
|
41
43
|
setError(null);
|
|
42
44
|
try {
|
|
43
|
-
await signUp({
|
|
45
|
+
await signUp({
|
|
46
|
+
email,
|
|
47
|
+
password,
|
|
48
|
+
firstName: firstName || undefined,
|
|
49
|
+
username: username || undefined,
|
|
50
|
+
});
|
|
51
|
+
setSuccessEmail(email);
|
|
44
52
|
onSuccess?.();
|
|
45
53
|
} catch (err) {
|
|
46
54
|
setError(
|
|
@@ -106,6 +114,18 @@ export function SignUp({
|
|
|
106
114
|
</p>
|
|
107
115
|
)}
|
|
108
116
|
|
|
117
|
+
{successEmail && (
|
|
118
|
+
<p
|
|
119
|
+
className="slx-success-text"
|
|
120
|
+
aria-live="polite"
|
|
121
|
+
style={{ color: 'var(--slx-success)' }}
|
|
122
|
+
>
|
|
123
|
+
Account created! We sent a verification link to{' '}
|
|
124
|
+
<strong>{successEmail}</strong>. Check your inbox to verify and sign
|
|
125
|
+
in.
|
|
126
|
+
</p>
|
|
127
|
+
)}
|
|
128
|
+
|
|
109
129
|
<form onSubmit={onSubmit}>
|
|
110
130
|
<div className="slx-field">
|
|
111
131
|
<label className="slx-label" htmlFor="slx-signup-name">
|
|
@@ -121,6 +141,23 @@ export function SignUp({
|
|
|
121
141
|
onChange={(e) => setFirstName(e.target.value)}
|
|
122
142
|
/>
|
|
123
143
|
</div>
|
|
144
|
+
<div className="slx-field">
|
|
145
|
+
<label className="slx-label" htmlFor="slx-signup-username">
|
|
146
|
+
Username
|
|
147
|
+
</label>
|
|
148
|
+
<input
|
|
149
|
+
id="slx-signup-username"
|
|
150
|
+
className="slx-input"
|
|
151
|
+
type="text"
|
|
152
|
+
autoComplete="username"
|
|
153
|
+
placeholder="ada"
|
|
154
|
+
value={username}
|
|
155
|
+
onChange={(e) => setUsername(e.target.value)}
|
|
156
|
+
/>
|
|
157
|
+
<p className="slx-hint">
|
|
158
|
+
Optional — lets you sign in with a username instead of your email.
|
|
159
|
+
</p>
|
|
160
|
+
</div>
|
|
124
161
|
<div className="slx-field">
|
|
125
162
|
<label className="slx-label" htmlFor="slx-signup-email">
|
|
126
163
|
Email
|