ce0cc6fbf0
- Added user registration and login functionality with JWT authentication. - Created auth controller, service, and module in the backend. - Implemented user model and user products management. - Integrated NextAuth for session management on the frontend. - Added middleware for protecting routes and handling public access. - Updated frontend API routes to include authorization headers. - Enhanced recipe and user product models to support ownership and visibility. - Created registration and login pages in the frontend. - Added necessary types for NextAuth session management.
84 lines
3.2 KiB
TypeScript
84 lines
3.2 KiB
TypeScript
'use client';
|
|
|
|
import { useState, FormEvent } from 'react';
|
|
import { signIn } from 'next-auth/react';
|
|
import { useRouter } from 'next/navigation';
|
|
|
|
const BACKEND_URL = process.env.NEXT_PUBLIC_API_URL ?? '/api';
|
|
|
|
export default function RegisterPage() {
|
|
const router = useRouter();
|
|
const [form, setForm] = useState({ username: '', email: '', password: '', confirm: '' });
|
|
const [error, setError] = useState('');
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
async function handleSubmit(e: FormEvent) {
|
|
e.preventDefault();
|
|
setError('');
|
|
if (form.password !== form.confirm) {
|
|
setError('Lösenorden matchar inte');
|
|
return;
|
|
}
|
|
setLoading(true);
|
|
const res = await fetch('/api/auth-register', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ username: form.username, email: form.email, password: form.password }),
|
|
});
|
|
if (!res.ok) {
|
|
const data = await res.json().catch(() => ({}));
|
|
setError(data.message ?? 'Registrering misslyckades');
|
|
setLoading(false);
|
|
return;
|
|
}
|
|
// Auto-login after register
|
|
await signIn('credentials', { username: form.username, password: form.password, redirect: false });
|
|
router.push('/');
|
|
router.refresh();
|
|
}
|
|
|
|
return (
|
|
<main style={{ maxWidth: 400, margin: '80px auto', padding: '0 1rem' }}>
|
|
<h1 style={{ marginBottom: '1.5rem' }}>Skapa konto</h1>
|
|
<form onSubmit={handleSubmit} style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
|
|
{(['username', 'email', 'password', 'confirm'] as const).map((field) => (
|
|
<div key={field}>
|
|
<label htmlFor={field} style={{ display: 'block', marginBottom: 4 }}>
|
|
{field === 'username' ? 'Användarnamn' : field === 'email' ? 'E-post' : field === 'password' ? 'Lösenord' : 'Bekräfta lösenord'}
|
|
</label>
|
|
<input
|
|
id={field}
|
|
type={field.includes('password') || field === 'confirm' ? 'password' : field === 'email' ? 'email' : 'text'}
|
|
value={form[field]}
|
|
onChange={(e) => setForm((f) => ({ ...f, [field]: e.target.value }))}
|
|
required
|
|
minLength={field.includes('password') || field === 'confirm' ? 8 : undefined}
|
|
style={{ width: '100%', padding: '8px 12px', borderRadius: 6, border: '1px solid #ccc', fontSize: '1rem' }}
|
|
/>
|
|
</div>
|
|
))}
|
|
{error && <p style={{ color: 'red', margin: 0 }}>{error}</p>}
|
|
<button
|
|
type="submit"
|
|
disabled={loading}
|
|
style={{
|
|
padding: '10px',
|
|
background: '#16a34a',
|
|
color: 'white',
|
|
border: 'none',
|
|
borderRadius: 6,
|
|
fontSize: '1rem',
|
|
cursor: loading ? 'not-allowed' : 'pointer',
|
|
opacity: loading ? 0.7 : 1,
|
|
}}
|
|
>
|
|
{loading ? 'Skapar konto...' : 'Skapa konto'}
|
|
</button>
|
|
<p style={{ textAlign: 'center', fontSize: '0.9rem' }}>
|
|
Har du redan ett konto? <a href="/login">Logga in</a>
|
|
</p>
|
|
</form>
|
|
</main>
|
|
);
|
|
}
|