Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
@@ -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';
9 changes: 7 additions & 2 deletions src/context/ToastContext.tsx
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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 (
<ToastContext.Provider value={{ toasts, addToast, removeToast, error, success, info }}>
<ToastContext.Provider value={value}>
{children}
<div className="fixed bottom-4 right-4 z-50 flex flex-col gap-2 pointer-events-none">
{toasts.map((toast) => (
Expand Down
20 changes: 20 additions & 0 deletions src/lib/authMiddleware.ts
Original file line number Diff line number Diff line change
@@ -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;
}
29 changes: 29 additions & 0 deletions src/lib/queryKeys.ts
Original file line number Diff line number Diff line change
@@ -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;
Loading