Coastal Timesheet v2 — full feature buildout
Features: - Copy Previous Week (entries duplication) - Bulk Approve/Reject (admin workflow) - Overtime tracking (employee + admin views) - DayCard component with auto-save - PDF generation, email notifications - React + Tailwind frontend, Prisma + PostgreSQL backend - Docker deployment (3 containers)
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
import { createContext, useContext, useState, useEffect, useCallback } from 'react';
|
||||
import api, { setTokens, clearTokens, getAccessToken } from '../api/client';
|
||||
|
||||
const AuthContext = createContext(null);
|
||||
|
||||
export function AuthProvider({ children }) {
|
||||
const [user, setUser] = useState(() => {
|
||||
try {
|
||||
const stored = localStorage.getItem('user');
|
||||
return stored ? JSON.parse(stored) : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
/* On mount, verify the stored token is still valid */
|
||||
useEffect(() => {
|
||||
async function verify() {
|
||||
const token = getAccessToken();
|
||||
if (!token) {
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const { data } = await api.get('/auth/me');
|
||||
setUser(data.user);
|
||||
localStorage.setItem('user', JSON.stringify(data.user));
|
||||
} catch {
|
||||
clearTokens();
|
||||
setUser(null);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
verify();
|
||||
}, []);
|
||||
|
||||
const login = useCallback(async (email, password) => {
|
||||
const { data } = await api.post('/auth/login', { email, password });
|
||||
setTokens(data.accessToken, data.refreshToken);
|
||||
setUser(data.user);
|
||||
localStorage.setItem('user', JSON.stringify(data.user));
|
||||
return data.user;
|
||||
}, []);
|
||||
|
||||
const logout = useCallback(async () => {
|
||||
try {
|
||||
await api.post('/auth/logout');
|
||||
} catch {
|
||||
/* ignore — we clear locally regardless */
|
||||
}
|
||||
clearTokens();
|
||||
setUser(null);
|
||||
}, []);
|
||||
|
||||
const isAdmin = user?.role === 'admin' || user?.role === 'super_admin';
|
||||
|
||||
const value = {
|
||||
user,
|
||||
loading,
|
||||
login,
|
||||
logout,
|
||||
isAdmin,
|
||||
isAuthenticated: !!user,
|
||||
};
|
||||
|
||||
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
|
||||
}
|
||||
|
||||
export function useAuth() {
|
||||
const ctx = useContext(AuthContext);
|
||||
if (!ctx) {
|
||||
throw new Error('useAuth must be used within an AuthProvider');
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
|
||||
export default AuthContext;
|
||||
Reference in New Issue
Block a user