From c2a5d730a0a4d90379b376a4205cb80ac43092de Mon Sep 17 00:00:00 2001 From: kinga Date: Sat, 25 Apr 2026 20:30:50 +0200 Subject: [PATCH 1/4] feat(flashcard-assistant): add flashcard assistant API and UI component for enhanced user interaction with flashcards --- LearningPlatform/app/(public)/login/page.tsx | 33 ++- .../dashboard/flashcards/study/page.tsx | 17 ++ .../app/api/flashcard-assistant/route.ts | 84 ++++++ .../student/flashcard-assistant-fab.tsx | 193 ++++++++++++++ .../student/lesson-assistant-fab.tsx | 240 ++++++++++++------ .../lib/flashcard-assistant-prompt.ts | 12 + LearningPlatform/lib/public-routes.ts | 1 + 7 files changed, 503 insertions(+), 77 deletions(-) create mode 100644 LearningPlatform/app/api/flashcard-assistant/route.ts create mode 100644 LearningPlatform/components/student/flashcard-assistant-fab.tsx create mode 100644 LearningPlatform/lib/flashcard-assistant-prompt.ts diff --git a/LearningPlatform/app/(public)/login/page.tsx b/LearningPlatform/app/(public)/login/page.tsx index ae9b619..c395d3b 100644 --- a/LearningPlatform/app/(public)/login/page.tsx +++ b/LearningPlatform/app/(public)/login/page.tsx @@ -15,6 +15,22 @@ import { Home } from 'lucide-react'; import { cn } from '@/lib/utils'; import { heroMarketingAuthInputClass, heroMarketingGlassText } from '@/lib/hero-marketing-classes'; +async function getInfraStatusMessage(): Promise { + try { + // Add a timestamp to bypass any browser/proxy caching of old health responses. + const res = await fetch(`/api/health?t=${Date.now()}`, { cache: 'no-store' }); + if (res.ok) return null; + + // Health endpoint returns 503 for DB/Payload connectivity issues. + if (res.status >= 500) { + return 'Cannot connect to the server/database right now. Please try again in a moment.'; + } + return null; + } catch { + return 'Cannot reach the backend right now. Check your connection and try again.'; + } +} + function LoginForm() { const isDark = useIsDark(); const router = useRouter(); @@ -40,6 +56,14 @@ function LoginForm() { setIsLoading(true); try { + // Fast-fail before credential check so infra outages are not mislabeled + // as "invalid email/password". + const precheckInfraMessage = await getInfraStatusMessage(); + if (precheckInfraMessage) { + setError(precheckInfraMessage); + return; + } + const result = await signIn('credentials', { email, password, @@ -47,7 +71,14 @@ function LoginForm() { }); if (result?.error) { - setError('Invalid email or password'); + const infraMessage = await getInfraStatusMessage(); + if (infraMessage) { + setError(infraMessage); + } else if (result.error.toLowerCase().includes('too many')) { + setError('Too many login attempts. Please try again later.'); + } else { + setError('Invalid email or password'); + } } else { router.push(safeCallbackUrl); router.refresh(); diff --git a/LearningPlatform/app/(student)/(shell)/dashboard/flashcards/study/page.tsx b/LearningPlatform/app/(student)/(shell)/dashboard/flashcards/study/page.tsx index 7e14c99..16f3dbd 100644 --- a/LearningPlatform/app/(student)/(shell)/dashboard/flashcards/study/page.tsx +++ b/LearningPlatform/app/(student)/(shell)/dashboard/flashcards/study/page.tsx @@ -36,6 +36,7 @@ import { } from 'lucide-react' import { cn } from '@/lib/utils' import { FlashcardRichText } from '@/components/student/flashcard-markdown' +import { FlashcardAssistantFab } from '@/components/student/flashcard-assistant-fab' // --- Types --- @@ -79,6 +80,14 @@ function humanizeSlug(slug: string): string { .join(' ') } +function isTypingTarget(target: EventTarget | null): boolean { + if (!(target instanceof HTMLElement)) return false + const tag = target.tagName + if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT') return true + if (target.isContentEditable) return true + return Boolean(target.closest('[contenteditable="true"], [role="textbox"]')) +} + function studyQuery( mode: string, tagSlug: string, @@ -280,6 +289,8 @@ function StudyPage() { useEffect(() => { function onKey(e: KeyboardEvent) { + if (isTypingTarget(e.target)) return + if (e.key === ' ' || e.key === 'Enter') { if (phase === 'question') { e.preventDefault() @@ -747,6 +758,12 @@ function StudyPage() { )} + +