feat(worker): login-only client (admin creates users)
This commit is contained in:
@@ -1,11 +1,10 @@
|
|||||||
import { createContext, useCallback, useContext, useState, type ReactNode } from 'react';
|
import { createContext, useCallback, useContext, useState, type ReactNode } from 'react';
|
||||||
import { clearToken, getToken } from '../lib/auth-storage';
|
import { clearToken, getToken } from '../lib/auth-storage';
|
||||||
import { signIn as apiSignIn, signUp as apiSignUp } from '../lib/api';
|
import { signIn as apiSignIn } from '../lib/api';
|
||||||
|
|
||||||
interface AuthContextValue {
|
interface AuthContextValue {
|
||||||
isAuthed: boolean;
|
isAuthed: boolean;
|
||||||
signIn: (email: string, password: string) => Promise<void>;
|
signIn: (email: string, password: string) => Promise<void>;
|
||||||
signUp: (email: string, password: string) => Promise<void>;
|
|
||||||
signOut: () => void;
|
signOut: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -19,25 +18,13 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
|||||||
setIsAuthed(true);
|
setIsAuthed(true);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const signUp = useCallback(
|
|
||||||
async (email: string, password: string) => {
|
|
||||||
// Register, then sign in to obtain the bearer token.
|
|
||||||
await apiSignUp(email, password);
|
|
||||||
await apiSignIn(email, password);
|
|
||||||
setIsAuthed(true);
|
|
||||||
},
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
const signOut = useCallback(() => {
|
const signOut = useCallback(() => {
|
||||||
clearToken();
|
clearToken();
|
||||||
setIsAuthed(false);
|
setIsAuthed(false);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AuthContext.Provider value={{ isAuthed, signIn, signUp, signOut }}>
|
<AuthContext.Provider value={{ isAuthed, signIn, signOut }}>{children}</AuthContext.Provider>
|
||||||
{children}
|
|
||||||
</AuthContext.Provider>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ export const API_URL = import.meta.env.VITE_API_URL ?? 'http://localhost:3000';
|
|||||||
export class ApiError extends Error {
|
export class ApiError extends Error {
|
||||||
constructor(
|
constructor(
|
||||||
public status: number,
|
public status: number,
|
||||||
message: string,
|
message: string
|
||||||
) {
|
) {
|
||||||
super(message);
|
super(message);
|
||||||
this.name = 'ApiError';
|
this.name = 'ApiError';
|
||||||
@@ -35,13 +35,3 @@ export async function signIn(email: string, password: string): Promise<void> {
|
|||||||
if (!token) throw new ApiError(500, 'Geen token ontvangen');
|
if (!token) throw new ApiError(500, 'Geen token ontvangen');
|
||||||
setToken(token);
|
setToken(token);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sign up affordance for testing: POST /api/auth/sign-up/email.
|
|
||||||
export async function signUp(email: string, password: string): Promise<void> {
|
|
||||||
const res = await fetch(`${API_URL}/api/auth/sign-up/email`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ email, password, name: email.split('@')[0] || 'Worker' }),
|
|
||||||
});
|
|
||||||
if (!res.ok) throw new ApiError(res.status, 'Registreren mislukt');
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,30 +1,21 @@
|
|||||||
import { useState, type FormEvent } from 'react';
|
import { useState, type FormEvent } from 'react';
|
||||||
import { useAuth } from '../auth/AuthContext';
|
import { useAuth } from '../auth/AuthContext';
|
||||||
|
|
||||||
type Mode = 'signin' | 'signup';
|
|
||||||
|
|
||||||
export default function Login() {
|
export default function Login() {
|
||||||
const { signIn, signUp } = useAuth();
|
const { signIn } = useAuth();
|
||||||
const [mode, setMode] = useState<Mode>('signin');
|
|
||||||
const [email, setEmail] = useState('');
|
const [email, setEmail] = useState('');
|
||||||
const [password, setPassword] = useState('');
|
const [password, setPassword] = useState('');
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [busy, setBusy] = useState(false);
|
const [busy, setBusy] = useState(false);
|
||||||
|
|
||||||
const submitLabel = mode === 'signin' ? 'Inloggen' : 'Registreren';
|
|
||||||
|
|
||||||
async function handleSubmit(e: FormEvent) {
|
async function handleSubmit(e: FormEvent) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setError(null);
|
setError(null);
|
||||||
setBusy(true);
|
setBusy(true);
|
||||||
try {
|
try {
|
||||||
if (mode === 'signin') {
|
await signIn(email, password);
|
||||||
await signIn(email, password);
|
|
||||||
} else {
|
|
||||||
await signUp(email, password);
|
|
||||||
}
|
|
||||||
} catch {
|
} catch {
|
||||||
setError(mode === 'signin' ? 'Inloggen mislukt' : 'Registreren mislukt');
|
setError('Inloggen mislukt');
|
||||||
} finally {
|
} finally {
|
||||||
setBusy(false);
|
setBusy(false);
|
||||||
}
|
}
|
||||||
@@ -49,26 +40,16 @@ export default function Login() {
|
|||||||
<input
|
<input
|
||||||
className="field-input"
|
className="field-input"
|
||||||
type="password"
|
type="password"
|
||||||
autoComplete={mode === 'signin' ? 'current-password' : 'new-password'}
|
autoComplete="current-password"
|
||||||
value={password}
|
value={password}
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
{error && <p className="login-error">{error}</p>}
|
{error && <p className="login-error">{error}</p>}
|
||||||
<button className="btn-primary" type="submit" disabled={busy}>
|
<button className="btn-primary" type="submit" disabled={busy}>
|
||||||
{submitLabel}
|
Inloggen
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
<button
|
|
||||||
className="login-toggle"
|
|
||||||
type="button"
|
|
||||||
onClick={() => {
|
|
||||||
setMode((m) => (m === 'signin' ? 'signup' : 'signin'));
|
|
||||||
setError(null);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{mode === 'signin' ? 'Nog geen account? Registreren' : 'Al een account? Inloggen'}
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user