nextjs-auth-module 1.1.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,340 @@
1
+ 'use client';
2
+
3
+ import { useState } from 'react';
4
+ import { useAuth } from '@/context/AuthContext';
5
+ import Link from 'next/link';
6
+ import { useRouter } from 'next/navigation';
7
+ import { EyeIcon, EyeSlashIcon, UserIcon, EnvelopeIcon, LockClosedIcon, CheckBadgeIcon, ArrowRightIcon } from '@heroicons/react/24/outline';
8
+ import { CheckCircleIcon } from '@heroicons/react/24/solid';
9
+
10
+ export default function RegisterPage() {
11
+ const [formData, setFormData] = useState({
12
+ name: '',
13
+ email: '',
14
+ password: '',
15
+ confirmPassword: '',
16
+ });
17
+ const [showPassword, setShowPassword] = useState(false);
18
+ const [showConfirmPassword, setShowConfirmPassword] = useState(false);
19
+ const [error, setError] = useState('');
20
+ const [loading, setLoading] = useState(false);
21
+ const [agreeToTerms, setAgreeToTerms] = useState(false);
22
+ const { register } = useAuth();
23
+ const router = useRouter();
24
+
25
+ // Password strength checker
26
+ const getPasswordStrength = () => {
27
+ const password = formData.password;
28
+ let strength = 0;
29
+ if (password.length >= 6) strength++;
30
+ if (password.match(/[a-z]+/)) strength++;
31
+ if (password.match(/[A-Z]+/)) strength++;
32
+ if (password.match(/[0-9]+/)) strength++;
33
+ if (password.match(/[$@#&!]+/)) strength++;
34
+ return strength;
35
+ };
36
+
37
+ const passwordStrength = getPasswordStrength();
38
+ const getStrengthText = () => {
39
+ if (passwordStrength <= 2) return { text: 'Weak', color: 'text-red-500', bg: 'bg-red-100' };
40
+ if (passwordStrength <= 3) return { text: 'Medium', color: 'text-yellow-500', bg: 'bg-yellow-100' };
41
+ if (passwordStrength <= 4) return { text: 'Strong', color: 'text-green-500', bg: 'bg-green-100' };
42
+ return { text: 'Very Strong', color: 'text-indigo-500', bg: 'bg-indigo-100' };
43
+ };
44
+
45
+ const handleChange = (e) => {
46
+ setFormData({
47
+ ...formData,
48
+ [e.target.name]: e.target.value,
49
+ });
50
+ };
51
+
52
+ const handleSubmit = async (e) => {
53
+ e.preventDefault();
54
+ setError('');
55
+
56
+ if (!agreeToTerms) {
57
+ setError('Please agree to the terms and conditions');
58
+ return;
59
+ }
60
+
61
+ if (formData.password !== formData.confirmPassword) {
62
+ setError('Passwords do not match');
63
+ return;
64
+ }
65
+
66
+ if (formData.password.length < 6) {
67
+ setError('Password must be at least 6 characters');
68
+ return;
69
+ }
70
+
71
+ setLoading(true);
72
+
73
+ try {
74
+ await register(formData.name, formData.email, formData.password);
75
+ router.push('/');
76
+ } catch (err) {
77
+ setError(err.message);
78
+ } finally {
79
+ setLoading(false);
80
+ }
81
+ };
82
+
83
+ return (
84
+ <div className="min-h-screen bg-gradient-to-br from-slate-50 via-white to-indigo-50 flex items-center justify-center px-4 py-8 relative overflow-hidden">
85
+ {/* Animated Background */}
86
+ <div className="absolute inset-0 overflow-hidden">
87
+ <div className="absolute -top-40 -right-32 w-80 h-80 bg-indigo-300 rounded-full mix-blend-multiply filter blur-3xl opacity-20 animate-blob"></div>
88
+ <div className="absolute -bottom-40 -left-32 w-80 h-80 bg-purple-300 rounded-full mix-blend-multiply filter blur-3xl opacity-20 animate-blob animation-delay-2000"></div>
89
+ <div className="absolute top-40 left-1/2 w-80 h-80 bg-pink-300 rounded-full mix-blend-multiply filter blur-3xl opacity-20 animate-blob animation-delay-4000"></div>
90
+ </div>
91
+
92
+ <div className="max-w-md w-full relative z-10">
93
+ {/* Logo/Brand */}
94
+ <div className="text-center mb-8">
95
+ {/* <div className="inline-flex items-center justify-center w-20 h-20 bg-gradient-to-br from-indigo-600 via-purple-600 to-pink-600 rounded-2xl mb-5 shadow-2xl transform hover:scale-105 transition-transform duration-300">
96
+ <span className="text-white text-3xl font-bold">⚡</span>
97
+ </div> */}
98
+ <h2 className="text-4xl font-bold text-gray-900 mb-3 bg-gradient-to-r from-indigo-600 to-purple-600 bg-clip-text text-transparent">
99
+ Create Account
100
+ </h2>
101
+ <p className="text-gray-600 text-lg">Join us and start your journey 🚀</p>
102
+ </div>
103
+
104
+ {/* Registration Form */}
105
+ <div className="bg-white/90 backdrop-blur-sm rounded-3xl shadow-2xl p-8 border border-gray-100">
106
+ <form onSubmit={handleSubmit} className="space-y-5">
107
+ {error && (
108
+ <div className="bg-red-50 border-l-4 border-red-500 text-red-700 px-4 py-3 rounded-lg text-sm flex items-start gap-3">
109
+ <div className="text-red-500">⚠️</div>
110
+ <div>{error}</div>
111
+ </div>
112
+ )}
113
+
114
+ {/* Full Name Field */}
115
+ <div className="group">
116
+ <label className="block text-sm font-semibold text-gray-700 mb-2">
117
+ Full Name
118
+ </label>
119
+ <div className="relative">
120
+ <div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
121
+ <UserIcon className="h-5 w-5 text-gray-400 group-focus-within:text-indigo-500 transition-colors" />
122
+ </div>
123
+ <input
124
+ type="text"
125
+ name="name"
126
+ value={formData.name}
127
+ onChange={handleChange}
128
+ className="w-full pl-10 pr-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-indigo-500 focus:border-transparent transition-all text-gray-700 placeholder-gray-400"
129
+ placeholder="John Smith"
130
+ />
131
+ </div>
132
+ </div>
133
+
134
+ {/* Email Field */}
135
+ <div className="group">
136
+ <label className="block text-sm font-semibold text-gray-700 mb-2">
137
+ Email Address
138
+ </label>
139
+ <div className="relative">
140
+ <div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
141
+ <EnvelopeIcon className="h-5 w-5 text-gray-400 group-focus-within:text-indigo-500 transition-colors" />
142
+ </div>
143
+ <input
144
+ type="email"
145
+ name="email"
146
+ value={formData.email}
147
+ onChange={handleChange}
148
+ className="w-full pl-10 pr-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-indigo-500 focus:border-transparent transition-all text-gray-700 placeholder-gray-400"
149
+ placeholder="you@example.com"
150
+ required
151
+ />
152
+ </div>
153
+ </div>
154
+
155
+ {/* Password Field */}
156
+ <div className="group">
157
+ <label className="block text-sm font-semibold text-gray-700 mb-2">
158
+ Password
159
+ </label>
160
+ <div className="relative">
161
+ <div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
162
+ <LockClosedIcon className="h-5 w-5 text-gray-400 group-focus-within:text-indigo-500 transition-colors" />
163
+ </div>
164
+ <input
165
+ type={showPassword ? 'text' : 'password'}
166
+ name="password"
167
+ value={formData.password}
168
+ onChange={handleChange}
169
+ className="w-full pl-10 pr-12 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-indigo-500 focus:border-transparent transition-all text-gray-700 placeholder-gray-400"
170
+ placeholder="Create a strong password"
171
+ required
172
+ />
173
+ <button
174
+ type="button"
175
+ onClick={() => setShowPassword(!showPassword)}
176
+ className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-500 hover:text-indigo-600 transition-colors"
177
+ >
178
+ {showPassword ? <EyeSlashIcon className="w-5 h-5" /> : <EyeIcon className="w-5 h-5" />}
179
+ </button>
180
+ </div>
181
+
182
+ {/* Password Strength Indicator */}
183
+ {formData.password && (
184
+ <div className="mt-2 space-y-1">
185
+ <div className="flex gap-1">
186
+ {[...Array(5)].map((_, i) => (
187
+ <div
188
+ key={i}
189
+ className={`h-1 flex-1 rounded-full transition-all duration-300 ${
190
+ i < passwordStrength
191
+ ? passwordStrength <= 2
192
+ ? 'bg-red-500'
193
+ : passwordStrength <= 3
194
+ ? 'bg-yellow-500'
195
+ : 'bg-green-500'
196
+ : 'bg-gray-200'
197
+ }`}
198
+ />
199
+ ))}
200
+ </div>
201
+ <div className="flex justify-between items-center">
202
+ <p className={`text-xs font-medium ${getStrengthText().color}`}>
203
+ {getStrengthText().text} Password
204
+ </p>
205
+ <p className="text-xs text-gray-500">
206
+ {formData.password.length} characters
207
+ </p>
208
+ </div>
209
+ </div>
210
+ )}
211
+ </div>
212
+
213
+ {/* Confirm Password Field */}
214
+ <div className="group">
215
+ <label className="block text-sm font-semibold text-gray-700 mb-2">
216
+ Confirm Password
217
+ </label>
218
+ <div className="relative">
219
+ <div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
220
+ <CheckBadgeIcon className="h-5 w-5 text-gray-400 group-focus-within:text-indigo-500 transition-colors" />
221
+ </div>
222
+ <input
223
+ type={showConfirmPassword ? 'text' : 'password'}
224
+ name="confirmPassword"
225
+ value={formData.confirmPassword}
226
+ onChange={handleChange}
227
+ className="w-full pl-10 pr-12 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-indigo-500 focus:border-transparent transition-all text-gray-700 placeholder-gray-400"
228
+ placeholder="Confirm your password"
229
+ required
230
+ />
231
+ <button
232
+ type="button"
233
+ onClick={() => setShowConfirmPassword(!showConfirmPassword)}
234
+ className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-500 hover:text-indigo-600 transition-colors"
235
+ >
236
+ {showConfirmPassword ? <EyeSlashIcon className="w-5 h-5" /> : <EyeIcon className="w-5 h-5" />}
237
+ </button>
238
+ </div>
239
+ {formData.confirmPassword && formData.password !== formData.confirmPassword && (
240
+ <p className="text-xs text-red-500 mt-1">Passwords do not match</p>
241
+ )}
242
+ {formData.confirmPassword && formData.password === formData.confirmPassword && formData.password && (
243
+ <p className="text-xs text-green-500 mt-1 flex items-center gap-1">
244
+ <CheckCircleIcon className="w-3 h-3" /> Passwords match
245
+ </p>
246
+ )}
247
+ </div>
248
+
249
+ {/* Terms and Conditions */}
250
+ <div className="flex items-start gap-3">
251
+ <input
252
+ type="checkbox"
253
+ id="terms"
254
+ checked={agreeToTerms}
255
+ onChange={(e) => setAgreeToTerms(e.target.checked)}
256
+ className="mt-1 w-4 h-4 text-indigo-600 border-gray-300 rounded focus:ring-indigo-500"
257
+ />
258
+ <label htmlFor="terms" className="text-sm text-gray-600">
259
+ I agree to the{' '}
260
+ <Link href="/terms" className="text-indigo-600 hover:text-indigo-700 font-medium">
261
+ Terms of Service
262
+ </Link>{' '}
263
+ and{' '}
264
+ <Link href="/privacy" className="text-indigo-600 hover:text-indigo-700 font-medium">
265
+ Privacy Policy
266
+ </Link>
267
+ </label>
268
+ </div>
269
+
270
+ {/* Submit Button */}
271
+ <button
272
+ type="submit"
273
+ disabled={loading}
274
+ className="group relative w-full py-3.5 bg-gradient-to-r from-indigo-600 via-purple-600 to-pink-600 text-white rounded-xl font-semibold hover:from-indigo-700 hover:via-purple-700 hover:to-pink-700 transition-all duration-200 shadow-lg hover:shadow-xl disabled:opacity-50 disabled:cursor-not-allowed overflow-hidden"
275
+ >
276
+ <span className="relative z-10 flex items-center justify-center gap-2">
277
+ {loading ? (
278
+ <>
279
+ <svg className="animate-spin h-5 w-5" viewBox="0 0 24 24">
280
+ <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" fill="none" />
281
+ <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" />
282
+ </svg>
283
+ Creating account...
284
+ </>
285
+ ) : (
286
+ <>
287
+ Create Account
288
+ <ArrowRightIcon className="w-5 h-5 group-hover:translate-x-1 transition-transform" />
289
+ </>
290
+ )}
291
+ </span>
292
+ </button>
293
+ </form>
294
+
295
+ {/* Divider */}
296
+ <div className="relative my-6">
297
+ <div className="absolute inset-0 flex items-center">
298
+ <div className="w-full border-t border-gray-200"></div>
299
+ </div>
300
+ <div className="relative flex justify-center text-sm">
301
+ <span className="px-4 bg-white text-gray-500">Or</span>
302
+ </div>
303
+ </div>
304
+
305
+ {/* Sign In Link */}
306
+ <div className="text-center">
307
+ <p className="text-sm text-gray-600">
308
+ Already have an account?{' '}
309
+ <Link
310
+ href="/login"
311
+ className="text-indigo-600 hover:text-indigo-700 font-semibold hover:underline transition-all"
312
+ >
313
+ Sign in
314
+ </Link>
315
+ </p>
316
+ </div>
317
+
318
+ </div>
319
+ </div>
320
+
321
+ <style jsx>{`
322
+ @keyframes blob {
323
+ 0% { transform: translate(0px, 0px) scale(1); }
324
+ 33% { transform: translate(30px, -50px) scale(1.1); }
325
+ 66% { transform: translate(-20px, 20px) scale(0.9); }
326
+ 100% { transform: translate(0px, 0px) scale(1); }
327
+ }
328
+ .animate-blob {
329
+ animation: blob 7s infinite;
330
+ }
331
+ .animation-delay-2000 {
332
+ animation-delay: 2s;
333
+ }
334
+ .animation-delay-4000 {
335
+ animation-delay: 4s;
336
+ }
337
+ `}</style>
338
+ </div>
339
+ );
340
+ }
@@ -0,0 +1,179 @@
1
+ 'use client';
2
+
3
+ import { useState, useEffect, Suspense } from 'react';
4
+ import { useSearchParams, useRouter } from 'next/navigation';
5
+ import Link from 'next/link';
6
+ import { EyeIcon, EyeSlashIcon } from '@heroicons/react/24/outline';
7
+
8
+ function ResetPasswordForm() {
9
+ const searchParams = useSearchParams();
10
+ const router = useRouter();
11
+ const token = searchParams.get('token');
12
+
13
+ const [password, setPassword] = useState('');
14
+ const [confirmPassword, setConfirmPassword] = useState('');
15
+ const [showPassword, setShowPassword] = useState(false);
16
+ const [showConfirmPassword, setShowConfirmPassword] = useState(false);
17
+ const [loading, setLoading] = useState(false);
18
+ const [message, setMessage] = useState('');
19
+ const [error, setError] = useState('');
20
+
21
+ const handleSubmit = async (e) => {
22
+ e.preventDefault();
23
+ setError('');
24
+ setMessage('');
25
+
26
+ if (password !== confirmPassword) {
27
+ setError('Passwords do not match');
28
+ return;
29
+ }
30
+
31
+ if (password.length < 6) {
32
+ setError('Password must be at least 6 characters');
33
+ return;
34
+ }
35
+
36
+ setLoading(true);
37
+
38
+ try {
39
+ const response = await fetch('/api/reset-password', {
40
+ method: 'POST',
41
+ headers: { 'Content-Type': 'application/json' },
42
+ body: JSON.stringify({ token, newPassword: password }),
43
+ });
44
+
45
+ const data = await response.json();
46
+
47
+ if (!response.ok) {
48
+ throw new Error(data.error || 'Failed to reset password');
49
+ }
50
+
51
+ setMessage(data.message);
52
+ setTimeout(() => {
53
+ router.push('/login');
54
+ }, 3000);
55
+ } catch (err) {
56
+ setError(err.message);
57
+ } finally {
58
+ setLoading(false);
59
+ }
60
+ };
61
+
62
+ if (!token) {
63
+ return (
64
+ <div className="text-center">
65
+ <div className="bg-red-50 border border-red-200 rounded-lg p-6">
66
+ <h3 className="text-lg font-semibold text-red-800 mb-2">Invalid Reset Link</h3>
67
+ <p className="text-red-600 mb-4">No reset token provided. Please request a new password reset.</p>
68
+ <Link
69
+ href="/forgot-password"
70
+ className="inline-block px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700"
71
+ >
72
+ Request New Reset Link
73
+ </Link>
74
+ </div>
75
+ </div>
76
+ );
77
+ }
78
+
79
+ return (
80
+ <form onSubmit={handleSubmit} className="space-y-6">
81
+ {error && (
82
+ <div className="bg-red-50 border border-red-200 text-red-600 px-4 py-3 rounded-lg text-sm">
83
+ {error}
84
+ </div>
85
+ )}
86
+
87
+ {message && (
88
+ <div className="bg-green-50 border border-green-200 text-green-600 px-4 py-3 rounded-lg text-sm">
89
+ {message}
90
+ <div className="mt-2 text-sm">Redirecting to login...</div>
91
+ </div>
92
+ )}
93
+
94
+ <div>
95
+ <label className="block text-sm font-medium text-gray-700 mb-2">
96
+ New Password
97
+ </label>
98
+ <div className="relative">
99
+ <input
100
+ type={showPassword ? 'text' : 'password'}
101
+ value={password}
102
+ onChange={(e) => setPassword(e.target.value)}
103
+ className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all pr-12"
104
+ placeholder="Enter new password"
105
+ required
106
+ disabled={loading}
107
+ />
108
+ <button
109
+ type="button"
110
+ onClick={() => setShowPassword(!showPassword)}
111
+ className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-500 hover:text-gray-700"
112
+ >
113
+ {showPassword ? <EyeSlashIcon className="w-5 h-5" /> : <EyeIcon className="w-5 h-5" />}
114
+ </button>
115
+ </div>
116
+ </div>
117
+
118
+ <div>
119
+ <label className="block text-sm font-medium text-gray-700 mb-2">
120
+ Confirm New Password
121
+ </label>
122
+ <div className="relative">
123
+ <input
124
+ type={showConfirmPassword ? 'text' : 'password'}
125
+ value={confirmPassword}
126
+ onChange={(e) => setConfirmPassword(e.target.value)}
127
+ className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all pr-12"
128
+ placeholder="Confirm new password"
129
+ required
130
+ disabled={loading}
131
+ />
132
+ <button
133
+ type="button"
134
+ onClick={() => setShowConfirmPassword(!showConfirmPassword)}
135
+ className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-500 hover:text-gray-700"
136
+ >
137
+ {showConfirmPassword ? <EyeSlashIcon className="w-5 h-5" /> : <EyeIcon className="w-5 h-5" />}
138
+ </button>
139
+ </div>
140
+ </div>
141
+
142
+ <button
143
+ type="submit"
144
+ disabled={loading}
145
+ className="w-full py-3 bg-gradient-to-r from-blue-600 to-purple-600 text-white rounded-lg font-medium hover:from-blue-700 hover:to-purple-700 transition-all duration-200 shadow-md hover:shadow-lg disabled:opacity-50 disabled:cursor-not-allowed"
146
+ >
147
+ {loading ? 'Resetting Password...' : 'Reset Password'}
148
+ </button>
149
+ </form>
150
+ );
151
+ }
152
+
153
+ export default function ResetPasswordPage() {
154
+ return (
155
+ <div className="min-h-screen bg-gradient-to-br from-blue-50 via-white to-purple-50 flex items-center justify-center px-6">
156
+ <div className="max-w-md w-full">
157
+ <div className="bg-white/80 backdrop-blur-sm rounded-2xl shadow-xl p-8">
158
+ <div className="text-center mb-8">
159
+ <div className="inline-flex items-center justify-center w-16 h-16 bg-gradient-to-r from-blue-600 to-purple-600 rounded-2xl mb-4 shadow-lg">
160
+ <span className="text-white text-2xl">🔐</span>
161
+ </div>
162
+ <h2 className="text-3xl font-bold text-gray-900 mb-2">Set New Password</h2>
163
+ <p className="text-gray-600">Create a new secure password for your account</p>
164
+ </div>
165
+
166
+ <Suspense fallback={<div className="text-center">Loading...</div>}>
167
+ <ResetPasswordForm />
168
+ </Suspense>
169
+
170
+ <div className="mt-6 text-center">
171
+ <Link href="/login" className="text-sm text-blue-600 hover:text-blue-700">
172
+ ← Back to Login
173
+ </Link>
174
+ </div>
175
+ </div>
176
+ </div>
177
+ </div>
178
+ );
179
+ }
@@ -0,0 +1,153 @@
1
+ 'use client';
2
+
3
+ import React, { createContext, useContext, useState, useEffect } from 'react';
4
+
5
+ const AuthContext = createContext();
6
+
7
+ export const useAuth = () => {
8
+ const context = useContext(AuthContext);
9
+ if (!context) {
10
+ throw new Error('useAuth must be used within AuthProvider');
11
+ }
12
+ return context;
13
+ };
14
+
15
+ export const AuthProvider = ({ children }) => {
16
+ const [user, setUser] = useState(null);
17
+ const [loading, setLoading] = useState(true);
18
+
19
+ useEffect(() => {
20
+ const token = localStorage.getItem('token');
21
+ const userData = localStorage.getItem('user');
22
+
23
+ if (token && userData) {
24
+ try {
25
+ setUser(JSON.parse(userData));
26
+ } catch (error) {
27
+ console.error('Error parsing user data:', error);
28
+ localStorage.removeItem('user');
29
+ }
30
+ }
31
+ setLoading(false);
32
+ }, []);
33
+
34
+ const login = async (email, password) => {
35
+ try {
36
+ const response = await fetch('/api/login', {
37
+ method: 'POST',
38
+ headers: { 'Content-Type': 'application/json' },
39
+ body: JSON.stringify({ email, password }),
40
+ });
41
+
42
+ const data = await response.json();
43
+
44
+ if (!response.ok) {
45
+ throw new Error(data.error || 'Login failed');
46
+ }
47
+
48
+ localStorage.setItem('token', data.token);
49
+ localStorage.setItem('user', JSON.stringify(data.user));
50
+ setUser(data.user);
51
+ return data;
52
+ } catch (error) {
53
+ console.error('Login error:', error);
54
+ throw error;
55
+ }
56
+ };
57
+
58
+ const register = async (name, email, password) => {
59
+ try {
60
+ const response = await fetch('/api/register', {
61
+ method: 'POST',
62
+ headers: { 'Content-Type': 'application/json' },
63
+ body: JSON.stringify({ name, email, password }),
64
+ });
65
+
66
+ const data = await response.json();
67
+
68
+ if (!response.ok) {
69
+ throw new Error(data.error || 'Registration failed');
70
+ }
71
+
72
+ localStorage.setItem('token', data.token);
73
+ localStorage.setItem('user', JSON.stringify(data.user));
74
+ setUser(data.user);
75
+ return data;
76
+ } catch (error) {
77
+ console.error('Registration error:', error);
78
+ throw error;
79
+ }
80
+ };
81
+
82
+ const forgotPassword = async (email) => {
83
+ try {
84
+ console.log('Calling forgot password API for:', email);
85
+
86
+ const response = await fetch('/api/forgot-password', {
87
+ method: 'POST',
88
+ headers: {
89
+ 'Content-Type': 'application/json',
90
+ },
91
+ body: JSON.stringify({ email }),
92
+ });
93
+
94
+ console.log('Response status:', response.status);
95
+
96
+ const data = await response.json();
97
+ console.log('Response data:', data);
98
+
99
+ if (!response.ok) {
100
+ throw new Error(data.error || 'Request failed');
101
+ }
102
+
103
+ return data;
104
+ } catch (error) {
105
+ console.error('Forgot password error:', error);
106
+ throw error;
107
+ }
108
+ };
109
+
110
+ const resetPassword = async (token, newPassword) => {
111
+ try {
112
+ const response = await fetch('/api/reset-password', {
113
+ method: 'POST',
114
+ headers: { 'Content-Type': 'application/json' },
115
+ body: JSON.stringify({ token, newPassword }),
116
+ });
117
+
118
+ const data = await response.json();
119
+
120
+ if (!response.ok) {
121
+ throw new Error(data.error || 'Password reset failed');
122
+ }
123
+
124
+ return data;
125
+ } catch (error) {
126
+ console.error('Reset password error:', error);
127
+ throw error;
128
+ }
129
+ };
130
+
131
+ const logout = () => {
132
+ localStorage.removeItem('token');
133
+ localStorage.removeItem('user');
134
+ setUser(null);
135
+ };
136
+
137
+ const value = {
138
+ user,
139
+ loading,
140
+ login,
141
+ register,
142
+ forgotPassword,
143
+ resetPassword,
144
+ logout,
145
+ isAuthenticated: !!user,
146
+ };
147
+
148
+ return (
149
+ <AuthContext.Provider value={value}>
150
+ {children}
151
+ </AuthContext.Provider>
152
+ );
153
+ };
package/src/index.js ADDED
@@ -0,0 +1,5 @@
1
+ // Export everything needed for the auth module
2
+ export { default as AuthProvider } from './context/AuthContext';
3
+ export { useAuth } from './context/AuthContext';
4
+ export * from './lib/auth';
5
+ export { default as pool } from './lib/db';