From ead48fb24975ac27aab885f95c13d383c626b6b4 Mon Sep 17 00:00:00 2001 From: AG Date: Mon, 1 Dec 2025 07:23:47 +0200 Subject: [PATCH] User already exists message is red. Modals to the viewport center. Fixed Quit option: it deletes the session. --- App.tsx | 1 + components/Plans.tsx | 4 +- components/Profile.tsx | 2 +- components/Tracker/ActiveSessionView.tsx | 4 +- components/Tracker/IdleView.tsx | 2 +- playwright-report/index.html | 2 +- server/prisma/dev.db | Bin 221184 -> 221184 bytes server/src/routes/sessions.ts | 27 +++++----- .../login-first-time-password-change.spec.ts | 48 ++++++++++++++++++ 9 files changed, 70 insertions(+), 20 deletions(-) create mode 100644 tests/login-first-time-password-change.spec.ts 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 bdf577d7de7d8fd7e738a0839312e27626f59fab..418f319be6956e57df8f2876373e0dc99af9305a 100644 GIT binary patch delta 7304 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 H&ys%vgw&Kw delta 6237 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 Q`g|bG(Nps)`M;z82cL4C&Hw-a 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(); + }); +});