diff --git a/src/components/index.ts b/src/components/index.ts new file mode 100644 index 00000000..1430e5b1 --- /dev/null +++ b/src/components/index.ts @@ -0,0 +1,9 @@ +/** + * Canonical component barrel — all shared UI components are exported from src/components. + * App-specific components in src/app/components should be migrated here over time. + * Import from '@/components' rather than '@/app/components' for shared pieces. + */ + +export * from './ui/Toast'; +export * from './shared/EnvGuard'; +export * from './errors/ErrorBoundarySystem'; diff --git a/src/context/ToastContext.tsx b/src/context/ToastContext.tsx index 2ca7aa1b..354a62fa 100644 --- a/src/context/ToastContext.tsx +++ b/src/context/ToastContext.tsx @@ -1,6 +1,6 @@ 'use client'; -import React, { createContext, useContext, useState, useCallback, ReactNode } from 'react'; +import React, { createContext, useContext, useState, useCallback, useMemo, ReactNode } from 'react'; import { Toast, ToastType } from '@/components/ui/Toast'; export interface ToastMessage { @@ -55,8 +55,13 @@ export function ToastProvider({ children }: { children: ReactNode }) { [addToast], ); + const value = useMemo( + () => ({ toasts, addToast, removeToast, error, success, info }), + [toasts, addToast, removeToast, error, success, info], + ); + return ( - + {children}
{toasts.map((toast) => ( diff --git a/src/lib/authMiddleware.ts b/src/lib/authMiddleware.ts new file mode 100644 index 00000000..cdce28c9 --- /dev/null +++ b/src/lib/authMiddleware.ts @@ -0,0 +1,20 @@ +import { NextRequest, NextResponse } from 'next/server'; + +/** + * Validates the Authorization header and returns a 401 response if missing or invalid. + * Usage: const unauth = requireAuth(request); if (unauth) return unauth; + */ +export function requireAuth(request: NextRequest): NextResponse | null { + const authHeader = request.headers.get('authorization'); + + if (!authHeader || !authHeader.startsWith('Bearer ')) { + return NextResponse.json({ message: 'Unauthorized' }, { status: 401 }); + } + + const token = authHeader.slice(7); + if (!token) { + return NextResponse.json({ message: 'Unauthorized' }, { status: 401 }); + } + + return null; +} diff --git a/src/lib/queryKeys.ts b/src/lib/queryKeys.ts new file mode 100644 index 00000000..549eb74e --- /dev/null +++ b/src/lib/queryKeys.ts @@ -0,0 +1,29 @@ +/** + * Query key factory — provides consistent, type-safe cache keys for all API calls. + * Use these as the first argument to any data-fetching hook or cache invalidation call. + */ +export const queryKeys = { + courses: { + all: ['courses'] as const, + list: (params?: { cursor?: string; limit?: number }) => + ['courses', 'list', params] as const, + detail: (id: string) => ['courses', id] as const, + lessons: (courseId: string) => ['courses', courseId, 'lessons'] as const, + }, + user: { + all: ['user'] as const, + progress: () => ['user', 'progress'] as const, + }, + search: { + all: ['search'] as const, + results: (query: string) => ['search', query] as const, + }, + bookmarks: { + all: ['bookmarks'] as const, + byLesson: (lessonId: string) => ['bookmarks', lessonId] as const, + }, + notes: { + all: ['notes'] as const, + byLesson: (lessonId: string) => ['notes', lessonId] as const, + }, +} as const;