From 918d87cbbbc7a20eb155081f92de6eaf3fb0e6c4 Mon Sep 17 00:00:00 2001 From: mARTin Date: Thu, 19 Mar 2026 09:02:26 +0100 Subject: [PATCH] fix: credentials login error and all-day resize bar position - Fix NextAuth CredentialsProvider + PrismaAdapter conflict by stubbing adapter session methods (JWT strategy doesn't use DB sessions) - Improve login error handling with redirect:false for real error messages - Move all-day section resize bar to top when allDayPosition is "below" - Fix resize drag direction for top-positioned all-day handle v1.51.2 Co-Authored-By: Claude Opus 4.6 --- package.json | 2 +- src/app/auth/login/page.tsx | 39 ++++++++++++++++++++++++++++------- src/components/WeeklyView.tsx | 35 +++++++++++++++++-------------- src/lib/auth.ts | 19 ++++++++++++++++- 4 files changed, 69 insertions(+), 26 deletions(-) diff --git a/package.json b/package.json index 7acc5d1..c40c3ce 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "my-weekly-todo-list", - "version": "1.51.1", + "version": "1.51.2", "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": { diff --git a/src/app/auth/login/page.tsx b/src/app/auth/login/page.tsx index 6a1195d..5a31b5d 100644 --- a/src/app/auth/login/page.tsx +++ b/src/app/auth/login/page.tsx @@ -11,16 +11,35 @@ function LoginContent() { const error = searchParams.get('error'); const [isLoading, setIsLoading] = useState(false); + const [credError, setCredError] = useState(null); + const handleCredentialsSignIn = async (e: React.FormEvent) => { e.preventDefault(); setIsLoading(true); + setCredError(null); const formData = new FormData(e.currentTarget); - await signIn('credentials', { - email: formData.get('email'), - password: formData.get('password'), - callbackUrl, - }); + try { + const result = await signIn('credentials', { + email: formData.get('email'), + password: formData.get('password'), + redirect: false, + }); + + if (result?.error) { + if (result.error === 'CredentialsSignin') { + setCredError('Invalid email or password'); + } else { + setCredError(result.error); + } + setIsLoading(false); + } else if (result?.ok) { + window.location.href = callbackUrl; + } + } catch { + setCredError('An unexpected error occurred'); + setIsLoading(false); + } }; const handleGoogleSignIn = () => { @@ -42,11 +61,15 @@ function LoginContent() {

{/* Error Message */} - {error && ( + {(error || credError) && (
- {error === 'CredentialsSignin' + {credError + ? credError + : error === 'CredentialsSignin' ? 'Invalid email or password' - : 'An error occurred during sign in'} + : error === 'email_not_verified' + ? 'Please verify your email before signing in' + : `An error occurred during sign in (${error})`}
)} diff --git a/src/components/WeeklyView.tsx b/src/components/WeeklyView.tsx index dabe474..a8c8294 100644 --- a/src/components/WeeklyView.tsx +++ b/src/components/WeeklyView.tsx @@ -1653,22 +1653,22 @@ export default function WeeklyView() { } return null; }); - const resizingRef = useRef<{ target: 'someday' | 'allday'; startY: number; startHeight: number } | null>(null); + const resizingRef = useRef<{ target: 'someday' | 'allday'; startY: number; startHeight: number; handleOnTop: boolean } | null>(null); - const startResize = useCallback((e: React.MouseEvent | React.TouchEvent, target: 'someday' | 'allday') => { + const startResize = useCallback((e: React.MouseEvent | React.TouchEvent, target: 'someday' | 'allday', handleOnTop = false) => { e.preventDefault(); e.stopPropagation(); const clientY = 'touches' in e ? e.touches[0].clientY : e.clientY; const section = target === 'someday' ? somedaySectionRef.current : (document.querySelector('.all-day-events-section') as HTMLElement); if (!section) return; - resizingRef.current = { target, startY: clientY, startHeight: section.getBoundingClientRect().height }; + resizingRef.current = { target, startY: clientY, startHeight: section.getBoundingClientRect().height, handleOnTop }; const onMove = (ev: MouseEvent | TouchEvent) => { if (!resizingRef.current) return; const y = 'touches' in ev ? ev.touches[0].clientY : ev.clientY; const rawDelta = y - resizingRef.current.startY; // Top handle: dragging up = increase height (invert delta); bottom handle: normal - const delta = resizingRef.current.target === 'someday' ? -rawDelta : rawDelta; + const delta = resizingRef.current.handleOnTop ? -rawDelta : rawDelta; const newHeight = Math.max(40, Math.min(600, resizingRef.current.startHeight + delta)); if (resizingRef.current.target === 'someday') setSomedayHeight(newHeight); else setAllDayHeight(newHeight); @@ -5411,8 +5411,20 @@ export default function WeeklyView() { ); if (allDayEvents.length === 0) return null; + const handleOnTop = effectiveAllDayPosition === "below"; + const resizeHandle = isAllDayExpanded ? ( +
startResize(e, 'allday', handleOnTop)} + onTouchStart={(e) => startResize(e, 'allday', handleOnTop)} + > +
+
+ ) : null; + return ( <> + {effectiveAllDayPosition === "below" && resizeHandle}
- {/* Resize handle - outside section so it's always at the bottom border */} - {isAllDayExpanded && ( -
startResize(e, 'allday')} - onTouchStart={(e) => startResize(e, 'allday')} - > -
-
- )} + {effectiveAllDayPosition === "above" && resizeHandle} ); })(); @@ -7315,8 +7318,8 @@ export default function WeeklyView() { {somedayExpanded && (
startResize(e, 'someday')} - onTouchStart={(e) => startResize(e, 'someday')} + onMouseDown={(e) => startResize(e, 'someday', true)} + onTouchStart={(e) => startResize(e, 'someday', true)} >
diff --git a/src/lib/auth.ts b/src/lib/auth.ts index 9d7c243..e40daa8 100644 --- a/src/lib/auth.ts +++ b/src/lib/auth.ts @@ -1,4 +1,5 @@ import { NextAuthOptions } from "next-auth"; +import type { Adapter } from "next-auth/adapters"; import GoogleProvider from "next-auth/providers/google"; import AzureADProvider from "next-auth/providers/azure-ad"; import AppleProvider from "next-auth/providers/apple"; @@ -7,8 +8,24 @@ import CredentialsProvider from "next-auth/providers/credentials"; import { compare } from "bcryptjs"; import { prisma } from "@/lib/prisma"; +// Wrap PrismaAdapter to prevent session creation for credentials provider +// (known NextAuth v4 issue: adapter tries to create DB session even with JWT strategy) +const prismaAdapter = PrismaAdapter(prisma) as Adapter; +const adapter: Adapter = { + ...prismaAdapter, + createUser: prismaAdapter.createUser, + getUser: prismaAdapter.getUser, + getUserByEmail: prismaAdapter.getUserByEmail, + getUserByAccount: prismaAdapter.getUserByAccount, + linkAccount: prismaAdapter.linkAccount, + createSession: () => { return null as any; }, + getSessionAndUser: () => { return null as any; }, + updateSession: () => { return null as any; }, + deleteSession: () => { return null as any; }, +}; + export const authOptions: NextAuthOptions = { - adapter: PrismaAdapter(prisma), + adapter, providers: [ GoogleProvider({ clientId: process.env.GOOGLE_CLIENT_ID || "",