fix: MEDIUM priority — shared validation, content caching, Schedule useReducer, stable keys
- Extract shared sanitization to src/lib/validation.ts, apply to all 3 registration routes (#2) - Replace key={index} with stable keys in About and News (#4) - Add 5-min in-memory content cache in content.ts, invalidate on admin section save (#6) - Refactor Schedule from 8 useState calls to useReducer — single dispatch, fewer re-renders (#8) - Remove Hero scroll indicator, add auto-scroll to next section on wheel/swipe Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,12 +2,28 @@ import { getSiteContent } from "@/lib/db";
|
||||
import { siteContent as fallback } from "@/data/content";
|
||||
import type { SiteContent } from "@/types/content";
|
||||
|
||||
let cached: { data: SiteContent; expiresAt: number } | null = null;
|
||||
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
|
||||
|
||||
export function getContent(): SiteContent {
|
||||
const now = Date.now();
|
||||
if (cached && now < cached.expiresAt) {
|
||||
return cached.data;
|
||||
}
|
||||
|
||||
try {
|
||||
const content = getSiteContent();
|
||||
if (content) return content;
|
||||
if (content) {
|
||||
cached = { data: content, expiresAt: now + CACHE_TTL };
|
||||
return content;
|
||||
}
|
||||
return fallback;
|
||||
} catch {
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
|
||||
/** Invalidate the content cache (call after admin edits). */
|
||||
export function invalidateContentCache() {
|
||||
cached = null;
|
||||
}
|
||||
|
||||
27
src/lib/validation.ts
Normal file
27
src/lib/validation.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Shared input sanitization for public registration endpoints.
|
||||
*/
|
||||
|
||||
export function sanitizeName(name: unknown): string | null {
|
||||
if (!name || typeof name !== "string") return null;
|
||||
const clean = name.trim().slice(0, 100);
|
||||
return clean || null;
|
||||
}
|
||||
|
||||
export function sanitizePhone(phone: unknown): string | null {
|
||||
if (!phone || typeof phone !== "string") return null;
|
||||
const clean = phone.replace(/\D/g, "").slice(0, 15);
|
||||
return clean.length >= 9 ? clean : null;
|
||||
}
|
||||
|
||||
export function sanitizeHandle(value: unknown): string | undefined {
|
||||
if (!value || typeof value !== "string") return undefined;
|
||||
const clean = value.trim().slice(0, 100);
|
||||
return clean || undefined;
|
||||
}
|
||||
|
||||
export function sanitizeText(value: unknown, maxLength: number = 200): string | undefined {
|
||||
if (!value || typeof value !== "string") return undefined;
|
||||
const clean = value.trim().slice(0, maxLength);
|
||||
return clean || undefined;
|
||||
}
|
||||
Reference in New Issue
Block a user