@pixygon/auth 1.0.0

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.
@@ -0,0 +1,410 @@
1
+ /**
2
+ * @pixygon/auth - Forgot Password Form Component
3
+ * Password reset request form for Pixygon Account
4
+ */
5
+
6
+ import { useState, useCallback, type FormEvent } from 'react';
7
+ import { useAuth } from '../hooks';
8
+ import type { ForgotPasswordFormProps, AuthError } from '../types';
9
+
10
+ export function ForgotPasswordForm({
11
+ onSuccess,
12
+ onError,
13
+ onNavigateLogin,
14
+ showBranding = true,
15
+ className = '',
16
+ }: ForgotPasswordFormProps) {
17
+ const { forgotPassword, isLoading, error } = useAuth();
18
+
19
+ const [email, setEmail] = useState('');
20
+ const [localError, setLocalError] = useState<string | null>(null);
21
+ const [submitted, setSubmitted] = useState(false);
22
+
23
+ const handleSubmit = useCallback(
24
+ async (e: FormEvent) => {
25
+ e.preventDefault();
26
+ setLocalError(null);
27
+
28
+ if (!email.trim()) {
29
+ setLocalError('Please enter your email address');
30
+ return;
31
+ }
32
+
33
+ if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
34
+ setLocalError('Please enter a valid email address');
35
+ return;
36
+ }
37
+
38
+ try {
39
+ await forgotPassword({ email: email.trim().toLowerCase() });
40
+ setSubmitted(true);
41
+ onSuccess?.();
42
+ } catch (err) {
43
+ const authError = err as AuthError;
44
+ onError?.(authError);
45
+ }
46
+ },
47
+ [email, forgotPassword, onSuccess, onError]
48
+ );
49
+
50
+ const displayError = localError || error?.message;
51
+
52
+ if (submitted) {
53
+ return (
54
+ <div className={`pixygon-auth-container ${className}`}>
55
+ <style>{`
56
+ .pixygon-auth-container {
57
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
58
+ background: #0f0f0f;
59
+ color: #ffffff;
60
+ padding: 2rem;
61
+ border-radius: 1rem;
62
+ max-width: 400px;
63
+ width: 100%;
64
+ margin: 0 auto;
65
+ }
66
+
67
+ .pixygon-auth-header {
68
+ text-align: center;
69
+ margin-bottom: 2rem;
70
+ }
71
+
72
+ .pixygon-auth-success-icon {
73
+ width: 64px;
74
+ height: 64px;
75
+ margin: 0 auto 1rem;
76
+ display: block;
77
+ }
78
+
79
+ .pixygon-auth-title {
80
+ font-size: 1.5rem;
81
+ font-weight: 600;
82
+ margin: 0 0 0.5rem;
83
+ }
84
+
85
+ .pixygon-auth-subtitle {
86
+ font-size: 0.875rem;
87
+ color: #a3a3a3;
88
+ margin: 0;
89
+ line-height: 1.5;
90
+ }
91
+
92
+ .pixygon-auth-link {
93
+ color: #6366f1;
94
+ text-decoration: none;
95
+ font-size: 0.875rem;
96
+ cursor: pointer;
97
+ background: none;
98
+ border: none;
99
+ padding: 0;
100
+ font-family: inherit;
101
+ }
102
+
103
+ .pixygon-auth-link:hover {
104
+ color: #818cf8;
105
+ text-decoration: underline;
106
+ }
107
+
108
+ .pixygon-auth-footer {
109
+ text-align: center;
110
+ margin-top: 1.5rem;
111
+ font-size: 0.875rem;
112
+ color: #a3a3a3;
113
+ }
114
+
115
+ .pixygon-auth-branding {
116
+ display: flex;
117
+ align-items: center;
118
+ justify-content: center;
119
+ gap: 0.5rem;
120
+ margin-top: 1.5rem;
121
+ font-size: 0.75rem;
122
+ color: #737373;
123
+ }
124
+ `}</style>
125
+
126
+ <div className="pixygon-auth-header">
127
+ <svg
128
+ className="pixygon-auth-success-icon"
129
+ viewBox="0 0 100 100"
130
+ fill="none"
131
+ xmlns="http://www.w3.org/2000/svg"
132
+ >
133
+ <circle cx="50" cy="50" r="45" fill="#22c55e" />
134
+ <path
135
+ d="M30 50L45 65L70 35"
136
+ stroke="white"
137
+ strokeWidth="6"
138
+ strokeLinecap="round"
139
+ strokeLinejoin="round"
140
+ />
141
+ </svg>
142
+ <h1 className="pixygon-auth-title">Check Your Email</h1>
143
+ <p className="pixygon-auth-subtitle">
144
+ We've sent password reset instructions to{' '}
145
+ <strong>{email}</strong>. Please check your inbox.
146
+ </p>
147
+ </div>
148
+
149
+ {onNavigateLogin && (
150
+ <div className="pixygon-auth-footer">
151
+ <button
152
+ type="button"
153
+ className="pixygon-auth-link"
154
+ onClick={onNavigateLogin}
155
+ >
156
+ Back to sign in
157
+ </button>
158
+ </div>
159
+ )}
160
+
161
+ {showBranding && (
162
+ <div className="pixygon-auth-branding">
163
+ Secured by Pixygon Account
164
+ </div>
165
+ )}
166
+ </div>
167
+ );
168
+ }
169
+
170
+ return (
171
+ <div className={`pixygon-auth-container ${className}`}>
172
+ <style>{`
173
+ .pixygon-auth-container {
174
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
175
+ background: #0f0f0f;
176
+ color: #ffffff;
177
+ padding: 2rem;
178
+ border-radius: 1rem;
179
+ max-width: 400px;
180
+ width: 100%;
181
+ margin: 0 auto;
182
+ }
183
+
184
+ .pixygon-auth-header {
185
+ text-align: center;
186
+ margin-bottom: 2rem;
187
+ }
188
+
189
+ .pixygon-auth-logo {
190
+ width: 64px;
191
+ height: 64px;
192
+ margin: 0 auto 1rem;
193
+ display: block;
194
+ }
195
+
196
+ .pixygon-auth-title {
197
+ font-size: 1.5rem;
198
+ font-weight: 600;
199
+ margin: 0 0 0.5rem;
200
+ }
201
+
202
+ .pixygon-auth-subtitle {
203
+ font-size: 0.875rem;
204
+ color: #a3a3a3;
205
+ margin: 0;
206
+ }
207
+
208
+ .pixygon-auth-form {
209
+ display: flex;
210
+ flex-direction: column;
211
+ gap: 1rem;
212
+ }
213
+
214
+ .pixygon-auth-input-group {
215
+ display: flex;
216
+ flex-direction: column;
217
+ gap: 0.375rem;
218
+ }
219
+
220
+ .pixygon-auth-label {
221
+ font-size: 0.875rem;
222
+ font-weight: 500;
223
+ color: #a3a3a3;
224
+ }
225
+
226
+ .pixygon-auth-input {
227
+ background: #262626;
228
+ border: 1px solid #404040;
229
+ border-radius: 0.5rem;
230
+ padding: 0.75rem 1rem;
231
+ font-size: 1rem;
232
+ color: #ffffff;
233
+ outline: none;
234
+ transition: border-color 0.2s, box-shadow 0.2s;
235
+ }
236
+
237
+ .pixygon-auth-input:focus {
238
+ border-color: #6366f1;
239
+ box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
240
+ }
241
+
242
+ .pixygon-auth-input.error {
243
+ border-color: #ef4444;
244
+ }
245
+
246
+ .pixygon-auth-button {
247
+ background: #6366f1;
248
+ color: white;
249
+ border: none;
250
+ border-radius: 0.5rem;
251
+ padding: 0.875rem 1.5rem;
252
+ font-size: 1rem;
253
+ font-weight: 600;
254
+ cursor: pointer;
255
+ transition: background 0.2s;
256
+ display: flex;
257
+ align-items: center;
258
+ justify-content: center;
259
+ gap: 0.5rem;
260
+ margin-top: 0.5rem;
261
+ }
262
+
263
+ .pixygon-auth-button:hover:not(:disabled) {
264
+ background: #4f46e5;
265
+ }
266
+
267
+ .pixygon-auth-button:disabled {
268
+ opacity: 0.6;
269
+ cursor: not-allowed;
270
+ }
271
+
272
+ .pixygon-auth-error {
273
+ background: rgba(239, 68, 68, 0.1);
274
+ border: 1px solid #ef4444;
275
+ border-radius: 0.5rem;
276
+ padding: 0.75rem 1rem;
277
+ color: #fca5a5;
278
+ font-size: 0.875rem;
279
+ }
280
+
281
+ .pixygon-auth-link {
282
+ color: #6366f1;
283
+ text-decoration: none;
284
+ font-size: 0.875rem;
285
+ cursor: pointer;
286
+ background: none;
287
+ border: none;
288
+ padding: 0;
289
+ font-family: inherit;
290
+ }
291
+
292
+ .pixygon-auth-link:hover {
293
+ color: #818cf8;
294
+ text-decoration: underline;
295
+ }
296
+
297
+ .pixygon-auth-footer {
298
+ text-align: center;
299
+ margin-top: 1.5rem;
300
+ font-size: 0.875rem;
301
+ color: #a3a3a3;
302
+ }
303
+
304
+ .pixygon-auth-branding {
305
+ display: flex;
306
+ align-items: center;
307
+ justify-content: center;
308
+ gap: 0.5rem;
309
+ margin-top: 1.5rem;
310
+ font-size: 0.75rem;
311
+ color: #737373;
312
+ }
313
+
314
+ .pixygon-auth-spinner {
315
+ width: 20px;
316
+ height: 20px;
317
+ border: 2px solid transparent;
318
+ border-top-color: currentColor;
319
+ border-radius: 50%;
320
+ animation: pixygon-spin 0.8s linear infinite;
321
+ }
322
+
323
+ @keyframes pixygon-spin {
324
+ to { transform: rotate(360deg); }
325
+ }
326
+ `}</style>
327
+
328
+ <div className="pixygon-auth-header">
329
+ <svg
330
+ className="pixygon-auth-logo"
331
+ viewBox="0 0 100 100"
332
+ fill="none"
333
+ xmlns="http://www.w3.org/2000/svg"
334
+ >
335
+ <circle cx="50" cy="50" r="45" fill="#6366f1" />
336
+ <path
337
+ d="M30 45L45 30L70 55L55 70L30 45Z"
338
+ fill="white"
339
+ fillOpacity="0.9"
340
+ />
341
+ <path
342
+ d="M35 55L50 70L45 75L30 60L35 55Z"
343
+ fill="white"
344
+ fillOpacity="0.7"
345
+ />
346
+ </svg>
347
+ <h1 className="pixygon-auth-title">Reset Password</h1>
348
+ <p className="pixygon-auth-subtitle">
349
+ Enter your email to receive reset instructions
350
+ </p>
351
+ </div>
352
+
353
+ <form className="pixygon-auth-form" onSubmit={handleSubmit}>
354
+ {displayError && (
355
+ <div className="pixygon-auth-error">{displayError}</div>
356
+ )}
357
+
358
+ <div className="pixygon-auth-input-group">
359
+ <label className="pixygon-auth-label" htmlFor="pixygon-forgot-email">
360
+ Email
361
+ </label>
362
+ <input
363
+ id="pixygon-forgot-email"
364
+ className={`pixygon-auth-input ${displayError ? 'error' : ''}`}
365
+ type="email"
366
+ value={email}
367
+ onChange={(e) => setEmail(e.target.value)}
368
+ placeholder="Enter your email"
369
+ autoComplete="email"
370
+ disabled={isLoading}
371
+ />
372
+ </div>
373
+
374
+ <button
375
+ type="submit"
376
+ className="pixygon-auth-button"
377
+ disabled={isLoading}
378
+ >
379
+ {isLoading ? (
380
+ <>
381
+ <div className="pixygon-auth-spinner" />
382
+ Sending...
383
+ </>
384
+ ) : (
385
+ 'Send Reset Link'
386
+ )}
387
+ </button>
388
+ </form>
389
+
390
+ {onNavigateLogin && (
391
+ <div className="pixygon-auth-footer">
392
+ Remember your password?{' '}
393
+ <button
394
+ type="button"
395
+ className="pixygon-auth-link"
396
+ onClick={onNavigateLogin}
397
+ >
398
+ Sign in
399
+ </button>
400
+ </div>
401
+ )}
402
+
403
+ {showBranding && (
404
+ <div className="pixygon-auth-branding">
405
+ Secured by Pixygon Account
406
+ </div>
407
+ )}
408
+ </div>
409
+ );
410
+ }