feat: dark mode color adjustment, multi-source quotes, UI text wrapping, and versioning rules
- Add dark mode color adjustment helper for category colors - Support multiple motivational quote source URLs - Fix text wrapping for tasks/events to prevent horizontal overflow - Improve someday list item layout for long text - Add CLAUDE.md with semantic versioning and git workflow rules - Update test/doc URLs and planning docs v1.1.0 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
8496c2daa4
commit
c82572935c
@ -127,7 +127,7 @@ We need to implement the full authentication logic and ensure proper session man
|
||||
- Set cookie with security flags (httpOnly, secure, sameSite)
|
||||
- Use jose library instead of jsonwebtoken to avoid CommonJS issues with Edge runtime
|
||||
</action>
|
||||
<verify>Run `curl -X POST http://localhost:3000/api/auth/signup -H "Content-Type: application/json" -d '{"email":"test@example.com","password":"password"}'` and verify response structure</verify>
|
||||
<verify>Run `curl -X POST http://localhost:3001/api/auth/signup -H "Content-Type: application/json" -d '{"email":"test@example.com","password":"password"}'` and verify response structure</verify>
|
||||
<done>User can successfully sign up and receive a valid session token in HTTP-only cookie</done>
|
||||
</task>
|
||||
|
||||
@ -151,7 +151,7 @@ We need to implement the full authentication logic and ensure proper session man
|
||||
- Use jose library instead of jsonwebtoken to avoid CommonJS issues with Edge runtime
|
||||
- Prevent timing attacks by comparing passwords in constant time
|
||||
</action>
|
||||
<verify>Run `curl -X POST http://localhost:3000/api/auth/login -H "Content-Type: application/json" -d '{"email":"test@example.com","password":"password"}'` and verify response structure</verify>
|
||||
<verify>Run `curl -X POST http://localhost:3001/api/auth/login -H "Content-Type: application/json" -d '{"email":"test@example.com","password":"password"}'` and verify response structure</verify>
|
||||
<done>User can successfully authenticate and receive a valid session token in HTTP-only cookie</done>
|
||||
</task>
|
||||
|
||||
@ -164,7 +164,7 @@ We need to implement the full authentication logic and ensure proper session man
|
||||
- Return success message to the client
|
||||
- Ensure cookie flags match those used during login (httpOnly, secure, sameSite)
|
||||
</action>
|
||||
<verify>Run `curl -X POST http://localhost:3000/api/auth/logout` and verify cookie is cleared</verify>
|
||||
<verify>Run `curl -X POST http://localhost:3001/api/auth/logout` and verify cookie is cleared</verify>
|
||||
<done>User can successfully log out and session cookie is cleared</done>
|
||||
</task>
|
||||
|
||||
|
||||
@ -102,7 +102,7 @@ Building upon the foundation established in Plan 1, this plan implements the rem
|
||||
- Send email using configured email service (details in infrastructure)
|
||||
- Not reveal if email exists in system to prevent enumeration
|
||||
</action>
|
||||
<verify>Run `curl -X POST http://localhost:3000/api/auth/forgot-password -H "Content-Type: application/json" -d '{"email":"test@example.com"}'` and verify behavior</verify>
|
||||
<verify>Run `curl -X POST http://localhost:3001/api/auth/forgot-password -H "Content-Type: application/json" -d '{"email":"test@example.com"}'` and verify behavior</verify>
|
||||
<done>User can request password reset and receive confirmation without revealing account existence</done>
|
||||
</task>
|
||||
|
||||
@ -125,7 +125,7 @@ Building upon the foundation established in Plan 1, this plan implements the rem
|
||||
- Return appropriate error responses for invalid or expired tokens
|
||||
- Use bcryptjs for password hashing
|
||||
</action>
|
||||
<verify>Run `curl -X POST http://localhost:3000/api/auth/reset-password -H "Content-Type: application/json" -d '{"email":"test@example.com","token":"reset-token","password":"newpassword"}'` and verify behavior</verify>
|
||||
<verify>Run `curl -X POST http://localhost:3001/api/auth/reset-password -H "Content-Type: application/json" -d '{"email":"test@example.com","token":"reset-token","password":"newpassword"}'` and verify behavior</verify>
|
||||
<done>User can complete password reset with valid token and receive confirmation</done>
|
||||
</task>
|
||||
|
||||
@ -148,7 +148,7 @@ Building upon the foundation established in Plan 1, this plan implements the rem
|
||||
- Clear token from database after verification
|
||||
- Handle invalid/missing/expired tokens gracefully
|
||||
</action>
|
||||
<verify>Visit `http://localhost:3000/api/auth/verify-email?token=verification-token` and verify behavior</verify>
|
||||
<verify>Visit `http://localhost:3001/api/auth/verify-email?token=verification-token` and verify behavior</verify>
|
||||
<done>User can verify email address with valid token and receive confirmation</done>
|
||||
</task>
|
||||
|
||||
@ -169,7 +169,7 @@ Building upon the foundation established in Plan 1, this plan implements the rem
|
||||
- Be styled consistently with the rest of the application
|
||||
- Have a link back to login page
|
||||
</action>
|
||||
<verify>Visit http://localhost:3000/auth/forgot-password and verify form renders correctly</verify>
|
||||
<verify>Visit http://localhost:3001/auth/forgot-password and verify form renders correctly</verify>
|
||||
<done>Forgot password page is accessible, visually consistent, and functional</done>
|
||||
</task>
|
||||
|
||||
@ -192,7 +192,7 @@ Building upon the foundation established in Plan 1, this plan implements the rem
|
||||
- Be styled consistently with the rest of the application
|
||||
- Have a link back to login page
|
||||
</action>
|
||||
<verify>Visit http://localhost:3000/auth/reset-password?token=test-token and verify form renders correctly</verify>
|
||||
<verify>Visit http://localhost:3001/auth/reset-password?token=test-token and verify form renders correctly</verify>
|
||||
<done>Password reset page is accessible, visually consistent, and functional</done>
|
||||
</task>
|
||||
|
||||
@ -213,7 +213,7 @@ Building upon the foundation established in Plan 1, this plan implements the rem
|
||||
- Provide clear feedback on verification status
|
||||
- Be styled consistently with the rest of the application
|
||||
</action>
|
||||
<verify>Visit http://localhost:3000/auth/verify-email?token=test-token and verify behavior</verify>
|
||||
<verify>Visit http://localhost:3001/auth/verify-email?token=test-token and verify behavior</verify>
|
||||
<done>Email verification page is accessible, visually consistent, and functional</done>
|
||||
</task>
|
||||
|
||||
|
||||
@ -88,7 +88,7 @@ The previous plans have established core authentication flows. This plan focuses
|
||||
- Send email using configured email service (details in infrastructure)
|
||||
- Return clear success/failure messages
|
||||
</action>
|
||||
<verify>Run `curl -X POST http://localhost:3000/api/auth/send-verification-email -H "Content-Type: application/json" -d '{"email":"test@example.com"}'` and verify behavior</verify>
|
||||
<verify>Run `curl -X POST http://localhost:3001/api/auth/send-verification-email -H "Content-Type: application/json" -d '{"email":"test@example.com"}'` and verify behavior</verify>
|
||||
<done>User can request verification email to be resent and receive confirmation</done>
|
||||
</task>
|
||||
|
||||
|
||||
@ -85,7 +85,7 @@ Output: Bidirectional synchronization logic, sync API endpoint, and calendar set
|
||||
- Handles errors gracefully with appropriate messaging
|
||||
|
||||
Follow the API endpoint pattern established in Phase 2 and authentication flow from Phase 1.</action>
|
||||
<verify>curl -X POST http://localhost:3000/api/calendar/sync returns 200 with successful sync status</verify>
|
||||
<verify>curl -X POST http://localhost:3001/api/calendar/sync returns 200 with successful sync status</verify>
|
||||
<done>Endpoint successfully triggers sync process and returns status JSON</done>
|
||||
</task>
|
||||
|
||||
|
||||
@ -80,7 +80,7 @@ Functional OAuth endpoints that exchange authorization codes for real tokens and
|
||||
- Handle and log token exchange errors appropriately
|
||||
- Store the tokens in the CalendarConnection database record
|
||||
</action>
|
||||
<verify>Run `curl -X GET "http://localhost:3000/api/calendar/google/oauth?code=valid_code"` and verify tokens are stored in database instead of just console logged</verify>
|
||||
<verify>Run `curl -X GET "http://localhost:3001/api/calendar/google/oauth?code=valid_code"` and verify tokens are stored in database instead of just console logged</verify>
|
||||
<done>Google OAuth callback endpoint exchanges real tokens and stores them in database</done>
|
||||
</task>
|
||||
|
||||
@ -102,7 +102,7 @@ Functional OAuth endpoints that exchange authorization codes for real tokens and
|
||||
- Handle and log token exchange errors appropriately
|
||||
- Store the tokens in the CalendarConnection database record
|
||||
</action>
|
||||
<verify>Run `curl -X GET "http://localhost:3000/api/calendar/apple/oauth?code=valid_code"` and verify tokens are stored in database instead of just console logged</verify>
|
||||
<verify>Run `curl -X GET "http://localhost:3001/api/calendar/apple/oauth?code=valid_code"` and verify tokens are stored in database instead of just console logged</verify>
|
||||
<done>Apple OAuth callback endpoint exchanges real tokens and stores them in database</done>
|
||||
</task>
|
||||
|
||||
|
||||
52
CLAUDE.md
Normal file
52
CLAUDE.md
Normal file
@ -0,0 +1,52 @@
|
||||
# Project Instructions for Claude Code
|
||||
|
||||
## Project Overview
|
||||
My Weekly ToDo List - A web-based weekly task management application built with Next.js.
|
||||
Deployed at: todo.martin-bierschenk.de
|
||||
|
||||
## Git Workflow & Versioning
|
||||
|
||||
### Branch Strategy (Git Flow Lite)
|
||||
- `main` - stable, deployed to todo.martin-bierschenk.de
|
||||
- `dev` - working branch for new features
|
||||
- Feature branches off `dev` for larger work
|
||||
- Merge `dev` → `main` when ready to deploy
|
||||
- `main` always reflects what's live
|
||||
|
||||
### Semantic Versioning (REQUIRED for every commit)
|
||||
Use semver (MAJOR.MINOR.PATCH) in `package.json`:
|
||||
|
||||
**Version bump rules:**
|
||||
- **PATCH** (x.x.+1): Bug fixes, typo corrections, minor CSS tweaks, dependency updates
|
||||
- Commit prefix: `fix:`, `chore:`, `style:`, `docs:`
|
||||
- **MINOR** (x.+1.0): New features, significant UI changes, new integrations
|
||||
- Commit prefix: `feat:`
|
||||
- **MAJOR** (+1.0.0): Breaking changes (database migrations, API changes, auth changes)
|
||||
- Commit prefix: any with `BREAKING CHANGE` in body
|
||||
|
||||
**On every git commit, you MUST:**
|
||||
1. Determine the version bump type based on changes (patch/minor/major)
|
||||
2. Update the `version` field in `package.json`
|
||||
3. Include the new version in the commit message footer, e.g.: `v1.1.0`
|
||||
4. After committing, create a git tag: `git tag v<version>`
|
||||
|
||||
**Commit message format:**
|
||||
```
|
||||
<type>: <description>
|
||||
|
||||
<optional body>
|
||||
|
||||
v<new-version>
|
||||
```
|
||||
|
||||
Example:
|
||||
```
|
||||
feat: add Microsoft To-Do integration
|
||||
|
||||
Sync tasks bidirectionally with Microsoft To-Do API.
|
||||
|
||||
v1.1.0
|
||||
```
|
||||
|
||||
### Current Version
|
||||
Check `package.json` → `version` field for the current version before bumping.
|
||||
@ -47,6 +47,6 @@ USER nextjs
|
||||
EXPOSE 3000
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
|
||||
CMD curl -f http://localhost:3000/api/auth/session || exit 1
|
||||
CMD curl -f http://localhost:3001/api/auth/session || exit 1
|
||||
|
||||
CMD ["npm", "run", "start"]
|
||||
|
||||
@ -26,7 +26,7 @@ This application can be deployed using Docker and Docker Compose.
|
||||
```bash
|
||||
docker-compose up --build
|
||||
```
|
||||
3. Access the application at http://localhost:3000
|
||||
3. Access the application at http://localhost:3001
|
||||
|
||||
### Environment Variables
|
||||
|
||||
|
||||
8
SETUP.md
8
SETUP.md
@ -39,7 +39,7 @@ This will create all necessary tables:
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Visit: **http://localhost:3000**
|
||||
Visit: **http://localhost:3001**
|
||||
|
||||
---
|
||||
|
||||
@ -47,14 +47,14 @@ Visit: **http://localhost:3000**
|
||||
|
||||
### Sign Up (Email/Password)
|
||||
|
||||
1. Go to http://localhost:3000/auth/signup
|
||||
1. Go to http://localhost:3001/auth/signup
|
||||
2. Enter email and password (min 8 characters)
|
||||
3. Click "Sign up"
|
||||
4. You'll be automatically signed in
|
||||
|
||||
### Sign In (Email/Password)
|
||||
|
||||
1. Go to http://localhost:3000/auth/login
|
||||
1. Go to http://localhost:3001/auth/login
|
||||
2. Enter your credentials
|
||||
3. Click "Sign in"
|
||||
|
||||
@ -82,7 +82,7 @@ To enable "Sign in with Google":
|
||||
4. Click **Create Credentials** → **OAuth client ID**
|
||||
5. Choose **Web application**
|
||||
6. Add these redirect URIs:
|
||||
- `http://localhost:3000/api/auth/callback/google`
|
||||
- `http://localhost:3001/api/auth/callback/google`
|
||||
- `https://todo.martin-bierschenk.de/api/auth/callback/google` (via Reverse Proxy)
|
||||
- `https://todo.martin-bierschenk.de/api/calendar/google/oauth` (via Reverse Proxy)
|
||||
7. Copy your **Client ID** and **Client Secret**
|
||||
|
||||
@ -161,7 +161,7 @@ The application follows a standard Next.js 14 architecture with:
|
||||
|
||||
## Assumptions
|
||||
|
||||
1. Application runs on localhost:3000 (development server)
|
||||
1. Application runs on localhost:3001 (development server)
|
||||
2. All components are properly rendered
|
||||
3. Network connectivity allows for API calls
|
||||
4. Required environment variables are configured
|
||||
|
||||
@ -41,7 +41,7 @@ I've created a comprehensive test plan for the My Weekly To Do List application
|
||||
## Implementation Notes
|
||||
|
||||
The test plan assumes:
|
||||
- Application will run on localhost:3000 during testing
|
||||
- Application will run on localhost:3001 during testing
|
||||
- All components are properly implemented and accessible
|
||||
- Environment variables are configured appropriately
|
||||
- Network connectivity supports API interactions
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
// Debug script to inspect login page structure and Tailwind CSS
|
||||
// Run this by visiting http://localhost:3000/auth/login in a browser and using DevTools
|
||||
// Run this by visiting http://localhost:3001/auth/login in a browser and using DevTools
|
||||
|
||||
console.log('=== DEBUGGING LOGIN PAGE STRUCTURE ===');
|
||||
|
||||
// This will be run in browser console after navigating to http://localhost:3000/auth/login
|
||||
// This will be run in browser console after navigating to http://localhost:3001/auth/login
|
||||
|
||||
// 1. Check if page loads properly
|
||||
console.log('1. Checking page load...');
|
||||
@ -23,7 +23,7 @@ console.log('\n3. Checking login elements:');
|
||||
const h1Elements = document.querySelectorAll('h1');
|
||||
console.log('H1 elements count:', h1Elements.length);
|
||||
h1Elements.forEach((el, i) => {
|
||||
console.log(` H1 ${i+1}: "${el.textContent}"`);
|
||||
console.log(` H1 ${i + 1}: "${el.textContent}"`);
|
||||
});
|
||||
|
||||
// 4. Check for form elements
|
||||
@ -59,7 +59,7 @@ const inputs = document.querySelectorAll('input');
|
||||
console.log('Inputs found:', inputs.length);
|
||||
inputs.forEach((input, i) => {
|
||||
if (input.type === 'email' || input.type === 'password') {
|
||||
console.log(` Input ${i+1}: type=${input.type}, name=${input.name}`);
|
||||
console.log(` Input ${i + 1}: type=${input.type}, name=${input.name}`);
|
||||
console.log(` Classes: ${input.className}`);
|
||||
}
|
||||
});
|
||||
@ -67,7 +67,7 @@ inputs.forEach((input, i) => {
|
||||
const buttons = document.querySelectorAll('button');
|
||||
console.log('Buttons found:', buttons.length);
|
||||
buttons.forEach((btn, i) => {
|
||||
console.log(` Button ${i+1}: type=${btn.type}, text="${btn.textContent.substring(0, 30)}..."`);
|
||||
console.log(` Button ${i + 1}: type=${btn.type}, text="${btn.textContent.substring(0, 30)}..."`);
|
||||
});
|
||||
|
||||
// 8. Check for expected text content
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
* This script is meant to be run in the browser console to debug signup issues.
|
||||
*
|
||||
* To use:
|
||||
* 1. Navigate to http://localhost:3000/auth/signup
|
||||
* 1. Navigate to http://localhost:3001/auth/signup
|
||||
* 2. Open browser developer tools (F12)
|
||||
* 3. Go to Console tab
|
||||
* 4. Paste this entire script and run it
|
||||
@ -30,7 +30,7 @@ if (!signupForm) {
|
||||
});
|
||||
} else {
|
||||
console.log('✅ Form found:', signupForm);
|
||||
|
||||
|
||||
// Check form attributes
|
||||
console.log('Form attributes:', {
|
||||
action: signupForm.action,
|
||||
@ -38,28 +38,28 @@ if (!signupForm) {
|
||||
id: signupForm.id,
|
||||
className: signupForm.className
|
||||
});
|
||||
|
||||
|
||||
// Add temporary event listener to see if submit is being called
|
||||
signupForm.addEventListener('submit', function(e) {
|
||||
signupForm.addEventListener('submit', function (e) {
|
||||
console.log('📋 Form submit intercepted');
|
||||
console.log('Submit event:', e);
|
||||
console.log('Form data will be sent to:', this.action || 'default');
|
||||
}, { once: true });
|
||||
|
||||
|
||||
// Check input fields
|
||||
const emailInput = document.querySelector('input[name="email"]');
|
||||
const passwordInput = document.querySelector('input[name="password"]');
|
||||
const submitButton = document.querySelector('button[type="submit"]');
|
||||
|
||||
|
||||
console.log('Input fields:');
|
||||
if (emailInput) console.log('✅ Email input:', emailInput);
|
||||
if (passwordInput) console.log('✅ Password input:', passwordInput);
|
||||
if (submitButton) console.log('✅ Submit button:', submitButton);
|
||||
|
||||
|
||||
// Monitor network requests
|
||||
console.log('🔍 Monitoring network requests...');
|
||||
const originalFetch = window.fetch;
|
||||
window.fetch = function(...args) {
|
||||
window.fetch = function (...args) {
|
||||
console.log('📡 Fetch called with:', args);
|
||||
const [input, init] = args;
|
||||
console.log(' URL:', input);
|
||||
@ -69,9 +69,9 @@ if (!signupForm) {
|
||||
}
|
||||
return originalFetch.apply(this, args);
|
||||
};
|
||||
|
||||
|
||||
// Monitor for errors
|
||||
window.addEventListener('error', function(e) {
|
||||
window.addEventListener('error', function (e) {
|
||||
console.error('🚨 JS Error caught:', e.error);
|
||||
console.error('Error location:', e.filename, e.lineno, e.colno);
|
||||
});
|
||||
|
||||
@ -41,7 +41,7 @@ Since this is a Next.js application, you can deploy it with:
|
||||
|
||||
# Health check
|
||||
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
||||
CMD curl -f http://localhost:3000/health || exit 1
|
||||
CMD curl -f http://localhost:3001/health || exit 1
|
||||
|
||||
# For Next.js, run in production mode
|
||||
CMD ["npm", "run", "start"]
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "my-weekly-todo-list",
|
||||
"version": "1.0.0",
|
||||
"version": "1.1.0",
|
||||
"description": "A web-based weekly task management application that organizes to-dos and calendar events in a single, intuitive weekly view",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
|
||||
@ -26,7 +26,7 @@ export default defineConfig({
|
||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||
use: {
|
||||
/* Base URL to use in actions like `await page.goto('')`. */
|
||||
baseURL: 'http://localhost:3000',
|
||||
baseURL: 'http://localhost:3001',
|
||||
|
||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||
trace: 'on-first-retry',
|
||||
@ -73,7 +73,7 @@ export default defineConfig({
|
||||
/* Run your local dev server before starting the tests */
|
||||
// webServer: {
|
||||
// command: 'npm run start',
|
||||
// url: 'http://localhost:3000',
|
||||
// url: 'http://localhost:3001',
|
||||
// reuseExistingServer: !process.env.CI,
|
||||
// },
|
||||
});
|
||||
|
||||
@ -465,6 +465,8 @@ h3 {
|
||||
--weekly-settings-toggle-active-bg: white;
|
||||
--weekly-settings-toggle-text: #6b7280;
|
||||
--weekly-settings-toggle-active-text: #111827;
|
||||
--weekly-header-brightness: 1;
|
||||
--weekly-goal-brightness: 1;
|
||||
}
|
||||
|
||||
/* Weekly Main Container */
|
||||
@ -709,7 +711,9 @@ h3 {
|
||||
position: relative;
|
||||
transition: all 0.2s ease;
|
||||
border-bottom: 1px solid var(--weekly-border); /* Restore lines */
|
||||
line-height: initial;
|
||||
line-height: 1.4;
|
||||
height: auto;
|
||||
min-height: 32px;
|
||||
}
|
||||
|
||||
/* Remove border from last item to look cleaner, or keep for paper look */
|
||||
@ -737,6 +741,9 @@ h3 {
|
||||
font-weight: var(--weekly-task-weight, 400);
|
||||
line-height: var(--weekly-task-line-height, 1.5);
|
||||
color: var(--weekly-task-color, var(--weekly-text));
|
||||
white-space: normal;
|
||||
word-break: break-word;
|
||||
overflow: visible;
|
||||
border: none;
|
||||
outline: none;
|
||||
background: transparent;
|
||||
@ -1264,19 +1271,20 @@ h3 {
|
||||
|
||||
.weekly-someday-list .weekly-task-item {
|
||||
border-bottom: 1px solid transparent;
|
||||
height: 32px;
|
||||
padding: 0 1rem;
|
||||
min-height: 32px;
|
||||
height: auto;
|
||||
padding: 4px 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.weekly-someday-list .weekly-task-text {
|
||||
font-size: var(--base-font-size);
|
||||
line-height: 32px;
|
||||
max-height: 32px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
line-height: 1.4;
|
||||
word-break: break-word;
|
||||
overflow: visible;
|
||||
white-space: normal;
|
||||
text-overflow: clip;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
@ -1607,6 +1615,8 @@ h3 {
|
||||
--weekly-settings-text: #ffffff;
|
||||
--weekly-settings-toggle-bg: #222222;
|
||||
--weekly-settings-toggle-active-bg: #333333;
|
||||
--weekly-header-brightness: 1.5;
|
||||
--weekly-goal-brightness: 1.5;
|
||||
--weekly-settings-toggle-text: #888888;
|
||||
--weekly-settings-toggle-active-text: #ffffff;
|
||||
}
|
||||
|
||||
@ -39,6 +39,7 @@ import {
|
||||
Palette,
|
||||
Sparkles,
|
||||
Info,
|
||||
Trash2,
|
||||
} from "lucide-react";
|
||||
|
||||
// Types
|
||||
@ -418,6 +419,29 @@ function invertColor(hex: string): string {
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to lighten color for dark mode
|
||||
function adjustColorForDarkMode(hex: string, isDarkMode: boolean): string {
|
||||
if (!isDarkMode || !hex || !hex.startsWith("#")) return hex;
|
||||
|
||||
// Simple hex to RGB
|
||||
let r = parseInt(hex.slice(1, 3), 16);
|
||||
let g = parseInt(hex.slice(3, 5), 16);
|
||||
let b = parseInt(hex.slice(5, 7), 16);
|
||||
|
||||
// Calculate brightness (0-255)
|
||||
const brightness = (r * 299 + g * 587 + b * 114) / 1000;
|
||||
|
||||
// If it's too dark for dark mode, lighten it
|
||||
if (brightness < 120) {
|
||||
r = Math.min(255, r + 100);
|
||||
g = Math.min(255, g + 100);
|
||||
b = Math.min(255, b + 100);
|
||||
return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
|
||||
}
|
||||
|
||||
return hex;
|
||||
}
|
||||
|
||||
// Main Component
|
||||
export default function WeeklyView() {
|
||||
const { data: session } = useSession();
|
||||
@ -558,6 +582,7 @@ export default function WeeklyView() {
|
||||
yearFontSize?: string;
|
||||
yearFontWeight?: string;
|
||||
yearColor?: string;
|
||||
quoteSourceUrls?: string[];
|
||||
}>({
|
||||
name: session?.user?.name || "",
|
||||
email: session?.user?.email || "",
|
||||
@ -609,6 +634,7 @@ export default function WeeklyView() {
|
||||
weekendColorSun: "#dc2626",
|
||||
pastDayColor: "#a6a6a7",
|
||||
quoteSourceUrl: "https://recite.vercel.app/api/random",
|
||||
quoteSourceUrls: ["https://recite.vercel.app/api/random"],
|
||||
});
|
||||
const [motivationalQuote, setMotivationalQuote] = useState("");
|
||||
const [showSummary, setShowSummary] = useState(false);
|
||||
@ -945,31 +971,42 @@ export default function WeeklyView() {
|
||||
|
||||
const fetchMotivationalQuote = useCallback(async () => {
|
||||
if (profile.goalFallbackType !== "quote") return;
|
||||
try {
|
||||
const url = profile.quoteSourceUrl || "https://recite.vercel.app/api/random";
|
||||
const res = await fetch(url);
|
||||
if (!res.ok) throw new Error("Failed to fetch quote");
|
||||
const data = await res.json();
|
||||
|
||||
// Handle different JSON formats (array or object)
|
||||
let quoteText = "";
|
||||
if (Array.isArray(data) && data.length > 0) {
|
||||
const item = data[0];
|
||||
quoteText = item.quote || item.text || item.content || (typeof item === 'string' ? item : "");
|
||||
if (item.author) quoteText += ` - ${item.author}`;
|
||||
} else if (data && typeof data === 'object') {
|
||||
quoteText = data.quote || data.text || data.content || "";
|
||||
if (data.author) quoteText += ` - ${data.author}`;
|
||||
} else if (typeof data === 'string') {
|
||||
quoteText = data;
|
||||
const urls = profile.quoteSourceUrls && profile.quoteSourceUrls.length > 0
|
||||
? profile.quoteSourceUrls
|
||||
: [profile.quoteSourceUrl || "https://recite.vercel.app/api/random"];
|
||||
|
||||
// Strategy: try sources until one works
|
||||
for (const url of urls) {
|
||||
try {
|
||||
const res = await fetch(url);
|
||||
if (!res.ok) continue;
|
||||
const data = await res.json();
|
||||
|
||||
let quoteText = "";
|
||||
if (Array.isArray(data) && data.length > 0) {
|
||||
const item = data[0];
|
||||
quoteText = item.quote || item.text || item.content || (typeof item === 'string' ? item : "");
|
||||
if (item.author) quoteText += ` - ${item.author}`;
|
||||
} else if (data && typeof data === 'object') {
|
||||
quoteText = data.quote || data.text || data.content || "";
|
||||
if (data.author) quoteText += ` - ${data.author}`;
|
||||
} else if (typeof data === 'string') {
|
||||
quoteText = data;
|
||||
}
|
||||
|
||||
if (quoteText) {
|
||||
setMotivationalQuote(quoteText);
|
||||
return; // Success!
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error fetching quote from ${url}:`, error);
|
||||
}
|
||||
|
||||
if (quoteText) setMotivationalQuote(quoteText);
|
||||
} catch (error) {
|
||||
console.error("Error fetching motivational quote:", error);
|
||||
setMotivationalQuote("Stay focused and productive.");
|
||||
}
|
||||
}, [profile.goalFallbackType, profile.quoteSourceUrl]);
|
||||
|
||||
// Final fallback if all failed
|
||||
setMotivationalQuote("Stay focused and productive.");
|
||||
}, [profile.goalFallbackType, profile.quoteSourceUrl, profile.quoteSourceUrls]);
|
||||
|
||||
// Fetch tasks on mount
|
||||
useEffect(() => {
|
||||
@ -1457,15 +1494,17 @@ export default function WeeklyView() {
|
||||
const dateStr = formatDateToISO(date); // Use local date formatting
|
||||
return tasks
|
||||
.filter((task) => {
|
||||
if (!task.scheduledDate) return false;
|
||||
// Exclude sub-tasks from top-level list (they render inside their parent)
|
||||
if (task.parentTaskId) return false;
|
||||
// Use string comparison to avoid timezone shifts
|
||||
const taskDateStr =
|
||||
typeof task.scheduledDate === "string"
|
||||
? task.scheduledDate.substring(0, 10)
|
||||
: formatDateToISO(new Date(task.scheduledDate));
|
||||
return taskDateStr === dateStr;
|
||||
if (!task.completed && task.scheduledDate) {
|
||||
// Exclude sub-tasks from top-level list (they render inside their parent)
|
||||
if (task.parentTaskId) return false;
|
||||
// Use string comparison to avoid timezone shifts
|
||||
const taskDateStr =
|
||||
typeof task.scheduledDate === "string"
|
||||
? task.scheduledDate.substring(0, 10)
|
||||
: formatDateToISO(new Date(task.scheduledDate));
|
||||
return taskDateStr === dateStr;
|
||||
}
|
||||
return false;
|
||||
})
|
||||
.sort((a, b) => {
|
||||
// Sort by time if available
|
||||
@ -1798,6 +1837,9 @@ export default function WeeklyView() {
|
||||
const taskDuration = task.duration || 15;
|
||||
const taskEnd = taskStart + taskDuration;
|
||||
|
||||
// Skip completed tasks (they don't render in the grid)
|
||||
if (task.completed) return false;
|
||||
|
||||
// Overlap condition: task starts before slot ends AND task ends after slot starts
|
||||
return taskStart < slotEnd && taskEnd > slotStart;
|
||||
});
|
||||
@ -3270,27 +3312,39 @@ export default function WeeklyView() {
|
||||
</div>
|
||||
|
||||
{/* CENTER SECTION: Week/Year, Goal, Focus Mode - Reveal on Hover */}
|
||||
<div className="flex items-center justify-center gap-6 absolute left-1/2 transform -translate-x-1/2">
|
||||
<div className="flex items-center justify-center gap-6 absolute left-1/2 transform -translate-x-1/2 group">
|
||||
{/* Week & Year */}
|
||||
<div className="whitespace-nowrap flex items-center gap-2">
|
||||
{(isLoading || isSyncing || syncStatus === "syncing") && (
|
||||
{(isLoading || isSyncing || syncStatus === "syncing") ? (
|
||||
<div className="weekly-spinner" title="Syncing..."></div>
|
||||
) : (
|
||||
<button
|
||||
onClick={() => { fetchCalendarEvents(); fetchTasks(); }}
|
||||
className="p-1 rounded-full hover:bg-gray-100 dark:hover:bg-gray-800 transition-all text-gray-400 hover:text-gray-600 opacity-0 group-hover:opacity-100"
|
||||
title="Refresh Calendar & Tasks"
|
||||
>
|
||||
<RefreshCcw size={14} />
|
||||
</button>
|
||||
)}
|
||||
<span style={{
|
||||
fontFamily: profile.cwFontFamily || "Inter",
|
||||
fontSize: profile.cwFontSize || "1.125rem",
|
||||
fontWeight: Number(profile.cwFontWeight || "700"),
|
||||
color: profile.cwColor || "#333333",
|
||||
color: adjustColorForDarkMode(profile.cwColor || "#333333", darkMode),
|
||||
filter: "brightness(var(--weekly-header-brightness, 1))"
|
||||
}}>
|
||||
KW {getWeekNumber(currentWeekStart).toString().padStart(2, "0")}
|
||||
</span>
|
||||
<span className="text-gray-400">|</span>
|
||||
<span style={{
|
||||
fontFamily: profile.yearFontFamily || "Inter",
|
||||
fontSize: profile.yearFontSize || "1.125rem",
|
||||
fontWeight: Number(profile.yearFontWeight || "700"),
|
||||
color: profile.yearColor || "#333333",
|
||||
}}>
|
||||
<span
|
||||
style={{
|
||||
fontFamily: profile.yearFontFamily || "Inter",
|
||||
fontSize: profile.yearFontSize || "1.125rem",
|
||||
fontWeight: Number(profile.yearFontWeight || "700"),
|
||||
color: adjustColorForDarkMode(profile.yearColor || "#333333", darkMode),
|
||||
filter: "brightness(var(--weekly-header-brightness, 1))"
|
||||
}}
|
||||
>
|
||||
{currentWeekStart.getFullYear()}
|
||||
</span>
|
||||
</div>
|
||||
@ -3330,6 +3384,8 @@ export default function WeeklyView() {
|
||||
: undefined,
|
||||
fontSize: profile.goalFontSize || undefined,
|
||||
fontWeight: profile.goalFontWeight || undefined,
|
||||
color: adjustColorForDarkMode((profile.goalFallbackType === "quote" ? profile.taskColor : undefined) || "#333333", darkMode),
|
||||
filter: "brightness(var(--weekly-goal-brightness, 1))"
|
||||
}}
|
||||
>
|
||||
{showNextTask
|
||||
@ -5640,7 +5696,15 @@ function TaskItem({
|
||||
</svg>
|
||||
</span>
|
||||
)}
|
||||
<span className="truncate">{task.title}</span>
|
||||
<span
|
||||
style={{
|
||||
whiteSpace: "normal",
|
||||
wordBreak: "break-word",
|
||||
overflow: "visible"
|
||||
}}
|
||||
>
|
||||
{task.title}
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<div className="task-actions absolute right-0 top-0 bottom-0 flex items-center bg-white/95 dark:bg-gray-800/95 pl-2 pr-1 shadow-[-12px_0_12px_-4px_rgba(255,255,255,0.95)] dark:shadow-[-12px_0_12px_-4px_rgba(31,41,55,0.95)] z-20">
|
||||
@ -6062,7 +6126,9 @@ interface SettingsSidebarProps {
|
||||
yearFontSize?: string;
|
||||
yearFontWeight?: string;
|
||||
yearColor?: string;
|
||||
quoteSourceUrls: string[];
|
||||
}) => void;
|
||||
quoteSourceUrls?: string[];
|
||||
goal: string;
|
||||
setGoal: (goal: string) => void;
|
||||
saveGoal: (goal: string) => void;
|
||||
@ -6288,6 +6354,7 @@ function SettingsSidebar({
|
||||
yearFontSize?: string;
|
||||
yearFontWeight?: string;
|
||||
yearColor?: string;
|
||||
quoteSourceUrls?: string[];
|
||||
}>({
|
||||
name: "",
|
||||
email: "",
|
||||
@ -8995,7 +9062,7 @@ function SettingsSidebar({
|
||||
flex: 1,
|
||||
padding: "10px",
|
||||
borderRadius: "6px",
|
||||
border: "none",
|
||||
border: profile.goalScope === "week" ? "2px solid var(--weekly-teal)" : "none",
|
||||
background:
|
||||
profile.goalScope === "week"
|
||||
? "var(--weekly-settings-toggle-active-bg)"
|
||||
@ -9004,7 +9071,8 @@ function SettingsSidebar({
|
||||
profile.goalScope === "week"
|
||||
? "var(--weekly-settings-toggle-active-text)"
|
||||
: "var(--weekly-settings-text)",
|
||||
fontWeight: 600,
|
||||
fontWeight: 700,
|
||||
boxShadow: profile.goalScope === "week" ? "0 2px 4px rgba(0,0,154,0.1)" : "none",
|
||||
cursor: "pointer",
|
||||
transition: "all 0.2s",
|
||||
}}
|
||||
@ -9020,7 +9088,7 @@ function SettingsSidebar({
|
||||
flex: 1,
|
||||
padding: "10px",
|
||||
borderRadius: "6px",
|
||||
border: "none",
|
||||
border: profile.goalScope === "day" ? "2px solid var(--weekly-teal)" : "none",
|
||||
background:
|
||||
profile.goalScope === "day"
|
||||
? "var(--weekly-settings-toggle-active-bg)"
|
||||
@ -9029,7 +9097,8 @@ function SettingsSidebar({
|
||||
profile.goalScope === "day"
|
||||
? "var(--weekly-settings-toggle-active-text)"
|
||||
: "var(--weekly-settings-text)",
|
||||
fontWeight: 600,
|
||||
fontWeight: 700,
|
||||
boxShadow: profile.goalScope === "day" ? "0 2px 4px rgba(0,154,154,0.1)" : "none",
|
||||
cursor: "pointer",
|
||||
transition: "all 0.2s",
|
||||
}}
|
||||
@ -9104,28 +9173,73 @@ function SettingsSidebar({
|
||||
color: "var(--weekly-settings-label)",
|
||||
}}
|
||||
>
|
||||
API-Datenquelle (URL)
|
||||
API-Datenquellen (URLs)
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={profile.quoteSourceUrl || ""}
|
||||
onChange={(e) =>
|
||||
setProfile((p) => ({
|
||||
...p,
|
||||
quoteSourceUrl: e.target.value,
|
||||
}))
|
||||
}
|
||||
className="weekly-input"
|
||||
placeholder="https://recite.vercel.app/api/random"
|
||||
style={{
|
||||
width: "100%",
|
||||
padding: "10px",
|
||||
fontSize: "0.95rem",
|
||||
borderRadius: "6px",
|
||||
border: "1px solid var(--weekly-border)",
|
||||
background: "var(--weekly-bg)",
|
||||
}}
|
||||
/>
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: "8px" }}>
|
||||
{(profile.quoteSourceUrls || [profile.quoteSourceUrl || "https://recite.vercel.app/api/random"]).map((url: string, idx: number) => (
|
||||
<div key={idx} style={{ display: "flex", gap: "8px" }}>
|
||||
<input
|
||||
type="text"
|
||||
value={url}
|
||||
onChange={(e) => {
|
||||
const newUrls = [...(profile.quoteSourceUrls || [profile.quoteSourceUrl || "https://recite.vercel.app/api/random"])];
|
||||
newUrls[idx] = e.target.value;
|
||||
setProfile((p) => ({ ...p, quoteSourceUrls: newUrls }));
|
||||
}}
|
||||
className="weekly-input"
|
||||
placeholder="https://..."
|
||||
style={{
|
||||
flex: 1,
|
||||
padding: "10px",
|
||||
fontSize: "0.95rem",
|
||||
borderRadius: "6px",
|
||||
border: "1px solid var(--weekly-border)",
|
||||
background: "var(--weekly-bg)",
|
||||
}}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
const newUrls = (profile.quoteSourceUrls || [profile.quoteSourceUrl || "https://recite.vercel.app/api/random"]).filter((_val: string, i: number) => i !== idx);
|
||||
setProfile((p) => ({ ...p, quoteSourceUrls: newUrls }));
|
||||
}}
|
||||
style={{
|
||||
padding: "8px",
|
||||
background: "#fee2e2",
|
||||
color: "#ef4444",
|
||||
border: "none",
|
||||
borderRadius: "6px",
|
||||
cursor: "pointer"
|
||||
}}
|
||||
>
|
||||
<Trash2 size={16} />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
const newUrls = [...(profile.quoteSourceUrls || [profile.quoteSourceUrl || "https://recite.vercel.app/api/random"]), ""];
|
||||
setProfile((p) => ({ ...p, quoteSourceUrls: newUrls }));
|
||||
}}
|
||||
style={{
|
||||
alignSelf: "flex-start",
|
||||
marginTop: "4px",
|
||||
padding: "6px 12px",
|
||||
fontSize: "0.85rem",
|
||||
background: "var(--weekly-teal)",
|
||||
color: "white",
|
||||
border: "none",
|
||||
borderRadius: "6px",
|
||||
cursor: "pointer",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "4px"
|
||||
}}
|
||||
>
|
||||
<Plus size={14} /> Add Source
|
||||
</button>
|
||||
</div>
|
||||
<p style={{ fontSize: "0.75rem", color: "var(--weekly-text-light)", marginTop: "4px" }}>
|
||||
URL that returns a JSON list or object of quotes (e.g. {'[{"quote":"...","author":"..."}]'} or {'{"quote":"..."}'}).
|
||||
</p>
|
||||
|
||||
@ -4,18 +4,18 @@
|
||||
*
|
||||
* Usage:
|
||||
* 1. Start the development server: npm run dev
|
||||
* 2. Visit http://localhost:3000/auth/signup in your browser
|
||||
* 2. Visit http://localhost:3001/auth/signup in your browser
|
||||
* 3. Fill in the form with:
|
||||
* - Email: mail@martin-bierschenk.de
|
||||
* - Password: My-Weekly-ToDo-List
|
||||
* 4. Or use the following curl command:
|
||||
*
|
||||
* curl -X POST http://localhost:3000/api/auth/signup \
|
||||
* curl -X POST http://localhost:3001/api/auth/signup \
|
||||
* -H "Content-Type: application/json" \
|
||||
* -d '{"email":"mail@martin-bierschenk.de","password":"My-Weekly-ToDo-List"}'
|
||||
*
|
||||
* For login after signup:
|
||||
* curl -X POST http://localhost:3000/api/auth/login \
|
||||
* curl -X POST http://localhost:3001/api/auth/login \
|
||||
* -H "Content-Type: application/json" \
|
||||
* -d '{"email":"mail@martin-bierschenk.de","password":"My-Weekly-ToDo-List"}'
|
||||
*/
|
||||
@ -32,18 +32,18 @@ console.log('Email: mail@martin-bierschenk.de');
|
||||
console.log('Password: My-Weekly-ToDo-List');
|
||||
console.log('');
|
||||
console.log('Method 1: Using Browser Interface');
|
||||
console.log('1. Navigate to http://localhost:3000/auth/signup');
|
||||
console.log('1. Navigate to http://localhost:3001/auth/signup');
|
||||
console.log('2. Fill in the form with the credentials above');
|
||||
console.log('3. Submit the form');
|
||||
console.log('');
|
||||
console.log('Method 2: Using curl commands (in terminal)');
|
||||
console.log('First, create the account:');
|
||||
console.log('curl -X POST http://localhost:3000/api/auth/signup \\');
|
||||
console.log('curl -X POST http://localhost:3001/api/auth/signup \\');
|
||||
console.log(' -H "Content-Type: application/json" \\');
|
||||
console.log(' -d \'{"email":"mail@martin-bierschenk.de","password":"My-Weekly-ToDo-List"}\'');
|
||||
console.log('');
|
||||
console.log('Then, authenticate with the account:');
|
||||
console.log('curl -X POST http://localhost:3000/api/auth/login \\');
|
||||
console.log('curl -X POST http://localhost:3001/api/auth/login \\');
|
||||
console.log(' -H "Content-Type: application/json" \\');
|
||||
console.log(' -d \'{"email":"mail@martin-bierschenk.de","password":"My-Weekly-ToDo-List"}\'');
|
||||
console.log('');
|
||||
|
||||
12
test-api.js
12
test-api.js
@ -5,9 +5,9 @@ const fetch = require('node-fetch');
|
||||
|
||||
async function testSignupApi() {
|
||||
console.log('Testing signup API endpoint...');
|
||||
|
||||
|
||||
try {
|
||||
const response = await fetch('http://localhost:3000/api/auth/signup', {
|
||||
const response = await fetch('http://localhost:3001/api/auth/signup', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@ -17,13 +17,13 @@ async function testSignupApi() {
|
||||
password: 'testpassword'
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
console.log('Status:', response.status);
|
||||
console.log('Headers:', response.headers.raw());
|
||||
|
||||
|
||||
const contentType = response.headers.get('content-type');
|
||||
console.log('Content-Type:', contentType);
|
||||
|
||||
|
||||
if (contentType && contentType.includes('application/json')) {
|
||||
const data = await response.json();
|
||||
console.log('JSON Response:', data);
|
||||
@ -32,7 +32,7 @@ async function testSignupApi() {
|
||||
console.log('Non-JSON Response (likely HTML):');
|
||||
console.log(text.substring(0, 500) + '...');
|
||||
}
|
||||
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error testing API:', error);
|
||||
}
|
||||
|
||||
@ -3,11 +3,11 @@ import { test, expect } from '@playwright/test';
|
||||
// Test authentication flow against your running application
|
||||
test.describe('Authentication Flow Tests', () => {
|
||||
test('should access login page', async ({ page }) => {
|
||||
await page.goto('http://localhost:3000/auth/login');
|
||||
|
||||
await page.goto('http://localhost:3001/auth/login');
|
||||
|
||||
// Check that we're on the login page
|
||||
await expect(page).toHaveTitle(/Login/);
|
||||
|
||||
|
||||
// Check for login form elements
|
||||
await expect(page.getByText('Login')).toBeVisible();
|
||||
await expect(page.getByPlaceholder('Email')).toBeVisible();
|
||||
@ -16,11 +16,11 @@ test.describe('Authentication Flow Tests', () => {
|
||||
});
|
||||
|
||||
test('should access signup page', async ({ page }) => {
|
||||
await page.goto('http://localhost:3000/auth/signup');
|
||||
|
||||
await page.goto('http://localhost:3001/auth/signup');
|
||||
|
||||
// Check that we're on the signup page
|
||||
await expect(page).toHaveTitle(/Sign Up/);
|
||||
|
||||
|
||||
// Check for signup form elements
|
||||
await expect(page.getByText('Sign Up')).toBeVisible();
|
||||
await expect(page.getByPlaceholder('Email')).toBeVisible();
|
||||
|
||||
@ -3,16 +3,16 @@ import { test, expect } from '@playwright/test';
|
||||
// Test to verify CSS styling is working in your application
|
||||
test.describe('CSS Debugging Tests', () => {
|
||||
test('should load with proper CSS styling', async ({ page }) => {
|
||||
await page.goto('http://localhost:3000');
|
||||
|
||||
await page.goto('http://localhost:3001');
|
||||
|
||||
// Verify main page elements have proper styling
|
||||
const mainHeader = page.getByText('My Weekly To Do List');
|
||||
await expect(mainHeader).toBeVisible();
|
||||
|
||||
|
||||
// Check for CSS classes that indicate styling is applied
|
||||
const taskItem = page.locator('.task-item');
|
||||
await expect(taskItem).toBeVisible();
|
||||
|
||||
|
||||
// Verify basic CSS properties are applied
|
||||
const taskItemStyles = await taskItem.evaluate(element => {
|
||||
const computedStyle = window.getComputedStyle(element);
|
||||
@ -22,18 +22,18 @@ test.describe('CSS Debugging Tests', () => {
|
||||
boxShadow: computedStyle.boxShadow
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
console.log('Task item CSS properties:', taskItemStyles);
|
||||
expect(taskItemStyles.backgroundColor).toBeTruthy();
|
||||
});
|
||||
|
||||
test('should have proper auth form styling', async ({ page }) => {
|
||||
await page.goto('http://localhost:3000/auth/login');
|
||||
|
||||
await page.goto('http://localhost:3001/auth/login');
|
||||
|
||||
// Check that auth form has proper styling
|
||||
const authForm = page.locator('.auth-form');
|
||||
await expect(authForm).toBeVisible();
|
||||
|
||||
|
||||
// Verify CSS properties on auth form
|
||||
const formStyles = await authForm.evaluate(element => {
|
||||
const computedStyle = window.getComputedStyle(element);
|
||||
@ -43,7 +43,7 @@ test.describe('CSS Debugging Tests', () => {
|
||||
boxShadow: computedStyle.boxShadow
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
console.log('Auth form CSS properties:', formStyles);
|
||||
expect(formStyles.backgroundColor).toBeTruthy();
|
||||
});
|
||||
|
||||
@ -3,19 +3,19 @@ import { test, expect } from '@playwright/test';
|
||||
// Simple test to verify the app is running and accessible
|
||||
test('My Weekly To Do List should be accessible', async ({ page }) => {
|
||||
// Navigate to the running application
|
||||
await page.goto('http://localhost:3000');
|
||||
|
||||
await page.goto('http://localhost:3001');
|
||||
|
||||
// Check that the main elements are present
|
||||
await expect(page).toHaveTitle(/My Weekly To Do List/);
|
||||
|
||||
|
||||
// Check for main UI elements
|
||||
await expect(page.getByText('My Weekly To Do List')).toBeVisible();
|
||||
await expect(page.getByText('Weekly View')).toBeVisible();
|
||||
});
|
||||
|
||||
test('Should be able to access tasks page', async ({ page }) => {
|
||||
await page.goto('http://localhost:3000');
|
||||
|
||||
await page.goto('http://localhost:3001');
|
||||
|
||||
// Try to navigate to tasks page
|
||||
const tasksLink = page.getByText('Tasks');
|
||||
await expect(tasksLink).toBeVisible();
|
||||
|
||||
@ -2,50 +2,50 @@ import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('Login Page Core Functionality', () => {
|
||||
test('should load the login page with core elements', async ({ page }) => {
|
||||
await page.goto('http://localhost:3000/auth/login');
|
||||
|
||||
await page.goto('http://localhost:3001/auth/login');
|
||||
|
||||
// Basic page verification
|
||||
await expect(page).toHaveURL(/login/);
|
||||
await expect(page).toHaveTitle(/Weekly To Do List/);
|
||||
|
||||
|
||||
// Check that the page has essential structure
|
||||
const h1Element = page.locator('h1');
|
||||
await expect(h1Element).toBeVisible();
|
||||
|
||||
|
||||
// Check for a login-related paragraph
|
||||
const loginParagraph = page.getByText(/Sign in to your account|Welcome back!/);
|
||||
await expect(loginParagraph).toBeVisible();
|
||||
|
||||
|
||||
// Check for form elements
|
||||
const emailInput = page.locator('input[name="email"]');
|
||||
const passwordInput = page.locator('input[name="password"]');
|
||||
const submitButton = page.locator('button[type="submit"]');
|
||||
|
||||
|
||||
await expect(emailInput).toBeVisible();
|
||||
await expect(passwordInput).toBeVisible();
|
||||
await expect(submitButton).toBeVisible();
|
||||
|
||||
|
||||
// Check for links
|
||||
const signupLink = page.getByRole('link', { name: /Don't have an account|Create account/ });
|
||||
const forgotPasswordLink = page.getByRole('link', { name: /Forgot your password/ });
|
||||
|
||||
|
||||
await expect(signupLink).toBeVisible();
|
||||
await expect(forgotPasswordLink).toBeVisible();
|
||||
|
||||
|
||||
console.log('✅ Login page loaded successfully with core elements');
|
||||
});
|
||||
|
||||
test('should have basic CSS structure', async ({ page }) => {
|
||||
await page.goto('http://localhost:3000/auth/login');
|
||||
|
||||
await page.goto('http://localhost:3001/auth/login');
|
||||
|
||||
// Check that the page has basic styling structure
|
||||
const body = page.locator('body');
|
||||
await expect(body).toBeVisible();
|
||||
|
||||
|
||||
// Check for main wrapper div that should have Tailwind classes
|
||||
const mainWrapper = page.locator('[class*="min-h-screen"]');
|
||||
await expect(mainWrapper).toBeVisible();
|
||||
|
||||
|
||||
console.log('✅ Basic CSS structure verified');
|
||||
});
|
||||
});
|
||||
@ -2,37 +2,37 @@ import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('Login Page Debug - Simplified', () => {
|
||||
test('should load login page successfully', async ({ page }) => {
|
||||
await page.goto('http://localhost:3000/auth/login');
|
||||
|
||||
await page.goto('http://localhost:3001/auth/login');
|
||||
|
||||
// Basic page load verification
|
||||
await expect(page).toHaveURL(/login/);
|
||||
await expect(page).toHaveTitle(/Weekly To Do List/);
|
||||
|
||||
|
||||
// Check for key elements that should be present
|
||||
await expect(page.locator('h1')).toContainText('Weekly To Do List');
|
||||
await expect(page.locator('p')).toContainText('Sign in to your account');
|
||||
|
||||
|
||||
// Check for form elements
|
||||
await expect(page.locator('input[name="email"]')).toBeVisible();
|
||||
await expect(page.locator('input[name="password"]')).toBeVisible();
|
||||
await expect(page.locator('button[type="submit"]')).toBeVisible();
|
||||
|
||||
|
||||
// Check for navigation links
|
||||
await expect(page.getByRole('link', { name: 'Don\'t have an account?' })).toBeVisible();
|
||||
await expect(page.getByRole('link', { name: 'Forgot your password?' })).toBeVisible();
|
||||
});
|
||||
|
||||
test('should have Tailwind CSS classes in structure', async ({ page }) => {
|
||||
await page.goto('http://localhost:3000/auth/login');
|
||||
|
||||
await page.goto('http://localhost:3001/auth/login');
|
||||
|
||||
// Check main container classes
|
||||
const container = page.locator('div.min-h-screen.bg-gray-50');
|
||||
await expect(container).toBeVisible();
|
||||
|
||||
|
||||
// Check the form container
|
||||
const formContainer = page.locator('div.bg-white.rounded-xl.shadow-lg.p-8');
|
||||
await expect(formContainer).toBeVisible();
|
||||
|
||||
|
||||
// Check for basic Tailwind classes that should be present
|
||||
const bodyClass = await page.evaluate(() => document.body.className);
|
||||
expect(bodyClass).toContain('min-h-screen');
|
||||
@ -40,19 +40,19 @@ test.describe('Login Page Debug - Simplified', () => {
|
||||
});
|
||||
|
||||
test('should have functional form elements', async ({ page }) => {
|
||||
await page.goto('http://localhost:3000/auth/login');
|
||||
|
||||
await page.goto('http://localhost:3001/auth/login');
|
||||
|
||||
// Test that the form exists
|
||||
const form = page.locator('form');
|
||||
await expect(form).toBeVisible();
|
||||
|
||||
|
||||
// Test that we can interact with inputs
|
||||
const emailInput = page.locator('input[name="email"]');
|
||||
const passwordInput = page.locator('input[name="password"]');
|
||||
|
||||
|
||||
await expect(emailInput).toBeVisible();
|
||||
await expect(passwordInput).toBeVisible();
|
||||
|
||||
|
||||
// Test that submit button exists
|
||||
const submitButton = page.locator('button[type="submit"]');
|
||||
await expect(submitButton).toBeVisible();
|
||||
|
||||
@ -2,50 +2,50 @@ import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('Login Page Debug Test', () => {
|
||||
test('should load the login page correctly', async ({ page }) => {
|
||||
await page.goto('http://localhost:3000/auth/login');
|
||||
|
||||
await page.goto('http://localhost:3001/auth/login');
|
||||
|
||||
// Check page title and heading
|
||||
await expect(page).toHaveTitle(/Weekly To Do List/);
|
||||
await expect(page.locator('h1')).toContainText('Weekly To Do List');
|
||||
await expect(page.locator('p:has-text("Sign in to your account")')).toBeVisible();
|
||||
|
||||
|
||||
// Check for Tailwind CSS classes in main container
|
||||
const mainContainer = page.locator('div.min-h-screen.bg-gray-50');
|
||||
await expect(mainContainer).toBeVisible();
|
||||
|
||||
|
||||
// Check for form elements
|
||||
const form = page.locator('form');
|
||||
await expect(form).toBeVisible();
|
||||
|
||||
|
||||
// Check for email input field
|
||||
const emailInput = page.locator('#email-address');
|
||||
await expect(emailInput).toBeVisible();
|
||||
await expect(emailInput).toHaveAttribute('type', 'email');
|
||||
await expect(emailInput).toHaveAttribute('placeholder', 'Email address');
|
||||
|
||||
|
||||
// Check for password input field
|
||||
const passwordInput = page.locator('#password');
|
||||
await expect(passwordInput).toBeVisible();
|
||||
await expect(passwordInput).toHaveAttribute('type', 'password');
|
||||
await expect(passwordInput).toHaveAttribute('placeholder', 'Password');
|
||||
|
||||
|
||||
// Check for submit button
|
||||
const submitButton = page.locator('button[type="submit"]');
|
||||
await expect(submitButton).toBeVisible();
|
||||
await expect(submitButton).toContainText('Sign in');
|
||||
|
||||
|
||||
// Check for links
|
||||
await expect(page.locator('a:has-text("Don\'t have an account?")')).toBeVisible();
|
||||
await expect(page.locator('a:has-text("Forgot your password?")')).toBeVisible();
|
||||
|
||||
|
||||
// Check for Tailwind classes in form container
|
||||
const formContainer = page.locator('div.max-w-md.w-full.space-y-8.bg-white.rounded-xl.shadow-lg.p-8');
|
||||
await expect(formContainer).toBeVisible();
|
||||
});
|
||||
|
||||
test('should have Tailwind CSS applied correctly', async ({ page }) => {
|
||||
await page.goto('http://localhost:3000/auth/login');
|
||||
|
||||
await page.goto('http://localhost:3001/auth/login');
|
||||
|
||||
// Check for various Tailwind utility classes
|
||||
const expectedClasses = [
|
||||
'min-h-screen',
|
||||
@ -174,21 +174,21 @@ test.describe('Login Page Debug Test', () => {
|
||||
'text-sm',
|
||||
'text-gray-600'
|
||||
];
|
||||
|
||||
|
||||
// Check that the page contains at least one element with Tailwind class
|
||||
const bodyElement = page.locator('body');
|
||||
const bodyClasses = await bodyElement.getAttribute('class');
|
||||
expect(bodyClasses).toBeTruthy();
|
||||
|
||||
|
||||
// Check for some specific Tailwind classes that should be present
|
||||
expect(bodyClasses).toContain('min-h-screen');
|
||||
expect(bodyClasses).toContain('bg-gray-50');
|
||||
|
||||
|
||||
// Check that the form has appropriate Tailwind classes
|
||||
const formElement = page.locator('form');
|
||||
const formClasses = await formElement.getAttribute('class');
|
||||
expect(formClasses).toContain('space-y-6');
|
||||
|
||||
|
||||
// Check that inputs have Tailwind classes
|
||||
const inputElement = page.locator('#email-address');
|
||||
const inputClasses = await inputElement.getAttribute('class');
|
||||
@ -198,33 +198,33 @@ test.describe('Login Page Debug Test', () => {
|
||||
});
|
||||
|
||||
test('should validate form submission with empty fields', async ({ page }) => {
|
||||
await page.goto('http://localhost:3000/auth/login');
|
||||
|
||||
await page.goto('http://localhost:3001/auth/login');
|
||||
|
||||
// Try submitting with empty fields
|
||||
await page.click('button[type="submit"]');
|
||||
|
||||
|
||||
// Should still be on the same page and show validation error
|
||||
await expect(page).toHaveURL('http://localhost:3000/auth/login');
|
||||
await expect(page).toHaveURL('http://localhost:3001/auth/login');
|
||||
});
|
||||
|
||||
test('should have working password visibility toggle', async ({ page }) => {
|
||||
await page.goto('http://localhost:3000/auth/login');
|
||||
|
||||
await page.goto('http://localhost:3001/auth/login');
|
||||
|
||||
const passwordInput = page.locator('#password');
|
||||
const toggleButton = page.locator('button[aria-label*="toggle"]');
|
||||
|
||||
|
||||
// Initially should be password type
|
||||
await expect(passwordInput).toHaveAttribute('type', 'password');
|
||||
|
||||
|
||||
// Click toggle button
|
||||
await toggleButton.click();
|
||||
|
||||
|
||||
// Should switch to text type
|
||||
await expect(passwordInput).toHaveAttribute('type', 'text');
|
||||
|
||||
|
||||
// Click again to toggle back
|
||||
await toggleButton.click();
|
||||
|
||||
|
||||
// Should switch back to password type
|
||||
await expect(passwordInput).toHaveAttribute('type', 'password');
|
||||
});
|
||||
|
||||
@ -2,51 +2,51 @@ import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('Login Page Structure and Tailwind Debug', () => {
|
||||
test('should display login page with proper structure', async ({ page }) => {
|
||||
await page.goto('http://localhost:3000/auth/login');
|
||||
|
||||
await page.goto('http://localhost:3001/auth/login');
|
||||
|
||||
// Verify main layout structure
|
||||
await expect(page.locator('div.min-h-screen.bg-gray-50')).toBeVisible();
|
||||
await expect(page.locator('div.flex.flex-col.items-center.justify-center.min-h-screen.py-12.px-4.sm:px-6.lg:px-8')).toBeVisible();
|
||||
|
||||
|
||||
// Check header elements
|
||||
await expect(page.locator('h1.text-3xl.font-bold.text-gray-900')).toBeVisible();
|
||||
await expect(page.locator('p.mt-2.text-gray-600')).toContainText('Sign in to your account');
|
||||
|
||||
|
||||
// Check form structure
|
||||
await expect(page.locator('form')).toBeVisible();
|
||||
await expect(page.locator('div.max-w-md.w-full.space-y-8.bg-white.rounded-xl.shadow-lg.p-8')).toBeVisible();
|
||||
|
||||
|
||||
// Check input fields
|
||||
await expect(page.locator('#email-address')).toBeVisible();
|
||||
await expect(page.locator('#password')).toBeVisible();
|
||||
|
||||
|
||||
// Check for submit button
|
||||
await expect(page.locator('button[type="submit"]')).toBeVisible();
|
||||
|
||||
|
||||
// Check links
|
||||
await expect(page.locator('a[href="/auth/signup"]')).toBeVisible();
|
||||
await expect(page.locator('a[href="/auth/forgot-password"]')).toBeVisible();
|
||||
});
|
||||
|
||||
test('should have proper Tailwind CSS classes applied', async ({ page }) => {
|
||||
await page.goto('http://localhost:3000/auth/login');
|
||||
|
||||
await page.goto('http://localhost:3001/auth/login');
|
||||
|
||||
// Test that the page uses Tailwind utility classes appropriately
|
||||
const pageBody = await page.evaluate(() => {
|
||||
const body = document.querySelector('body');
|
||||
return body?.className || '';
|
||||
});
|
||||
|
||||
|
||||
// Check for essential Tailwind classes
|
||||
expect(pageBody).toContain('min-h-screen');
|
||||
expect(pageBody).toContain('bg-gray-50');
|
||||
|
||||
|
||||
// Check form container classes
|
||||
const formContainer = await page.evaluate(() => {
|
||||
const container = document.querySelector('.max-w-md.w-full.space-y-8.bg-white.rounded-xl.shadow-lg.p-8');
|
||||
return container?.className || '';
|
||||
});
|
||||
|
||||
|
||||
expect(formContainer).toContain('max-w-md');
|
||||
expect(formContainer).toContain('bg-white');
|
||||
expect(formContainer).toContain('rounded-xl');
|
||||
@ -55,23 +55,23 @@ test.describe('Login Page Structure and Tailwind Debug', () => {
|
||||
});
|
||||
|
||||
test('should have interactive elements working', async ({ page }) => {
|
||||
await page.goto('http://localhost:3000/auth/login');
|
||||
|
||||
await page.goto('http://localhost:3001/auth/login');
|
||||
|
||||
// Test password visibility toggle
|
||||
const passwordField = page.locator('#password');
|
||||
const toggleButton = page.locator('button:has-text("Toggle password visibility")');
|
||||
|
||||
|
||||
// Check initial state
|
||||
const initialType = await passwordField.getAttribute('type');
|
||||
expect(initialType).toBe('password');
|
||||
|
||||
|
||||
// If toggle exists, test it
|
||||
if (await toggleButton.isVisible()) {
|
||||
await toggleButton.click();
|
||||
|
||||
|
||||
const toggledType = await passwordField.getAttribute('type');
|
||||
expect(toggledType).toBe('text');
|
||||
|
||||
|
||||
await toggleButton.click();
|
||||
const finalType = await passwordField.getAttribute('type');
|
||||
expect(finalType).toBe('password');
|
||||
|
||||
Loading…
Reference in New Issue
Block a user