diff --git a/App.tsx b/App.tsx index 27d6080..e3b1222 100644 --- a/App.tsx +++ b/App.tsx @@ -102,6 +102,7 @@ function App() { const handleStartSession = async (plan?: WorkoutPlan, startWeight?: number) => { if (!currentUser) return; + if (activeSession) return; // Get latest weight from profile or default const profile = getCurrentUserProfile(currentUser.id); diff --git a/components/Plans.tsx b/components/Plans.tsx index 31a5b04..a6067ff 100644 --- a/components/Plans.tsx +++ b/components/Plans.tsx @@ -265,7 +265,7 @@ const Plans: React.FC = ({ userId, onStartPlan, lang }) => { {showExerciseSelector && ( -
+
{t('select_exercise', lang)}
@@ -292,7 +292,7 @@ const Plans: React.FC = ({ userId, onStartPlan, lang }) => {
{isCreatingExercise && ( -
+

{t('create_exercise', lang)}

diff --git a/components/Profile.tsx b/components/Profile.tsx index b1059bf..93c6e64 100644 --- a/components/Profile.tsx +++ b/components/Profile.tsx @@ -490,7 +490,7 @@ const Profile: React.FC = ({ user, onLogout, lang, onLanguageChang - {createMsg &&

{createMsg}

} + {createMsg &&

{createMsg}

}
{/* User List */} diff --git a/components/Tracker/ActiveSessionView.tsx b/components/Tracker/ActiveSessionView.tsx index 9f7d0c8..fc9534c 100644 --- a/components/Tracker/ActiveSessionView.tsx +++ b/components/Tracker/ActiveSessionView.tsx @@ -432,7 +432,7 @@ const ActiveSessionView: React.FC = ({ tracker, activeSe {/* Finish Confirmation Dialog */} {showFinishConfirm && ( -
+

{t('finish_confirm_title', lang)}

{t('finish_confirm_msg', lang)}

@@ -459,7 +459,7 @@ const ActiveSessionView: React.FC = ({ tracker, activeSe {/* Quit Without Saving Confirmation Dialog */} {showQuitConfirm && ( -
+

{t('quit_confirm_title', lang)}

{t('quit_confirm_msg', lang)}

diff --git a/components/Tracker/IdleView.tsx b/components/Tracker/IdleView.tsx index 4a2831e..4b437d8 100644 --- a/components/Tracker/IdleView.tsx +++ b/components/Tracker/IdleView.tsx @@ -94,7 +94,7 @@ const IdleView: React.FC = ({ tracker, lang }) => {
{showPlanPrep && ( -
+

{showPlanPrep.name}

diff --git a/playwright-report/index.html b/playwright-report/index.html index d2e145c..74375b0 100644 --- a/playwright-report/index.html +++ b/playwright-report/index.html @@ -82,4 +82,4 @@ Error generating stack: `+n.message+`
- \ No newline at end of file + \ No newline at end of file diff --git a/server/prisma/dev.db b/server/prisma/dev.db index bdf577d..418f319 100644 Binary files a/server/prisma/dev.db and b/server/prisma/dev.db differ diff --git a/server/src/routes/sessions.ts b/server/src/routes/sessions.ts index 78eea1a..5536096 100644 --- a/server/src/routes/sessions.ts +++ b/server/src/routes/sessions.ts @@ -81,6 +81,16 @@ router.post('/', async (req: any, res) => { return res.json(updated); } else { // Create + // If creating a new active session (endTime is null), check if one already exists + if (!end) { + const active = await prisma.workoutSession.findFirst({ + where: { userId, endTime: null } + }); + if (active) { + return res.status(400).json({ error: 'An active session already exists' }); + } + } + const created = await prisma.workoutSession.create({ data: { id, // Use provided ID or let DB gen? Frontend usually generates UUIDs. @@ -282,7 +292,7 @@ router.post('/active/log-set', async (req: any, res) => { break; } } - + const mappedNewSet = { ...newSet, exerciseName: newSet.exercise.name, @@ -299,7 +309,7 @@ router.post('/active/log-set', async (req: any, res) => { exerciseName: newSet.exercise.name, type: newSet.exercise.type }; - + res.json({ success: true, newSet: mappedNewSet, activeExerciseId: null }); } catch (error) { @@ -381,23 +391,14 @@ router.delete('/active', async (req: any, res) => { try { const userId = req.user.userId; - // Find active session - const activeSession = await prisma.workoutSession.findFirst({ + // Delete all active sessions for this user to ensure clean state + await prisma.workoutSession.deleteMany({ 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); diff --git a/tests/login-first-time-password-change.spec.ts b/tests/login-first-time-password-change.spec.ts new file mode 100644 index 0000000..f3f87af --- /dev/null +++ b/tests/login-first-time-password-change.spec.ts @@ -0,0 +1,48 @@ +// spec: specs/gymflow-test-plan.md +// seed: tests/core-auth.spec.ts + +import { test, expect } from '@playwright/test'; + +test.describe('I. Core & Authentication', () => { + test('A. Login - First-Time Password Change', async ({ page }) => { + // 1. Navigate to the login page. + await page.goto('http://localhost:3000/'); + + // Log in as admin to create a new user for testing first-time login + await page.getByRole('textbox', { name: 'user@gymflow.ai' }).fill('admin@gymflow.ai'); + await page.locator('input[type="password"]').fill('admin1234'); + await page.getByRole('button', { name: 'Login' }).click(); + + // Navigate to profile and create a new user + await page.getByRole('button', { name: 'Profile' }).click(); + await page.getByRole('textbox', { name: 'Email' }).fill('test@gymflow.ai'); + await page.getByRole('textbox', { name: 'Password', exact: true }).fill('test1234'); + await page.getByRole('button', { name: 'Create' }).click(); + + // Log out as admin + await page.getByRole('button', { name: 'Logout' }).click(); + + // 2. Log in with a first-time user's temporary credentials. + await page.getByRole('textbox', { name: 'user@gymflow.ai' }).fill('test@gymflow.ai'); + await page.locator('input[type="password"]').fill('test1234'); + await page.getByRole('button', { name: 'Login' }).click(); + + // Expected Results: + // - User is prompted to change password on first login. + await expect(page.getByRole('heading', { name: 'Change Password' })).toBeVisible(); + + // 3. Enter a new password (at least 4 characters). + await page.getByRole('textbox').fill('newtestpass'); + + // 4. Click 'Save' or 'Change Password' button. + await page.getByRole('button', { name: 'Save & Login' }).click(); + + // Expected Results: + // - New password is set successfully. + // - User is logged into the application. + await page.waitForLoadState('networkidle'); + await expect(page.getByRole('button', { name: 'Tracker' })).toBeVisible(); + // - No error messages are displayed. + await expect(page.locator('text=Invalid credentials')).not.toBeVisible(); + }); +});