Ongoing workout session data is persistent now
This commit is contained in:
86
App.tsx
86
App.tsx
@@ -9,7 +9,7 @@ import Plans from './components/Plans';
|
|||||||
import Login from './components/Login';
|
import Login from './components/Login';
|
||||||
import Profile from './components/Profile';
|
import Profile from './components/Profile';
|
||||||
import { TabView, WorkoutSession, WorkoutSet, WorkoutPlan, User, Language } from './types';
|
import { TabView, WorkoutSession, WorkoutSet, WorkoutPlan, User, Language } from './types';
|
||||||
import { getSessions, saveSession, deleteSession, getPlans } from './services/storage';
|
import { getSessions, saveSession, deleteSession, getPlans, getActiveSession, updateActiveSession, deleteActiveSession } from './services/storage';
|
||||||
import { getCurrentUserProfile, getMe } from './services/auth';
|
import { getCurrentUserProfile, getMe } from './services/auth';
|
||||||
import { getSystemLanguage } from './services/i18n';
|
import { getSystemLanguage } from './services/i18n';
|
||||||
import { generateId } from './utils/uuid';
|
import { generateId } from './utils/uuid';
|
||||||
@@ -35,6 +35,20 @@ function App() {
|
|||||||
const res = await getMe();
|
const res = await getMe();
|
||||||
if (res.success && res.user) {
|
if (res.success && res.user) {
|
||||||
setCurrentUser(res.user);
|
setCurrentUser(res.user);
|
||||||
|
|
||||||
|
// Restore active workout session from database
|
||||||
|
const activeSession = await getActiveSession(res.user.id);
|
||||||
|
if (activeSession) {
|
||||||
|
setActiveSession(activeSession);
|
||||||
|
// Restore plan if session has planId
|
||||||
|
if (activeSession.planId) {
|
||||||
|
const plans = await getPlans(res.user.id);
|
||||||
|
const plan = plans.find(p => p.id === activeSession.planId);
|
||||||
|
if (plan) {
|
||||||
|
setActivePlan(plan);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
localStorage.removeItem('token');
|
localStorage.removeItem('token');
|
||||||
}
|
}
|
||||||
@@ -78,7 +92,7 @@ function App() {
|
|||||||
setCurrentUser(updatedUser);
|
setCurrentUser(updatedUser);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleStartSession = (plan?: WorkoutPlan, startWeight?: number) => {
|
const handleStartSession = async (plan?: WorkoutPlan, startWeight?: number) => {
|
||||||
if (!currentUser) return;
|
if (!currentUser) return;
|
||||||
|
|
||||||
// Get latest weight from profile or default
|
// Get latest weight from profile or default
|
||||||
@@ -97,12 +111,15 @@ function App() {
|
|||||||
setActivePlan(plan || null);
|
setActivePlan(plan || null);
|
||||||
setActiveSession(newSession);
|
setActiveSession(newSession);
|
||||||
setCurrentTab('TRACK');
|
setCurrentTab('TRACK');
|
||||||
|
|
||||||
|
// Save to database immediately
|
||||||
|
await saveSession(currentUser.id, newSession);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEndSession = async () => {
|
const handleEndSession = async () => {
|
||||||
if (activeSession && currentUser) {
|
if (activeSession && currentUser) {
|
||||||
const finishedSession = { ...activeSession, endTime: Date.now() };
|
const finishedSession = { ...activeSession, endTime: Date.now() };
|
||||||
await saveSession(currentUser.id, finishedSession);
|
await updateActiveSession(currentUser.id, finishedSession);
|
||||||
setSessions(prev => [finishedSession, ...prev]);
|
setSessions(prev => [finishedSession, ...prev]);
|
||||||
setActiveSession(null);
|
setActiveSession(null);
|
||||||
setActivePlan(null);
|
setActivePlan(null);
|
||||||
@@ -115,39 +132,47 @@ function App() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAddSet = (set: WorkoutSet) => {
|
const handleAddSet = async (set: WorkoutSet) => {
|
||||||
if (activeSession) {
|
if (activeSession && currentUser) {
|
||||||
setActiveSession(prev => {
|
const updatedSession = {
|
||||||
if (!prev) return null;
|
...activeSession,
|
||||||
return {
|
sets: [...activeSession.sets, set]
|
||||||
...prev,
|
};
|
||||||
sets: [...prev.sets, set]
|
setActiveSession(updatedSession);
|
||||||
};
|
// Save to database
|
||||||
});
|
await updateActiveSession(currentUser.id, updatedSession);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRemoveSetFromActive = (setId: string) => {
|
const handleRemoveSetFromActive = async (setId: string) => {
|
||||||
if (activeSession) {
|
if (activeSession && currentUser) {
|
||||||
setActiveSession(prev => {
|
const updatedSession = {
|
||||||
if (!prev) return null;
|
...activeSession,
|
||||||
return {
|
sets: activeSession.sets.filter(s => s.id !== setId)
|
||||||
...prev,
|
};
|
||||||
sets: prev.sets.filter(s => s.id !== setId)
|
setActiveSession(updatedSession);
|
||||||
};
|
// Save to database
|
||||||
});
|
await updateActiveSession(currentUser.id, updatedSession);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleUpdateSetInActive = (updatedSet: WorkoutSet) => {
|
const handleUpdateSetInActive = async (updatedSet: WorkoutSet) => {
|
||||||
if (activeSession) {
|
if (activeSession && currentUser) {
|
||||||
setActiveSession(prev => {
|
const updatedSession = {
|
||||||
if (!prev) return null;
|
...activeSession,
|
||||||
return {
|
sets: activeSession.sets.map(s => s.id === updatedSet.id ? updatedSet : s)
|
||||||
...prev,
|
};
|
||||||
sets: prev.sets.map(s => s.id === updatedSet.id ? updatedSet : s)
|
setActiveSession(updatedSession);
|
||||||
};
|
// Save to database
|
||||||
});
|
await updateActiveSession(currentUser.id, updatedSession);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleQuitSession = async () => {
|
||||||
|
if (currentUser) {
|
||||||
|
await deleteActiveSession(currentUser.id);
|
||||||
|
setActiveSession(null);
|
||||||
|
setActivePlan(null);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -184,6 +209,7 @@ function App() {
|
|||||||
activePlan={activePlan}
|
activePlan={activePlan}
|
||||||
onSessionStart={handleStartSession}
|
onSessionStart={handleStartSession}
|
||||||
onSessionEnd={handleEndSession}
|
onSessionEnd={handleEndSession}
|
||||||
|
onSessionQuit={handleQuitSession}
|
||||||
onSetAdded={handleAddSet}
|
onSetAdded={handleAddSet}
|
||||||
onRemoveSet={handleRemoveSetFromActive}
|
onRemoveSet={handleRemoveSetFromActive}
|
||||||
onUpdateSet={handleUpdateSetInActive}
|
onUpdateSet={handleUpdateSetInActive}
|
||||||
|
|||||||
@@ -75,8 +75,11 @@ const Profile: React.FC<ProfileProps> = ({ user, onLogout, lang, onLanguageChang
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [user.id, user.role, JSON.stringify(user.profile)]);
|
}, [user.id, user.role, JSON.stringify(user.profile)]);
|
||||||
|
|
||||||
const refreshUserList = () => {
|
const refreshUserList = async () => {
|
||||||
setAllUsers(getUsers());
|
const res = await getUsers();
|
||||||
|
if (res.success && res.users) {
|
||||||
|
setAllUsers(res.users);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const refreshExercises = async () => {
|
const refreshExercises = async () => {
|
||||||
@@ -143,16 +146,16 @@ const Profile: React.FC<ProfileProps> = ({ user, onLogout, lang, onLanguageChang
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAdminDeleteUser = (uid: string) => {
|
const handleAdminDeleteUser = async (uid: string) => {
|
||||||
if (confirm(t('delete_confirm', lang))) {
|
if (confirm(t('delete_confirm', lang))) {
|
||||||
deleteUser(uid);
|
await deleteUser(uid);
|
||||||
refreshUserList();
|
await refreshUserList();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAdminBlockUser = (uid: string, isBlocked: boolean) => {
|
const handleAdminBlockUser = async (uid: string, isBlocked: boolean) => {
|
||||||
toggleBlockUser(uid, isBlocked);
|
await toggleBlockUser(uid, isBlocked);
|
||||||
refreshUserList();
|
await refreshUserList();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAdminResetPass = (uid: string) => {
|
const handleAdminResetPass = (uid: string) => {
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ interface TrackerProps {
|
|||||||
activePlan: WorkoutPlan | null;
|
activePlan: WorkoutPlan | null;
|
||||||
onSessionStart: (plan?: WorkoutPlan, startWeight?: number) => void;
|
onSessionStart: (plan?: WorkoutPlan, startWeight?: number) => void;
|
||||||
onSessionEnd: () => void;
|
onSessionEnd: () => void;
|
||||||
|
onSessionQuit: () => void;
|
||||||
onSetAdded: (set: WorkoutSet) => void;
|
onSetAdded: (set: WorkoutSet) => void;
|
||||||
onRemoveSet: (setId: string) => void;
|
onRemoveSet: (setId: string) => void;
|
||||||
onUpdateSet: (set: WorkoutSet) => void;
|
onUpdateSet: (set: WorkoutSet) => void;
|
||||||
@@ -23,7 +24,7 @@ interface TrackerProps {
|
|||||||
import FilledInput from './FilledInput';
|
import FilledInput from './FilledInput';
|
||||||
import ExerciseModal from './ExerciseModal';
|
import ExerciseModal from './ExerciseModal';
|
||||||
|
|
||||||
const Tracker: React.FC<TrackerProps> = ({ userId, userWeight, activeSession, activePlan, onSessionStart, onSessionEnd, onSetAdded, onRemoveSet, onUpdateSet, lang }) => {
|
const Tracker: React.FC<TrackerProps> = ({ userId, userWeight, activeSession, activePlan, onSessionStart, onSessionEnd, onSessionQuit, onSetAdded, onRemoveSet, onUpdateSet, lang }) => {
|
||||||
const [exercises, setExercises] = useState<ExerciseDef[]>([]);
|
const [exercises, setExercises] = useState<ExerciseDef[]>([]);
|
||||||
const [plans, setPlans] = useState<WorkoutPlan[]>([]);
|
const [plans, setPlans] = useState<WorkoutPlan[]>([]);
|
||||||
const [selectedExercise, setSelectedExercise] = useState<ExerciseDef | null>(null);
|
const [selectedExercise, setSelectedExercise] = useState<ExerciseDef | null>(null);
|
||||||
@@ -665,8 +666,7 @@ const Tracker: React.FC<TrackerProps> = ({ userId, userWeight, activeSession, ac
|
|||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setShowQuitConfirm(false);
|
setShowQuitConfirm(false);
|
||||||
// Quit without saving - just navigate away or reset
|
onSessionQuit();
|
||||||
window.location.reload();
|
|
||||||
}}
|
}}
|
||||||
className="px-6 py-2.5 rounded-full bg-green-600 text-white font-medium hover:bg-green-700"
|
className="px-6 py-2.5 rounded-full bg-green-600 text-white font-medium hover:bg-green-700"
|
||||||
>
|
>
|
||||||
|
|||||||
Binary file not shown.
@@ -18,7 +18,7 @@ const app = express();
|
|||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
async function ensureAdminUser() {
|
async function ensureAdminUser() {
|
||||||
const adminEmail = process.env.ADMIN_EMAIL || 'admin@gymflow.ai';
|
const adminEmail = process.env.ADMIN_EMAIL || 'admin@gymflow.ai';
|
||||||
const adminPassword = process.env.ADMIN_PASSWORD || 'admin123';
|
const adminPassword = process.env.ADMIN_PASSWORD || 'admin1234';
|
||||||
|
|
||||||
const prisma = new PrismaClient();
|
const prisma = new PrismaClient();
|
||||||
|
|
||||||
|
|||||||
@@ -62,6 +62,47 @@ router.post('/login', async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Register
|
||||||
|
router.post('/register', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { email, password } = req.body;
|
||||||
|
|
||||||
|
// Check if user already exists
|
||||||
|
const existingUser = await prisma.user.findUnique({ where: { email } });
|
||||||
|
if (existingUser) {
|
||||||
|
return res.status(400).json({ error: 'User already exists' });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!password || password.length < 4) {
|
||||||
|
return res.status(400).json({ error: 'Password too short' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const hashedPassword = await bcrypt.hash(password, 10);
|
||||||
|
|
||||||
|
const user = await prisma.user.create({
|
||||||
|
data: {
|
||||||
|
email,
|
||||||
|
password: hashedPassword,
|
||||||
|
role: 'USER',
|
||||||
|
profile: {
|
||||||
|
create: {
|
||||||
|
weight: 70
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
include: { profile: true }
|
||||||
|
});
|
||||||
|
|
||||||
|
const token = jwt.sign({ userId: user.id, role: user.role }, JWT_SECRET);
|
||||||
|
const { password: _, ...userSafe } = user;
|
||||||
|
|
||||||
|
res.json({ success: true, user: userSafe, token });
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
res.status(500).json({ error: 'Server error' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Change Password
|
// Change Password
|
||||||
router.post('/change-password', async (req, res) => {
|
router.post('/change-password', async (req, res) => {
|
||||||
|
|
||||||
@@ -138,4 +179,92 @@ router.patch('/profile', async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// Admin: Get All Users
|
||||||
|
router.get('/users', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const token = req.headers.authorization?.split(' ')[1];
|
||||||
|
if (!token) return res.status(401).json({ error: 'Unauthorized' });
|
||||||
|
|
||||||
|
const decoded = jwt.verify(token, JWT_SECRET) as any;
|
||||||
|
if (decoded.role !== 'ADMIN') {
|
||||||
|
return res.status(403).json({ error: 'Admin access required' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const users = await prisma.user.findMany({
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
email: true,
|
||||||
|
role: true,
|
||||||
|
isBlocked: true,
|
||||||
|
isFirstLogin: true,
|
||||||
|
profile: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
res.json({ success: true, users });
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
res.status(500).json({ error: 'Server error' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Admin: Delete User
|
||||||
|
router.delete('/users/:id', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const token = req.headers.authorization?.split(' ')[1];
|
||||||
|
if (!token) return res.status(401).json({ error: 'Unauthorized' });
|
||||||
|
|
||||||
|
const decoded = jwt.verify(token, JWT_SECRET) as any;
|
||||||
|
if (decoded.role !== 'ADMIN') {
|
||||||
|
return res.status(403).json({ error: 'Admin access required' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const { id } = req.params;
|
||||||
|
|
||||||
|
// Prevent deleting self
|
||||||
|
if (id === decoded.userId) {
|
||||||
|
return res.status(400).json({ error: 'Cannot delete yourself' });
|
||||||
|
}
|
||||||
|
|
||||||
|
await prisma.user.delete({ where: { id } });
|
||||||
|
|
||||||
|
res.json({ success: true });
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
res.status(500).json({ error: 'Server error' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Admin: Toggle Block User
|
||||||
|
router.patch('/users/:id/block', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const token = req.headers.authorization?.split(' ')[1];
|
||||||
|
if (!token) return res.status(401).json({ error: 'Unauthorized' });
|
||||||
|
|
||||||
|
const decoded = jwt.verify(token, JWT_SECRET) as any;
|
||||||
|
if (decoded.role !== 'ADMIN') {
|
||||||
|
return res.status(403).json({ error: 'Admin access required' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const { id } = req.params;
|
||||||
|
const { block } = req.body;
|
||||||
|
|
||||||
|
// Prevent blocking self
|
||||||
|
if (id === decoded.userId) {
|
||||||
|
return res.status(400).json({ error: 'Cannot block yourself' });
|
||||||
|
}
|
||||||
|
|
||||||
|
await prisma.user.update({
|
||||||
|
where: { id },
|
||||||
|
data: { isBlocked: block }
|
||||||
|
});
|
||||||
|
|
||||||
|
res.json({ success: true });
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
res.status(500).json({ error: 'Server error' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|||||||
@@ -133,6 +133,121 @@ router.post('/', async (req: any, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Get active session (session without endTime)
|
||||||
|
router.get('/active', async (req: any, res) => {
|
||||||
|
try {
|
||||||
|
const userId = req.user.userId;
|
||||||
|
const activeSession = await prisma.workoutSession.findFirst({
|
||||||
|
where: {
|
||||||
|
userId,
|
||||||
|
endTime: null
|
||||||
|
},
|
||||||
|
include: { sets: { include: { exercise: true }, orderBy: { order: 'asc' } } }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!activeSession) {
|
||||||
|
return res.json({ success: true, session: null });
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json({ success: true, session: activeSession });
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
res.status(500).json({ error: 'Server error' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update active session (for real-time set updates)
|
||||||
|
router.put('/active', async (req: any, res) => {
|
||||||
|
try {
|
||||||
|
const userId = req.user.userId;
|
||||||
|
const { id, startTime, endTime, userBodyWeight, note, planId, planName, sets } = req.body;
|
||||||
|
|
||||||
|
// Convert timestamps to Date objects if they are numbers
|
||||||
|
const start = new Date(startTime);
|
||||||
|
const end = endTime ? new Date(endTime) : null;
|
||||||
|
const weight = userBodyWeight ? parseFloat(userBodyWeight) : null;
|
||||||
|
|
||||||
|
// Check if session exists and belongs to user
|
||||||
|
const existing = await prisma.workoutSession.findFirst({
|
||||||
|
where: { id, userId }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!existing) {
|
||||||
|
return res.status(404).json({ error: 'Session not found' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete existing sets to replace them
|
||||||
|
await prisma.workoutSet.deleteMany({ where: { sessionId: id } });
|
||||||
|
|
||||||
|
const updated = await prisma.workoutSession.update({
|
||||||
|
where: { id },
|
||||||
|
data: {
|
||||||
|
startTime: start,
|
||||||
|
endTime: end,
|
||||||
|
userBodyWeight: weight,
|
||||||
|
note,
|
||||||
|
planId,
|
||||||
|
planName,
|
||||||
|
sets: {
|
||||||
|
create: sets.map((s: any, idx: number) => ({
|
||||||
|
exerciseId: s.exerciseId,
|
||||||
|
order: idx,
|
||||||
|
weight: s.weight,
|
||||||
|
reps: s.reps,
|
||||||
|
distanceMeters: s.distanceMeters,
|
||||||
|
durationSeconds: s.durationSeconds,
|
||||||
|
completed: s.completed !== undefined ? s.completed : true
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
include: { sets: { include: { exercise: true } } }
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update user profile weight if session has weight and is finished
|
||||||
|
if (weight && end) {
|
||||||
|
await prisma.userProfile.upsert({
|
||||||
|
where: { userId },
|
||||||
|
create: { userId, weight },
|
||||||
|
update: { weight }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json({ success: true, session: updated });
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
res.status(500).json({ error: 'Server error' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Delete active session (quit without saving)
|
||||||
|
router.delete('/active', async (req: any, res) => {
|
||||||
|
try {
|
||||||
|
const userId = req.user.userId;
|
||||||
|
|
||||||
|
// Find active session
|
||||||
|
const activeSession = await prisma.workoutSession.findFirst({
|
||||||
|
where: {
|
||||||
|
userId,
|
||||||
|
endTime: null
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!activeSession) {
|
||||||
|
return res.json({ success: true, message: 'No active session found' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the session (cascade will delete sets)
|
||||||
|
await prisma.workoutSession.delete({
|
||||||
|
where: { id: activeSession.id }
|
||||||
|
});
|
||||||
|
|
||||||
|
res.json({ success: true });
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
res.status(500).json({ error: 'Server error' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Delete session
|
// Delete session
|
||||||
router.delete('/:id', async (req: any, res) => {
|
router.delete('/:id', async (req: any, res) => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -27,6 +27,15 @@ export const api = {
|
|||||||
if (!res.ok) throw new Error(await res.text());
|
if (!res.ok) throw new Error(await res.text());
|
||||||
return res.json();
|
return res.json();
|
||||||
},
|
},
|
||||||
|
put: async (endpoint: string, data: any) => {
|
||||||
|
const res = await fetch(`${API_URL}${endpoint}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: headers(),
|
||||||
|
body: JSON.stringify(data)
|
||||||
|
});
|
||||||
|
if (!res.ok) throw new Error(await res.text());
|
||||||
|
return res.json();
|
||||||
|
},
|
||||||
delete: async (endpoint: string) => {
|
delete: async (endpoint: string) => {
|
||||||
const res = await fetch(`${API_URL}${endpoint}`, {
|
const res = await fetch(`${API_URL}${endpoint}`, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
import { User, UserRole, UserProfile } from '../types';
|
import { User, UserRole, UserProfile } from '../types';
|
||||||
import { api, setAuthToken, removeAuthToken } from './api';
|
import { api, setAuthToken, removeAuthToken } from './api';
|
||||||
|
|
||||||
export const getUsers = (): any[] => {
|
export const getUsers = async (): Promise<{ success: boolean; users?: User[]; error?: string }> => {
|
||||||
// Not used in frontend anymore
|
try {
|
||||||
return [];
|
const res = await api.get('/auth/users');
|
||||||
|
return res;
|
||||||
|
} catch (e) {
|
||||||
|
return { success: false, error: 'Failed to fetch users' };
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const login = async (email: string, password: string): Promise<{ success: boolean; user?: User; error?: string }> => {
|
export const login = async (email: string, password: string): Promise<{ success: boolean; user?: User; error?: string }> => {
|
||||||
@@ -43,11 +47,21 @@ export const createUser = async (email: string, password: string): Promise<{ suc
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const deleteUser = async (userId: string) => {
|
export const deleteUser = async (userId: string) => {
|
||||||
// Admin only, not implemented in frontend UI yet
|
try {
|
||||||
|
const res = await api.delete(`/auth/users/${userId}`);
|
||||||
|
return res;
|
||||||
|
} catch (e) {
|
||||||
|
return { success: false, error: 'Failed to delete user' };
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const toggleBlockUser = (userId: string, block: boolean) => {
|
export const toggleBlockUser = async (userId: string, block: boolean) => {
|
||||||
// Admin only
|
try {
|
||||||
|
const res = await api.patch(`/auth/users/${userId}/block`, { block });
|
||||||
|
return res;
|
||||||
|
} catch (e) {
|
||||||
|
return { success: false, error: 'Failed to update user status' };
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const adminResetPassword = (userId: string, newPass: string) => {
|
export const adminResetPassword = (userId: string, newPass: string) => {
|
||||||
|
|||||||
@@ -24,6 +24,37 @@ export const saveSession = async (userId: string, session: WorkoutSession): Prom
|
|||||||
await api.post('/sessions', session);
|
await api.post('/sessions', session);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getActiveSession = async (userId: string): Promise<WorkoutSession | null> => {
|
||||||
|
try {
|
||||||
|
const response = await api.get('/sessions/active');
|
||||||
|
if (!response.success || !response.session) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const session = response.session;
|
||||||
|
// Convert ISO date strings to timestamps
|
||||||
|
return {
|
||||||
|
...session,
|
||||||
|
startTime: new Date(session.startTime).getTime(),
|
||||||
|
endTime: session.endTime ? new Date(session.endTime).getTime() : undefined,
|
||||||
|
sets: session.sets.map((set: any) => ({
|
||||||
|
...set,
|
||||||
|
exerciseName: set.exercise?.name || 'Unknown',
|
||||||
|
type: set.exercise?.type || 'STRENGTH'
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateActiveSession = async (userId: string, session: WorkoutSession): Promise<void> => {
|
||||||
|
await api.put('/sessions/active', session);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteActiveSession = async (userId: string): Promise<void> => {
|
||||||
|
await api.delete('/sessions/active');
|
||||||
|
};
|
||||||
|
|
||||||
export const deleteSession = async (userId: string, id: string): Promise<void> => {
|
export const deleteSession = async (userId: string, id: string): Promise<void> => {
|
||||||
await api.delete(`/sessions/${id}`);
|
await api.delete(`/sessions/${id}`);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user