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:
BizzleBot
2026-02-16 10:01:39 +00:00
commit f718a76153
65 changed files with 6756 additions and 0 deletions
+79
View File
@@ -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;