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)
|
- Set cookie with security flags (httpOnly, secure, sameSite)
|
||||||
- Use jose library instead of jsonwebtoken to avoid CommonJS issues with Edge runtime
|
- Use jose library instead of jsonwebtoken to avoid CommonJS issues with Edge runtime
|
||||||
</action>
|
</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>
|
<done>User can successfully sign up and receive a valid session token in HTTP-only cookie</done>
|
||||||
</task>
|
</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
|
- Use jose library instead of jsonwebtoken to avoid CommonJS issues with Edge runtime
|
||||||
- Prevent timing attacks by comparing passwords in constant time
|
- Prevent timing attacks by comparing passwords in constant time
|
||||||
</action>
|
</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>
|
<done>User can successfully authenticate and receive a valid session token in HTTP-only cookie</done>
|
||||||
</task>
|
</task>
|
||||||
|
|
||||||
@ -164,7 +164,7 @@ We need to implement the full authentication logic and ensure proper session man
|
|||||||
- Return success message to the client
|
- Return success message to the client
|
||||||
- Ensure cookie flags match those used during login (httpOnly, secure, sameSite)
|
- Ensure cookie flags match those used during login (httpOnly, secure, sameSite)
|
||||||
</action>
|
</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>
|
<done>User can successfully log out and session cookie is cleared</done>
|
||||||
</task>
|
</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)
|
- Send email using configured email service (details in infrastructure)
|
||||||
- Not reveal if email exists in system to prevent enumeration
|
- Not reveal if email exists in system to prevent enumeration
|
||||||
</action>
|
</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>
|
<done>User can request password reset and receive confirmation without revealing account existence</done>
|
||||||
</task>
|
</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
|
- Return appropriate error responses for invalid or expired tokens
|
||||||
- Use bcryptjs for password hashing
|
- Use bcryptjs for password hashing
|
||||||
</action>
|
</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>
|
<done>User can complete password reset with valid token and receive confirmation</done>
|
||||||
</task>
|
</task>
|
||||||
|
|
||||||
@ -148,7 +148,7 @@ Building upon the foundation established in Plan 1, this plan implements the rem
|
|||||||
- Clear token from database after verification
|
- Clear token from database after verification
|
||||||
- Handle invalid/missing/expired tokens gracefully
|
- Handle invalid/missing/expired tokens gracefully
|
||||||
</action>
|
</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>
|
<done>User can verify email address with valid token and receive confirmation</done>
|
||||||
</task>
|
</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
|
- Be styled consistently with the rest of the application
|
||||||
- Have a link back to login page
|
- Have a link back to login page
|
||||||
</action>
|
</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>
|
<done>Forgot password page is accessible, visually consistent, and functional</done>
|
||||||
</task>
|
</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
|
- Be styled consistently with the rest of the application
|
||||||
- Have a link back to login page
|
- Have a link back to login page
|
||||||
</action>
|
</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>
|
<done>Password reset page is accessible, visually consistent, and functional</done>
|
||||||
</task>
|
</task>
|
||||||
|
|
||||||
@ -213,7 +213,7 @@ Building upon the foundation established in Plan 1, this plan implements the rem
|
|||||||
- Provide clear feedback on verification status
|
- Provide clear feedback on verification status
|
||||||
- Be styled consistently with the rest of the application
|
- Be styled consistently with the rest of the application
|
||||||
</action>
|
</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>
|
<done>Email verification page is accessible, visually consistent, and functional</done>
|
||||||
</task>
|
</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)
|
- Send email using configured email service (details in infrastructure)
|
||||||
- Return clear success/failure messages
|
- Return clear success/failure messages
|
||||||
</action>
|
</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>
|
<done>User can request verification email to be resent and receive confirmation</done>
|
||||||
</task>
|
</task>
|
||||||
|
|
||||||
|
|||||||
@ -85,7 +85,7 @@ Output: Bidirectional synchronization logic, sync API endpoint, and calendar set
|
|||||||
- Handles errors gracefully with appropriate messaging
|
- Handles errors gracefully with appropriate messaging
|
||||||
|
|
||||||
Follow the API endpoint pattern established in Phase 2 and authentication flow from Phase 1.</action>
|
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>
|
<done>Endpoint successfully triggers sync process and returns status JSON</done>
|
||||||
</task>
|
</task>
|
||||||
|
|
||||||
|
|||||||
@ -80,7 +80,7 @@ Functional OAuth endpoints that exchange authorization codes for real tokens and
|
|||||||
- Handle and log token exchange errors appropriately
|
- Handle and log token exchange errors appropriately
|
||||||
- Store the tokens in the CalendarConnection database record
|
- Store the tokens in the CalendarConnection database record
|
||||||
</action>
|
</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>
|
<done>Google OAuth callback endpoint exchanges real tokens and stores them in database</done>
|
||||||
</task>
|
</task>
|
||||||
|
|
||||||
@ -102,7 +102,7 @@ Functional OAuth endpoints that exchange authorization codes for real tokens and
|
|||||||
- Handle and log token exchange errors appropriately
|
- Handle and log token exchange errors appropriately
|
||||||
- Store the tokens in the CalendarConnection database record
|
- Store the tokens in the CalendarConnection database record
|
||||||
</action>
|
</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>
|
<done>Apple OAuth callback endpoint exchanges real tokens and stores them in database</done>
|
||||||
</task>
|
</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
|
EXPOSE 3000
|
||||||
|
|
||||||
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
|
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"]
|
CMD ["npm", "run", "start"]
|
||||||
|
|||||||
@ -26,7 +26,7 @@ This application can be deployed using Docker and Docker Compose.
|
|||||||
```bash
|
```bash
|
||||||
docker-compose up --build
|
docker-compose up --build
|
||||||
```
|
```
|
||||||
3. Access the application at http://localhost:3000
|
3. Access the application at http://localhost:3001
|
||||||
|
|
||||||
### Environment Variables
|
### Environment Variables
|
||||||
|
|
||||||
|
|||||||
8
SETUP.md
8
SETUP.md
@ -39,7 +39,7 @@ This will create all necessary tables:
|
|||||||
npm run dev
|
npm run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
Visit: **http://localhost:3000**
|
Visit: **http://localhost:3001**
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -47,14 +47,14 @@ Visit: **http://localhost:3000**
|
|||||||
|
|
||||||
### Sign Up (Email/Password)
|
### 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)
|
2. Enter email and password (min 8 characters)
|
||||||
3. Click "Sign up"
|
3. Click "Sign up"
|
||||||
4. You'll be automatically signed in
|
4. You'll be automatically signed in
|
||||||
|
|
||||||
### Sign In (Email/Password)
|
### Sign In (Email/Password)
|
||||||
|
|
||||||
1. Go to http://localhost:3000/auth/login
|
1. Go to http://localhost:3001/auth/login
|
||||||
2. Enter your credentials
|
2. Enter your credentials
|
||||||
3. Click "Sign in"
|
3. Click "Sign in"
|
||||||
|
|
||||||
@ -82,7 +82,7 @@ To enable "Sign in with Google":
|
|||||||
4. Click **Create Credentials** → **OAuth client ID**
|
4. Click **Create Credentials** → **OAuth client ID**
|
||||||
5. Choose **Web application**
|
5. Choose **Web application**
|
||||||
6. Add these redirect URIs:
|
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/auth/callback/google` (via Reverse Proxy)
|
||||||
- `https://todo.martin-bierschenk.de/api/calendar/google/oauth` (via Reverse Proxy)
|
- `https://todo.martin-bierschenk.de/api/calendar/google/oauth` (via Reverse Proxy)
|
||||||
7. Copy your **Client ID** and **Client Secret**
|
7. Copy your **Client ID** and **Client Secret**
|
||||||
|
|||||||
@ -161,7 +161,7 @@ The application follows a standard Next.js 14 architecture with:
|
|||||||
|
|
||||||
## Assumptions
|
## Assumptions
|
||||||
|
|
||||||
1. Application runs on localhost:3000 (development server)
|
1. Application runs on localhost:3001 (development server)
|
||||||
2. All components are properly rendered
|
2. All components are properly rendered
|
||||||
3. Network connectivity allows for API calls
|
3. Network connectivity allows for API calls
|
||||||
4. Required environment variables are configured
|
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
|
## Implementation Notes
|
||||||
|
|
||||||
The test plan assumes:
|
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
|
- All components are properly implemented and accessible
|
||||||
- Environment variables are configured appropriately
|
- Environment variables are configured appropriately
|
||||||
- Network connectivity supports API interactions
|
- Network connectivity supports API interactions
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
// Debug script to inspect login page structure and Tailwind CSS
|
// 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 ===');
|
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
|
// 1. Check if page loads properly
|
||||||
console.log('1. Checking page load...');
|
console.log('1. Checking page load...');
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
* This script is meant to be run in the browser console to debug signup issues.
|
* This script is meant to be run in the browser console to debug signup issues.
|
||||||
*
|
*
|
||||||
* To use:
|
* To use:
|
||||||
* 1. Navigate to http://localhost:3000/auth/signup
|
* 1. Navigate to http://localhost:3001/auth/signup
|
||||||
* 2. Open browser developer tools (F12)
|
* 2. Open browser developer tools (F12)
|
||||||
* 3. Go to Console tab
|
* 3. Go to Console tab
|
||||||
* 4. Paste this entire script and run it
|
* 4. Paste this entire script and run it
|
||||||
|
|||||||
@ -41,7 +41,7 @@ Since this is a Next.js application, you can deploy it with:
|
|||||||
|
|
||||||
# Health check
|
# Health check
|
||||||
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
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
|
# For Next.js, run in production mode
|
||||||
CMD ["npm", "run", "start"]
|
CMD ["npm", "run", "start"]
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "my-weekly-todo-list",
|
"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",
|
"description": "A web-based weekly task management application that organizes to-dos and calendar events in a single, intuitive weekly view",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@ -26,7 +26,7 @@ export default defineConfig({
|
|||||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||||
use: {
|
use: {
|
||||||
/* Base URL to use in actions like `await page.goto('')`. */
|
/* 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 */
|
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||||
trace: 'on-first-retry',
|
trace: 'on-first-retry',
|
||||||
@ -73,7 +73,7 @@ export default defineConfig({
|
|||||||
/* Run your local dev server before starting the tests */
|
/* Run your local dev server before starting the tests */
|
||||||
// webServer: {
|
// webServer: {
|
||||||
// command: 'npm run start',
|
// command: 'npm run start',
|
||||||
// url: 'http://localhost:3000',
|
// url: 'http://localhost:3001',
|
||||||
// reuseExistingServer: !process.env.CI,
|
// reuseExistingServer: !process.env.CI,
|
||||||
// },
|
// },
|
||||||
});
|
});
|
||||||
|
|||||||
@ -465,6 +465,8 @@ h3 {
|
|||||||
--weekly-settings-toggle-active-bg: white;
|
--weekly-settings-toggle-active-bg: white;
|
||||||
--weekly-settings-toggle-text: #6b7280;
|
--weekly-settings-toggle-text: #6b7280;
|
||||||
--weekly-settings-toggle-active-text: #111827;
|
--weekly-settings-toggle-active-text: #111827;
|
||||||
|
--weekly-header-brightness: 1;
|
||||||
|
--weekly-goal-brightness: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Weekly Main Container */
|
/* Weekly Main Container */
|
||||||
@ -709,7 +711,9 @@ h3 {
|
|||||||
position: relative;
|
position: relative;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
border-bottom: 1px solid var(--weekly-border); /* Restore lines */
|
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 */
|
/* 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);
|
font-weight: var(--weekly-task-weight, 400);
|
||||||
line-height: var(--weekly-task-line-height, 1.5);
|
line-height: var(--weekly-task-line-height, 1.5);
|
||||||
color: var(--weekly-task-color, var(--weekly-text));
|
color: var(--weekly-task-color, var(--weekly-text));
|
||||||
|
white-space: normal;
|
||||||
|
word-break: break-word;
|
||||||
|
overflow: visible;
|
||||||
border: none;
|
border: none;
|
||||||
outline: none;
|
outline: none;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
@ -1264,19 +1271,20 @@ h3 {
|
|||||||
|
|
||||||
.weekly-someday-list .weekly-task-item {
|
.weekly-someday-list .weekly-task-item {
|
||||||
border-bottom: 1px solid transparent;
|
border-bottom: 1px solid transparent;
|
||||||
height: 32px;
|
min-height: 32px;
|
||||||
padding: 0 1rem;
|
height: auto;
|
||||||
|
padding: 4px 1rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
.weekly-someday-list .weekly-task-text {
|
.weekly-someday-list .weekly-task-text {
|
||||||
font-size: var(--base-font-size);
|
font-size: var(--base-font-size);
|
||||||
line-height: 32px;
|
line-height: 1.4;
|
||||||
max-height: 32px;
|
word-break: break-word;
|
||||||
overflow: hidden;
|
overflow: visible;
|
||||||
white-space: nowrap;
|
white-space: normal;
|
||||||
text-overflow: ellipsis;
|
text-overflow: clip;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1607,6 +1615,8 @@ h3 {
|
|||||||
--weekly-settings-text: #ffffff;
|
--weekly-settings-text: #ffffff;
|
||||||
--weekly-settings-toggle-bg: #222222;
|
--weekly-settings-toggle-bg: #222222;
|
||||||
--weekly-settings-toggle-active-bg: #333333;
|
--weekly-settings-toggle-active-bg: #333333;
|
||||||
|
--weekly-header-brightness: 1.5;
|
||||||
|
--weekly-goal-brightness: 1.5;
|
||||||
--weekly-settings-toggle-text: #888888;
|
--weekly-settings-toggle-text: #888888;
|
||||||
--weekly-settings-toggle-active-text: #ffffff;
|
--weekly-settings-toggle-active-text: #ffffff;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -39,6 +39,7 @@ import {
|
|||||||
Palette,
|
Palette,
|
||||||
Sparkles,
|
Sparkles,
|
||||||
Info,
|
Info,
|
||||||
|
Trash2,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
|
|
||||||
// Types
|
// 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
|
// Main Component
|
||||||
export default function WeeklyView() {
|
export default function WeeklyView() {
|
||||||
const { data: session } = useSession();
|
const { data: session } = useSession();
|
||||||
@ -558,6 +582,7 @@ export default function WeeklyView() {
|
|||||||
yearFontSize?: string;
|
yearFontSize?: string;
|
||||||
yearFontWeight?: string;
|
yearFontWeight?: string;
|
||||||
yearColor?: string;
|
yearColor?: string;
|
||||||
|
quoteSourceUrls?: string[];
|
||||||
}>({
|
}>({
|
||||||
name: session?.user?.name || "",
|
name: session?.user?.name || "",
|
||||||
email: session?.user?.email || "",
|
email: session?.user?.email || "",
|
||||||
@ -609,6 +634,7 @@ export default function WeeklyView() {
|
|||||||
weekendColorSun: "#dc2626",
|
weekendColorSun: "#dc2626",
|
||||||
pastDayColor: "#a6a6a7",
|
pastDayColor: "#a6a6a7",
|
||||||
quoteSourceUrl: "https://recite.vercel.app/api/random",
|
quoteSourceUrl: "https://recite.vercel.app/api/random",
|
||||||
|
quoteSourceUrls: ["https://recite.vercel.app/api/random"],
|
||||||
});
|
});
|
||||||
const [motivationalQuote, setMotivationalQuote] = useState("");
|
const [motivationalQuote, setMotivationalQuote] = useState("");
|
||||||
const [showSummary, setShowSummary] = useState(false);
|
const [showSummary, setShowSummary] = useState(false);
|
||||||
@ -945,13 +971,18 @@ export default function WeeklyView() {
|
|||||||
|
|
||||||
const fetchMotivationalQuote = useCallback(async () => {
|
const fetchMotivationalQuote = useCallback(async () => {
|
||||||
if (profile.goalFallbackType !== "quote") return;
|
if (profile.goalFallbackType !== "quote") return;
|
||||||
|
|
||||||
|
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 {
|
try {
|
||||||
const url = profile.quoteSourceUrl || "https://recite.vercel.app/api/random";
|
|
||||||
const res = await fetch(url);
|
const res = await fetch(url);
|
||||||
if (!res.ok) throw new Error("Failed to fetch quote");
|
if (!res.ok) continue;
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
// Handle different JSON formats (array or object)
|
|
||||||
let quoteText = "";
|
let quoteText = "";
|
||||||
if (Array.isArray(data) && data.length > 0) {
|
if (Array.isArray(data) && data.length > 0) {
|
||||||
const item = data[0];
|
const item = data[0];
|
||||||
@ -964,12 +995,18 @@ export default function WeeklyView() {
|
|||||||
quoteText = data;
|
quoteText = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (quoteText) setMotivationalQuote(quoteText);
|
if (quoteText) {
|
||||||
} catch (error) {
|
setMotivationalQuote(quoteText);
|
||||||
console.error("Error fetching motivational quote:", error);
|
return; // Success!
|
||||||
setMotivationalQuote("Stay focused and productive.");
|
|
||||||
}
|
}
|
||||||
}, [profile.goalFallbackType, profile.quoteSourceUrl]);
|
} catch (error) {
|
||||||
|
console.error(`Error fetching quote from ${url}:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Final fallback if all failed
|
||||||
|
setMotivationalQuote("Stay focused and productive.");
|
||||||
|
}, [profile.goalFallbackType, profile.quoteSourceUrl, profile.quoteSourceUrls]);
|
||||||
|
|
||||||
// Fetch tasks on mount
|
// Fetch tasks on mount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -1457,7 +1494,7 @@ export default function WeeklyView() {
|
|||||||
const dateStr = formatDateToISO(date); // Use local date formatting
|
const dateStr = formatDateToISO(date); // Use local date formatting
|
||||||
return tasks
|
return tasks
|
||||||
.filter((task) => {
|
.filter((task) => {
|
||||||
if (!task.scheduledDate) return false;
|
if (!task.completed && task.scheduledDate) {
|
||||||
// Exclude sub-tasks from top-level list (they render inside their parent)
|
// Exclude sub-tasks from top-level list (they render inside their parent)
|
||||||
if (task.parentTaskId) return false;
|
if (task.parentTaskId) return false;
|
||||||
// Use string comparison to avoid timezone shifts
|
// Use string comparison to avoid timezone shifts
|
||||||
@ -1466,6 +1503,8 @@ export default function WeeklyView() {
|
|||||||
? task.scheduledDate.substring(0, 10)
|
? task.scheduledDate.substring(0, 10)
|
||||||
: formatDateToISO(new Date(task.scheduledDate));
|
: formatDateToISO(new Date(task.scheduledDate));
|
||||||
return taskDateStr === dateStr;
|
return taskDateStr === dateStr;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
})
|
})
|
||||||
.sort((a, b) => {
|
.sort((a, b) => {
|
||||||
// Sort by time if available
|
// Sort by time if available
|
||||||
@ -1798,6 +1837,9 @@ export default function WeeklyView() {
|
|||||||
const taskDuration = task.duration || 15;
|
const taskDuration = task.duration || 15;
|
||||||
const taskEnd = taskStart + taskDuration;
|
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
|
// Overlap condition: task starts before slot ends AND task ends after slot starts
|
||||||
return taskStart < slotEnd && taskEnd > slotStart;
|
return taskStart < slotEnd && taskEnd > slotStart;
|
||||||
});
|
});
|
||||||
@ -3270,27 +3312,39 @@ export default function WeeklyView() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* CENTER SECTION: Week/Year, Goal, Focus Mode - Reveal on Hover */}
|
{/* 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 */}
|
{/* Week & Year */}
|
||||||
<div className="whitespace-nowrap flex items-center gap-2">
|
<div className="whitespace-nowrap flex items-center gap-2">
|
||||||
{(isLoading || isSyncing || syncStatus === "syncing") && (
|
{(isLoading || isSyncing || syncStatus === "syncing") ? (
|
||||||
<div className="weekly-spinner" title="Syncing..."></div>
|
<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={{
|
<span style={{
|
||||||
fontFamily: profile.cwFontFamily || "Inter",
|
fontFamily: profile.cwFontFamily || "Inter",
|
||||||
fontSize: profile.cwFontSize || "1.125rem",
|
fontSize: profile.cwFontSize || "1.125rem",
|
||||||
fontWeight: Number(profile.cwFontWeight || "700"),
|
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")}
|
KW {getWeekNumber(currentWeekStart).toString().padStart(2, "0")}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-gray-400">|</span>
|
<span className="text-gray-400">|</span>
|
||||||
<span style={{
|
<span
|
||||||
|
style={{
|
||||||
fontFamily: profile.yearFontFamily || "Inter",
|
fontFamily: profile.yearFontFamily || "Inter",
|
||||||
fontSize: profile.yearFontSize || "1.125rem",
|
fontSize: profile.yearFontSize || "1.125rem",
|
||||||
fontWeight: Number(profile.yearFontWeight || "700"),
|
fontWeight: Number(profile.yearFontWeight || "700"),
|
||||||
color: profile.yearColor || "#333333",
|
color: adjustColorForDarkMode(profile.yearColor || "#333333", darkMode),
|
||||||
}}>
|
filter: "brightness(var(--weekly-header-brightness, 1))"
|
||||||
|
}}
|
||||||
|
>
|
||||||
{currentWeekStart.getFullYear()}
|
{currentWeekStart.getFullYear()}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@ -3330,6 +3384,8 @@ export default function WeeklyView() {
|
|||||||
: undefined,
|
: undefined,
|
||||||
fontSize: profile.goalFontSize || undefined,
|
fontSize: profile.goalFontSize || undefined,
|
||||||
fontWeight: profile.goalFontWeight || undefined,
|
fontWeight: profile.goalFontWeight || undefined,
|
||||||
|
color: adjustColorForDarkMode((profile.goalFallbackType === "quote" ? profile.taskColor : undefined) || "#333333", darkMode),
|
||||||
|
filter: "brightness(var(--weekly-goal-brightness, 1))"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{showNextTask
|
{showNextTask
|
||||||
@ -5640,7 +5696,15 @@ function TaskItem({
|
|||||||
</svg>
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
<span className="truncate">{task.title}</span>
|
<span
|
||||||
|
style={{
|
||||||
|
whiteSpace: "normal",
|
||||||
|
wordBreak: "break-word",
|
||||||
|
overflow: "visible"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{task.title}
|
||||||
|
</span>
|
||||||
</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">
|
<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;
|
yearFontSize?: string;
|
||||||
yearFontWeight?: string;
|
yearFontWeight?: string;
|
||||||
yearColor?: string;
|
yearColor?: string;
|
||||||
|
quoteSourceUrls: string[];
|
||||||
}) => void;
|
}) => void;
|
||||||
|
quoteSourceUrls?: string[];
|
||||||
goal: string;
|
goal: string;
|
||||||
setGoal: (goal: string) => void;
|
setGoal: (goal: string) => void;
|
||||||
saveGoal: (goal: string) => void;
|
saveGoal: (goal: string) => void;
|
||||||
@ -6288,6 +6354,7 @@ function SettingsSidebar({
|
|||||||
yearFontSize?: string;
|
yearFontSize?: string;
|
||||||
yearFontWeight?: string;
|
yearFontWeight?: string;
|
||||||
yearColor?: string;
|
yearColor?: string;
|
||||||
|
quoteSourceUrls?: string[];
|
||||||
}>({
|
}>({
|
||||||
name: "",
|
name: "",
|
||||||
email: "",
|
email: "",
|
||||||
@ -8995,7 +9062,7 @@ function SettingsSidebar({
|
|||||||
flex: 1,
|
flex: 1,
|
||||||
padding: "10px",
|
padding: "10px",
|
||||||
borderRadius: "6px",
|
borderRadius: "6px",
|
||||||
border: "none",
|
border: profile.goalScope === "week" ? "2px solid var(--weekly-teal)" : "none",
|
||||||
background:
|
background:
|
||||||
profile.goalScope === "week"
|
profile.goalScope === "week"
|
||||||
? "var(--weekly-settings-toggle-active-bg)"
|
? "var(--weekly-settings-toggle-active-bg)"
|
||||||
@ -9004,7 +9071,8 @@ function SettingsSidebar({
|
|||||||
profile.goalScope === "week"
|
profile.goalScope === "week"
|
||||||
? "var(--weekly-settings-toggle-active-text)"
|
? "var(--weekly-settings-toggle-active-text)"
|
||||||
: "var(--weekly-settings-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",
|
cursor: "pointer",
|
||||||
transition: "all 0.2s",
|
transition: "all 0.2s",
|
||||||
}}
|
}}
|
||||||
@ -9020,7 +9088,7 @@ function SettingsSidebar({
|
|||||||
flex: 1,
|
flex: 1,
|
||||||
padding: "10px",
|
padding: "10px",
|
||||||
borderRadius: "6px",
|
borderRadius: "6px",
|
||||||
border: "none",
|
border: profile.goalScope === "day" ? "2px solid var(--weekly-teal)" : "none",
|
||||||
background:
|
background:
|
||||||
profile.goalScope === "day"
|
profile.goalScope === "day"
|
||||||
? "var(--weekly-settings-toggle-active-bg)"
|
? "var(--weekly-settings-toggle-active-bg)"
|
||||||
@ -9029,7 +9097,8 @@ function SettingsSidebar({
|
|||||||
profile.goalScope === "day"
|
profile.goalScope === "day"
|
||||||
? "var(--weekly-settings-toggle-active-text)"
|
? "var(--weekly-settings-toggle-active-text)"
|
||||||
: "var(--weekly-settings-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",
|
cursor: "pointer",
|
||||||
transition: "all 0.2s",
|
transition: "all 0.2s",
|
||||||
}}
|
}}
|
||||||
@ -9104,21 +9173,23 @@ function SettingsSidebar({
|
|||||||
color: "var(--weekly-settings-label)",
|
color: "var(--weekly-settings-label)",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
API-Datenquelle (URL)
|
API-Datenquellen (URLs)
|
||||||
</label>
|
</label>
|
||||||
|
<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
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={profile.quoteSourceUrl || ""}
|
value={url}
|
||||||
onChange={(e) =>
|
onChange={(e) => {
|
||||||
setProfile((p) => ({
|
const newUrls = [...(profile.quoteSourceUrls || [profile.quoteSourceUrl || "https://recite.vercel.app/api/random"])];
|
||||||
...p,
|
newUrls[idx] = e.target.value;
|
||||||
quoteSourceUrl: e.target.value,
|
setProfile((p) => ({ ...p, quoteSourceUrls: newUrls }));
|
||||||
}))
|
}}
|
||||||
}
|
|
||||||
className="weekly-input"
|
className="weekly-input"
|
||||||
placeholder="https://recite.vercel.app/api/random"
|
placeholder="https://..."
|
||||||
style={{
|
style={{
|
||||||
width: "100%",
|
flex: 1,
|
||||||
padding: "10px",
|
padding: "10px",
|
||||||
fontSize: "0.95rem",
|
fontSize: "0.95rem",
|
||||||
borderRadius: "6px",
|
borderRadius: "6px",
|
||||||
@ -9126,6 +9197,49 @@ function SettingsSidebar({
|
|||||||
background: "var(--weekly-bg)",
|
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" }}>
|
<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":"..."}'}).
|
URL that returns a JSON list or object of quotes (e.g. {'[{"quote":"...","author":"..."}]'} or {'{"quote":"..."}'}).
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@ -4,18 +4,18 @@
|
|||||||
*
|
*
|
||||||
* Usage:
|
* Usage:
|
||||||
* 1. Start the development server: npm run dev
|
* 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:
|
* 3. Fill in the form with:
|
||||||
* - Email: mail@martin-bierschenk.de
|
* - Email: mail@martin-bierschenk.de
|
||||||
* - Password: My-Weekly-ToDo-List
|
* - Password: My-Weekly-ToDo-List
|
||||||
* 4. Or use the following curl command:
|
* 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" \
|
* -H "Content-Type: application/json" \
|
||||||
* -d '{"email":"mail@martin-bierschenk.de","password":"My-Weekly-ToDo-List"}'
|
* -d '{"email":"mail@martin-bierschenk.de","password":"My-Weekly-ToDo-List"}'
|
||||||
*
|
*
|
||||||
* For login after signup:
|
* 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" \
|
* -H "Content-Type: application/json" \
|
||||||
* -d '{"email":"mail@martin-bierschenk.de","password":"My-Weekly-ToDo-List"}'
|
* -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('Password: My-Weekly-ToDo-List');
|
||||||
console.log('');
|
console.log('');
|
||||||
console.log('Method 1: Using Browser Interface');
|
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('2. Fill in the form with the credentials above');
|
||||||
console.log('3. Submit the form');
|
console.log('3. Submit the form');
|
||||||
console.log('');
|
console.log('');
|
||||||
console.log('Method 2: Using curl commands (in terminal)');
|
console.log('Method 2: Using curl commands (in terminal)');
|
||||||
console.log('First, create the account:');
|
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(' -H "Content-Type: application/json" \\');
|
||||||
console.log(' -d \'{"email":"mail@martin-bierschenk.de","password":"My-Weekly-ToDo-List"}\'');
|
console.log(' -d \'{"email":"mail@martin-bierschenk.de","password":"My-Weekly-ToDo-List"}\'');
|
||||||
console.log('');
|
console.log('');
|
||||||
console.log('Then, authenticate with the account:');
|
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(' -H "Content-Type: application/json" \\');
|
||||||
console.log(' -d \'{"email":"mail@martin-bierschenk.de","password":"My-Weekly-ToDo-List"}\'');
|
console.log(' -d \'{"email":"mail@martin-bierschenk.de","password":"My-Weekly-ToDo-List"}\'');
|
||||||
console.log('');
|
console.log('');
|
||||||
|
|||||||
@ -7,7 +7,7 @@ async function testSignupApi() {
|
|||||||
console.log('Testing signup API endpoint...');
|
console.log('Testing signup API endpoint...');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch('http://localhost:3000/api/auth/signup', {
|
const response = await fetch('http://localhost:3001/api/auth/signup', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { test, expect } from '@playwright/test';
|
|||||||
// Test authentication flow against your running application
|
// Test authentication flow against your running application
|
||||||
test.describe('Authentication Flow Tests', () => {
|
test.describe('Authentication Flow Tests', () => {
|
||||||
test('should access login page', async ({ page }) => {
|
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
|
// Check that we're on the login page
|
||||||
await expect(page).toHaveTitle(/Login/);
|
await expect(page).toHaveTitle(/Login/);
|
||||||
@ -16,7 +16,7 @@ test.describe('Authentication Flow Tests', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should access signup page', async ({ page }) => {
|
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
|
// Check that we're on the signup page
|
||||||
await expect(page).toHaveTitle(/Sign Up/);
|
await expect(page).toHaveTitle(/Sign Up/);
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { test, expect } from '@playwright/test';
|
|||||||
// Test to verify CSS styling is working in your application
|
// Test to verify CSS styling is working in your application
|
||||||
test.describe('CSS Debugging Tests', () => {
|
test.describe('CSS Debugging Tests', () => {
|
||||||
test('should load with proper CSS styling', async ({ page }) => {
|
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
|
// Verify main page elements have proper styling
|
||||||
const mainHeader = page.getByText('My Weekly To Do List');
|
const mainHeader = page.getByText('My Weekly To Do List');
|
||||||
@ -28,7 +28,7 @@ test.describe('CSS Debugging Tests', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should have proper auth form styling', async ({ page }) => {
|
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
|
// Check that auth form has proper styling
|
||||||
const authForm = page.locator('.auth-form');
|
const authForm = page.locator('.auth-form');
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { test, expect } from '@playwright/test';
|
|||||||
// Simple test to verify the app is running and accessible
|
// Simple test to verify the app is running and accessible
|
||||||
test('My Weekly To Do List should be accessible', async ({ page }) => {
|
test('My Weekly To Do List should be accessible', async ({ page }) => {
|
||||||
// Navigate to the running application
|
// Navigate to the running application
|
||||||
await page.goto('http://localhost:3000');
|
await page.goto('http://localhost:3001');
|
||||||
|
|
||||||
// Check that the main elements are present
|
// Check that the main elements are present
|
||||||
await expect(page).toHaveTitle(/My Weekly To Do List/);
|
await expect(page).toHaveTitle(/My Weekly To Do List/);
|
||||||
@ -14,7 +14,7 @@ test('My Weekly To Do List should be accessible', async ({ page }) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('Should be able to access tasks page', async ({ page }) => {
|
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
|
// Try to navigate to tasks page
|
||||||
const tasksLink = page.getByText('Tasks');
|
const tasksLink = page.getByText('Tasks');
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { test, expect } from '@playwright/test';
|
|||||||
|
|
||||||
test.describe('Login Page Core Functionality', () => {
|
test.describe('Login Page Core Functionality', () => {
|
||||||
test('should load the login page with core elements', async ({ page }) => {
|
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
|
// Basic page verification
|
||||||
await expect(page).toHaveURL(/login/);
|
await expect(page).toHaveURL(/login/);
|
||||||
@ -36,7 +36,7 @@ test.describe('Login Page Core Functionality', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should have basic CSS structure', async ({ page }) => {
|
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
|
// Check that the page has basic styling structure
|
||||||
const body = page.locator('body');
|
const body = page.locator('body');
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { test, expect } from '@playwright/test';
|
|||||||
|
|
||||||
test.describe('Login Page Debug - Simplified', () => {
|
test.describe('Login Page Debug - Simplified', () => {
|
||||||
test('should load login page successfully', async ({ page }) => {
|
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
|
// Basic page load verification
|
||||||
await expect(page).toHaveURL(/login/);
|
await expect(page).toHaveURL(/login/);
|
||||||
@ -23,7 +23,7 @@ test.describe('Login Page Debug - Simplified', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should have Tailwind CSS classes in structure', async ({ page }) => {
|
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
|
// Check main container classes
|
||||||
const container = page.locator('div.min-h-screen.bg-gray-50');
|
const container = page.locator('div.min-h-screen.bg-gray-50');
|
||||||
@ -40,7 +40,7 @@ test.describe('Login Page Debug - Simplified', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should have functional form elements', async ({ page }) => {
|
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
|
// Test that the form exists
|
||||||
const form = page.locator('form');
|
const form = page.locator('form');
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { test, expect } from '@playwright/test';
|
|||||||
|
|
||||||
test.describe('Login Page Debug Test', () => {
|
test.describe('Login Page Debug Test', () => {
|
||||||
test('should load the login page correctly', async ({ page }) => {
|
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
|
// Check page title and heading
|
||||||
await expect(page).toHaveTitle(/Weekly To Do List/);
|
await expect(page).toHaveTitle(/Weekly To Do List/);
|
||||||
@ -44,7 +44,7 @@ test.describe('Login Page Debug Test', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should have Tailwind CSS applied correctly', async ({ page }) => {
|
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
|
// Check for various Tailwind utility classes
|
||||||
const expectedClasses = [
|
const expectedClasses = [
|
||||||
@ -198,17 +198,17 @@ test.describe('Login Page Debug Test', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should validate form submission with empty fields', async ({ page }) => {
|
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
|
// Try submitting with empty fields
|
||||||
await page.click('button[type="submit"]');
|
await page.click('button[type="submit"]');
|
||||||
|
|
||||||
// Should still be on the same page and show validation error
|
// 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 }) => {
|
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 passwordInput = page.locator('#password');
|
||||||
const toggleButton = page.locator('button[aria-label*="toggle"]');
|
const toggleButton = page.locator('button[aria-label*="toggle"]');
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { test, expect } from '@playwright/test';
|
|||||||
|
|
||||||
test.describe('Login Page Structure and Tailwind Debug', () => {
|
test.describe('Login Page Structure and Tailwind Debug', () => {
|
||||||
test('should display login page with proper structure', async ({ page }) => {
|
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
|
// Verify main layout structure
|
||||||
await expect(page.locator('div.min-h-screen.bg-gray-50')).toBeVisible();
|
await expect(page.locator('div.min-h-screen.bg-gray-50')).toBeVisible();
|
||||||
@ -29,7 +29,7 @@ test.describe('Login Page Structure and Tailwind Debug', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should have proper Tailwind CSS classes applied', async ({ page }) => {
|
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
|
// Test that the page uses Tailwind utility classes appropriately
|
||||||
const pageBody = await page.evaluate(() => {
|
const pageBody = await page.evaluate(() => {
|
||||||
@ -55,7 +55,7 @@ test.describe('Login Page Structure and Tailwind Debug', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should have interactive elements working', async ({ page }) => {
|
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
|
// Test password visibility toggle
|
||||||
const passwordField = page.locator('#password');
|
const passwordField = page.locator('#password');
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user