fix: HIGH priority — scroll debounce, timing-safe auth, a11y, error logging, cleanup dead modals

- Header: throttle scroll handler via requestAnimationFrame (was firing 60+/sec)
- Auth: use crypto.timingSafeEqual for password and token signature comparison
- A11y: add role="dialog", aria-modal, aria-label to all modals (SignupModal, NewsModal, TeamProfile lightbox)
- A11y: add aria-label to close buttons, menu toggle (with aria-expanded), floating CTA
- A11y: add aria-label to MC Instagram buttons
- Error logging: add console.error with route names to all API catch blocks (admin + public)
- Fix open-day-register error leak (was returning raw DB error to client)
- Fix MasterClasses key={index} → key={item.title}
- Delete 3 unused modal components (BookingModal, MasterClassSignupModal, OpenDaySignupModal) — replaced by unified SignupModal

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-19 14:01:21 +03:00
parent 127990e532
commit 66dce3f8f5
19 changed files with 56 additions and 638 deletions

View File

@@ -21,7 +21,8 @@ export async function PUT(request: NextRequest) {
return NextResponse.json({ ok: true });
}
return NextResponse.json({ error: "Unknown action" }, { status: 400 });
} catch {
} catch (err) {
console.error("[admin/group-bookings] error:", err);
return NextResponse.json({ error: "Internal error" }, { status: 500 });
}
}

View File

@@ -19,7 +19,8 @@ export async function POST(request: NextRequest) {
}
const id = addMcRegistration(masterClassTitle.trim(), name.trim(), instagram.trim(), telegram?.trim() || undefined);
return NextResponse.json({ ok: true, id });
} catch {
} catch (err) {
console.error("[admin/mc-registrations] error:", err);
return NextResponse.json({ error: "Internal error" }, { status: 500 });
}
}
@@ -48,7 +49,8 @@ export async function PUT(request: NextRequest) {
}
updateMcRegistration(id, name.trim(), instagram.trim(), telegram?.trim() || undefined);
return NextResponse.json({ ok: true });
} catch {
} catch (err) {
console.error("[admin/mc-registrations] error:", err);
return NextResponse.json({ error: "Internal error" }, { status: 500 });
}
}

View File

@@ -28,7 +28,8 @@ export async function PUT(request: NextRequest) {
return NextResponse.json({ ok: true });
}
return NextResponse.json({ error: "Unknown action" }, { status: 400 });
} catch {
} catch (err) {
console.error("[admin/open-day/bookings] error:", err);
return NextResponse.json({ error: "Internal error" }, { status: 500 });
}
}

View File

@@ -39,7 +39,8 @@ export async function PUT(request: NextRequest) {
const { id, ...data } = body;
updateOpenDayClass(id, data);
return NextResponse.json({ ok: true });
} catch {
} catch (err) {
console.error("[admin/open-day/classes] error:", err);
return NextResponse.json({ error: "Internal error" }, { status: 500 });
}
}

View File

@@ -27,7 +27,8 @@ export async function POST(request: NextRequest) {
}
const id = createOpenDayEvent(body);
return NextResponse.json({ ok: true, id });
} catch {
} catch (err) {
console.error("[admin/open-day] error:", err);
return NextResponse.json({ error: "Internal error" }, { status: 500 });
}
}
@@ -39,7 +40,8 @@ export async function PUT(request: NextRequest) {
const { id, ...data } = body;
updateOpenDayEvent(id, data);
return NextResponse.json({ ok: true });
} catch {
} catch (err) {
console.error("[admin/open-day] error:", err);
return NextResponse.json({ error: "Internal error" }, { status: 500 });
}
}

View File

@@ -30,7 +30,8 @@ export async function PUT(request: NextRequest) {
status as ReminderStatus | null
);
return NextResponse.json({ ok: true });
} catch {
} catch (err) {
console.error("[admin/reminders] error:", err);
return NextResponse.json({ error: "Internal error" }, { status: 500 });
}
}

View File

@@ -20,7 +20,8 @@ export async function GET(request: NextRequest) {
// Instagram returns 200 for existing profiles, 404 for non-existing
const valid = res.ok;
return NextResponse.json({ valid });
} catch {
} catch (err) {
console.error("[admin/validate-instagram] error:", err);
// Network error or timeout — don't block the user
return NextResponse.json({ valid: true, uncertain: true });
}

View File

@@ -27,7 +27,8 @@ export async function POST(request: NextRequest) {
const id = addGroupBooking(cleanName, cleanPhone, cleanGroup, cleanIg, cleanTg);
return NextResponse.json({ ok: true, id });
} catch {
} catch (err) {
console.error("[group-booking] POST error:", err);
return NextResponse.json({ error: "Internal error" }, { status: 500 });
}
}

View File

@@ -38,7 +38,8 @@ export async function POST(request: Request) {
);
return NextResponse.json({ ok: true, id });
} catch {
} catch (err) {
console.error("[master-class-register] POST error:", err);
return NextResponse.json({ error: "Internal error" }, { status: 500 });
}
}

View File

@@ -58,6 +58,7 @@ export async function POST(request: NextRequest) {
if (msg.includes("UNIQUE")) {
return NextResponse.json({ error: "Вы уже записаны на это занятие" }, { status: 409 });
}
return NextResponse.json({ error: msg }, { status: 500 });
console.error("[open-day-register] POST error:", e);
return NextResponse.json({ error: "Internal error" }, { status: 500 });
}
}