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 <noreply@anthropic.com>
This commit is contained in:
mARTin 2026-03-19 09:02:26 +01:00
parent eeabe2c1c5
commit 918d87cbbb
4 changed files with 69 additions and 26 deletions

View File

@ -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": {

View File

@ -11,16 +11,35 @@ function LoginContent() {
const error = searchParams.get('error');
const [isLoading, setIsLoading] = useState(false);
const [credError, setCredError] = useState<string | null>(null);
const handleCredentialsSignIn = async (e: React.FormEvent<HTMLFormElement>) => {
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() {
</p>
{/* Error Message */}
{error && (
{(error || credError) && (
<div className="weekly-auth-error">
{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})`}
</div>
)}

View File

@ -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 ? (
<div
className="resize-handle"
onMouseDown={(e) => startResize(e, 'allday', handleOnTop)}
onTouchStart={(e) => startResize(e, 'allday', handleOnTop)}
>
<div className="resize-handle-bar" />
</div>
) : null;
return (
<>
{effectiveAllDayPosition === "below" && resizeHandle}
<section
className={`all-day-events-section ${isAllDayExpanded ? "expanded" : "collapsed"}`}
style={isAllDayExpanded && allDayHeight ? { height: `${allDayHeight}px`, overflowY: 'auto' } : undefined}
@ -5532,16 +5544,7 @@ export default function WeeklyView() {
)}
</div>
</section>
{/* Resize handle - outside section so it's always at the bottom border */}
{isAllDayExpanded && (
<div
className="resize-handle"
onMouseDown={(e) => startResize(e, 'allday')}
onTouchStart={(e) => startResize(e, 'allday')}
>
<div className="resize-handle-bar" />
</div>
)}
{effectiveAllDayPosition === "above" && resizeHandle}
</>
);
})();
@ -7315,8 +7318,8 @@ export default function WeeklyView() {
{somedayExpanded && (
<div
className="resize-handle"
onMouseDown={(e) => startResize(e, 'someday')}
onTouchStart={(e) => startResize(e, 'someday')}
onMouseDown={(e) => startResize(e, 'someday', true)}
onTouchStart={(e) => startResize(e, 'someday', true)}
>
<div className="resize-handle-bar" />
</div>

View File

@ -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 || "",