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
25 changes: 17 additions & 8 deletions src/app/api/auth/signup/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,55 +14,64 @@ export const runtime = 'edge';
export async function POST(
request: NextRequest,
): Promise<NextResponse<AuthResponseDTO | AuthErrorDTO>> {
edgeLog('info', '/api/auth/signup', 'POST request received');
const route = '/api/auth/signup';
edgeLog('info', route, 'POST request received');

const { addHeaders, rateLimitResponse } = withRateLimit(request, 'AUTH');
if (rateLimitResponse) return rateLimitResponse as NextResponse;

try {
const result = validateBody(SignupRequestSchema, await request.json());
if (!result.ok) return addHeaders(result.error) as NextResponse;
if (!result.ok) {
edgeLog('warn', route, 'Validation failed', { reason: 'schema' });
return addHeaders(result.error) as NextResponse;
}

const { name, email, password, confirmPassword } = result.data;

// Basic validation
if (!name || !email || !password || !confirmPassword) {
edgeLog('warn', route, 'Validation failed', { reason: 'missing_fields' });
return addHeaders(NextResponse.json({ message: 'All fields are required' }, { status: 400 }));
}

if (password !== confirmPassword) {
edgeLog('warn', route, 'Validation failed', { reason: 'password_mismatch' });
return addHeaders(NextResponse.json({ message: "Passwords don't match" }, { status: 400 }));
}

if (password.length < 6) {
edgeLog('warn', route, 'Validation failed', { reason: 'password_too_short' });
return addHeaders(
NextResponse.json({ message: 'Password must be at least 6 characters' }, { status: 400 }),
);
}

// Mock: block already-registered email
if (email === 'existing@teachlink.com') {
edgeLog('warn', route, 'Registration conflict', { reason: 'email_exists' });
return addHeaders(
NextResponse.json({ message: 'Email already registered' }, { status: 409 }),
);
}

const userId = Math.random().toString(36).substring(2, 9);
edgeLog('info', route, 'Account created', { userId });

return addHeaders(
NextResponse.json(
{
message: 'Account created successfully',
user: {
id: Math.random().toString(36).substring(2, 9),
name,
email,
},
user: { id: userId, name, email },
token: `mock-jwt-token-${Date.now()}`,
},
{ status: 201 },
),
);
} catch (error) {
console.error('Signup error:', error);
edgeLog('error', route, 'Unhandled signup error', {
error: error instanceof Error ? error.message : String(error),
});

return addHeaders(NextResponse.json({ message: 'Internal server error' }, { status: 500 }));
}
Expand Down
33 changes: 33 additions & 0 deletions src/app/api/errors/report/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { NextRequest, NextResponse } from 'next/server';
import { createLogger } from '@/lib/logging';

const logger = createLogger('errors.report');

export async function POST(request: NextRequest): Promise<NextResponse> {
try {
const report = await request.json();

// Build a real Error so normalizeError captures name + message + stack properly
const clientError = report.errorData?.message
? Object.assign(new Error(report.errorData.message), {
name: report.errorData.type ?? 'ClientError',
})
: undefined;

logger.error('Client error report', {
context: {
reportId: report.id,
sessionId: report.sessionId,
userId: report.userId,
url: report.url,
environment: report.environment,
},
error: clientError,
});

return NextResponse.json({ ok: true }, { status: 200 });
} catch (err) {
logger.warn('Failed to process error report', { error: err });
return NextResponse.json({ ok: false }, { status: 400 });
}
}
11 changes: 10 additions & 1 deletion src/app/global-error.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
'use client';

import React from 'react';
import React, { useEffect } from 'react';
import { UserFriendlyErrorDisplay } from '@/components/errors/UserFriendlyErrorDisplay';
import { errorReportingService } from '@/services/errorReporting';

export default function GlobalError({
error,
Expand All @@ -10,6 +11,14 @@ export default function GlobalError({
error: Error & { digest?: string };
reset: () => void;
}) {
useEffect(() => {
errorReportingService.addBreadcrumb('global-error', {
errorMessage: error.message,
digest: error.digest,
});
errorReportingService.reportError(error);
}, [error]);

return (
<html>
<body>
Expand Down
Loading