User already exists message is red. Modals to the viewport center. Fixed Quit option: it deletes the session.
This commit is contained in:
1
App.tsx
1
App.tsx
@@ -102,6 +102,7 @@ function App() {
|
|||||||
|
|
||||||
const handleStartSession = async (plan?: WorkoutPlan, startWeight?: number) => {
|
const handleStartSession = async (plan?: WorkoutPlan, startWeight?: number) => {
|
||||||
if (!currentUser) return;
|
if (!currentUser) return;
|
||||||
|
if (activeSession) return;
|
||||||
|
|
||||||
// Get latest weight from profile or default
|
// Get latest weight from profile or default
|
||||||
const profile = getCurrentUserProfile(currentUser.id);
|
const profile = getCurrentUserProfile(currentUser.id);
|
||||||
|
|||||||
@@ -265,7 +265,7 @@ const Plans: React.FC<PlansProps> = ({ userId, onStartPlan, lang }) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{showExerciseSelector && (
|
{showExerciseSelector && (
|
||||||
<div className="absolute inset-0 bg-surface z-50 flex flex-col animate-in slide-in-from-bottom-full duration-200">
|
<div className="fixed inset-0 bg-surface z-50 flex flex-col animate-in slide-in-from-bottom-full duration-200">
|
||||||
<div className="px-4 py-3 border-b border-outline-variant flex justify-between items-center bg-surface-container">
|
<div className="px-4 py-3 border-b border-outline-variant flex justify-between items-center bg-surface-container">
|
||||||
<span className="font-medium text-on-surface">{t('select_exercise', lang)}</span>
|
<span className="font-medium text-on-surface">{t('select_exercise', lang)}</span>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
@@ -292,7 +292,7 @@ const Plans: React.FC<PlansProps> = ({ userId, onStartPlan, lang }) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isCreatingExercise && (
|
{isCreatingExercise && (
|
||||||
<div className="absolute inset-0 bg-surface z-[60] flex flex-col animate-in slide-in-from-bottom-full duration-200">
|
<div className="fixed inset-0 bg-surface z-[60] flex flex-col animate-in slide-in-from-bottom-full duration-200">
|
||||||
<div className="px-4 py-3 border-b border-outline-variant flex justify-between items-center bg-surface-container">
|
<div className="px-4 py-3 border-b border-outline-variant flex justify-between items-center bg-surface-container">
|
||||||
<h3 className="text-title-medium font-medium text-on-surface">{t('create_exercise', lang)}</h3>
|
<h3 className="text-title-medium font-medium text-on-surface">{t('create_exercise', lang)}</h3>
|
||||||
<button onClick={() => setIsCreatingExercise(false)} className="p-2 text-on-surface-variant hover:bg-white/5 rounded-full"><X /></button>
|
<button onClick={() => setIsCreatingExercise(false)} className="p-2 text-on-surface-variant hover:bg-white/5 rounded-full"><X /></button>
|
||||||
|
|||||||
@@ -490,7 +490,7 @@ const Profile: React.FC<ProfileProps> = ({ user, onLogout, lang, onLanguageChang
|
|||||||
<button onClick={handleCreateUser} className="w-full py-2 bg-primary text-on-primary rounded-full text-sm font-medium">
|
<button onClick={handleCreateUser} className="w-full py-2 bg-primary text-on-primary rounded-full text-sm font-medium">
|
||||||
{t('create_btn', lang)}
|
{t('create_btn', lang)}
|
||||||
</button>
|
</button>
|
||||||
{createMsg && <p className="text-xs text-on-surface-variant text-center">{createMsg}</p>}
|
{createMsg && <p className="text-xs text-error text-center font-medium">{createMsg}</p>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* User List */}
|
{/* User List */}
|
||||||
|
|||||||
@@ -432,7 +432,7 @@ const ActiveSessionView: React.FC<ActiveSessionViewProps> = ({ tracker, activeSe
|
|||||||
|
|
||||||
{/* Finish Confirmation Dialog */}
|
{/* Finish Confirmation Dialog */}
|
||||||
{showFinishConfirm && (
|
{showFinishConfirm && (
|
||||||
<div className="absolute inset-0 bg-black/60 z-50 flex items-center justify-center p-6 backdrop-blur-sm">
|
<div className="fixed inset-0 bg-black/60 z-50 flex items-center justify-center p-6 backdrop-blur-sm">
|
||||||
<div className="w-full max-w-sm bg-surface-container rounded-[28px] p-6 shadow-elevation-3">
|
<div className="w-full max-w-sm bg-surface-container rounded-[28px] p-6 shadow-elevation-3">
|
||||||
<h3 className="text-2xl font-normal text-on-surface mb-2">{t('finish_confirm_title', lang)}</h3>
|
<h3 className="text-2xl font-normal text-on-surface mb-2">{t('finish_confirm_title', lang)}</h3>
|
||||||
<p className="text-on-surface-variant text-sm mb-8">{t('finish_confirm_msg', lang)}</p>
|
<p className="text-on-surface-variant text-sm mb-8">{t('finish_confirm_msg', lang)}</p>
|
||||||
@@ -459,7 +459,7 @@ const ActiveSessionView: React.FC<ActiveSessionViewProps> = ({ tracker, activeSe
|
|||||||
|
|
||||||
{/* Quit Without Saving Confirmation Dialog */}
|
{/* Quit Without Saving Confirmation Dialog */}
|
||||||
{showQuitConfirm && (
|
{showQuitConfirm && (
|
||||||
<div className="absolute inset-0 bg-black/60 z-50 flex items-center justify-center p-6 backdrop-blur-sm">
|
<div className="fixed inset-0 bg-black/60 z-50 flex items-center justify-center p-6 backdrop-blur-sm">
|
||||||
<div className="w-full max-w-sm bg-surface-container rounded-[28px] p-6 shadow-elevation-3">
|
<div className="w-full max-w-sm bg-surface-container rounded-[28px] p-6 shadow-elevation-3">
|
||||||
<h3 className="text-2xl font-normal text-error mb-2">{t('quit_confirm_title', lang)}</h3>
|
<h3 className="text-2xl font-normal text-error mb-2">{t('quit_confirm_title', lang)}</h3>
|
||||||
<p className="text-on-surface-variant text-sm mb-8">{t('quit_confirm_msg', lang)}</p>
|
<p className="text-on-surface-variant text-sm mb-8">{t('quit_confirm_msg', lang)}</p>
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ const IdleView: React.FC<IdleViewProps> = ({ tracker, lang }) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{showPlanPrep && (
|
{showPlanPrep && (
|
||||||
<div className="absolute inset-0 bg-black/60 z-50 flex items-center justify-center p-6 backdrop-blur-sm">
|
<div className="fixed inset-0 bg-black/60 z-50 flex items-center justify-center p-6 backdrop-blur-sm">
|
||||||
<div className="w-full max-w-sm bg-surface-container rounded-[28px] p-6 shadow-elevation-3">
|
<div className="w-full max-w-sm bg-surface-container rounded-[28px] p-6 shadow-elevation-3">
|
||||||
<h3 className="text-2xl font-normal text-on-surface mb-4">{showPlanPrep.name}</h3>
|
<h3 className="text-2xl font-normal text-on-surface mb-4">{showPlanPrep.name}</h3>
|
||||||
<div className="bg-surface-container-high p-4 rounded-xl text-on-surface-variant text-sm mb-8">
|
<div className="bg-surface-container-high p-4 rounded-xl text-on-surface-variant text-sm mb-8">
|
||||||
|
|||||||
@@ -82,4 +82,4 @@ Error generating stack: `+n.message+`
|
|||||||
<div id='root'></div>
|
<div id='root'></div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
<script id="playwrightReportBase64" type="application/zip">data:application/zip;base64,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</script>
|
<script id="playwrightReportBase64" type="application/zip">data:application/zip;base64,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</script>
|
||||||
Binary file not shown.
@@ -81,6 +81,16 @@ router.post('/', async (req: any, res) => {
|
|||||||
return res.json(updated);
|
return res.json(updated);
|
||||||
} else {
|
} else {
|
||||||
// Create
|
// 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({
|
const created = await prisma.workoutSession.create({
|
||||||
data: {
|
data: {
|
||||||
id, // Use provided ID or let DB gen? Frontend usually generates UUIDs.
|
id, // Use provided ID or let DB gen? Frontend usually generates UUIDs.
|
||||||
@@ -381,23 +391,14 @@ router.delete('/active', async (req: any, res) => {
|
|||||||
try {
|
try {
|
||||||
const userId = req.user.userId;
|
const userId = req.user.userId;
|
||||||
|
|
||||||
// Find active session
|
// Delete all active sessions for this user to ensure clean state
|
||||||
const activeSession = await prisma.workoutSession.findFirst({
|
await prisma.workoutSession.deleteMany({
|
||||||
where: {
|
where: {
|
||||||
userId,
|
userId,
|
||||||
endTime: null
|
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 });
|
res.json({ success: true });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
|||||||
48
tests/login-first-time-password-change.spec.ts
Normal file
48
tests/login-first-time-password-change.spec.ts
Normal file
@@ -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();
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user