From b2b5d34d1722878dddc65031ccca584f41c416b0 Mon Sep 17 00:00:00 2001 From: Meirbek-dev Date: Mon, 16 Dec 2024 19:41:59 +0500 Subject: [PATCH] improved error handling for root route --- app/(root)/(routes)/page.tsx | 30 +++++++++--- app/(root)/layout.tsx | 49 ++++++++++++++----- .../billboards/[billboardId]/route.ts | 12 ++--- app/api/[storeId]/sizes/[sizeId]/route.ts | 20 ++++++-- app/api/webhook/route.ts | 4 +- 5 files changed, 83 insertions(+), 32 deletions(-) diff --git a/app/(root)/(routes)/page.tsx b/app/(root)/(routes)/page.tsx index 9d425ea..63b7f97 100644 --- a/app/(root)/(routes)/page.tsx +++ b/app/(root)/(routes)/page.tsx @@ -1,19 +1,37 @@ 'use client'; -import { useEffect } from 'react'; +import { useEffect, useCallback } from 'react'; +import { useRouter } from 'next/navigation'; import { useStoreModal } from '@/hooks/use-store-modal'; +// Компонент настройки магазина const SetupPage = () => { - const onOpen = useStoreModal((state) => state.onOpen); - const isOpen = useStoreModal((state) => state.isOpen); + const router = useRouter(); + + // Используем селекторы из хука с мемоизацией + const { onOpen, isOpen } = useStoreModal( + useCallback( + (state) => ({ + onOpen: state.onOpen, + isOpen: state.isOpen, + }), + [], + ), + ); useEffect(() => { - if (!isOpen) { - onOpen(); + try { + if (!isOpen) { + onOpen(); + } + } catch (error) { + console.error('[SETUP_PAGE]', error); } }, [isOpen, onOpen]); - return null; + // Возвращаем пустой фрагмент вместо null для лучшей производительности + return <>; }; + export default SetupPage; diff --git a/app/(root)/layout.tsx b/app/(root)/layout.tsx index 5355bd6..c2f4adb 100644 --- a/app/(root)/layout.tsx +++ b/app/(root)/layout.tsx @@ -1,24 +1,47 @@ import { auth } from '@clerk/nextjs/server'; import { redirect } from 'next/navigation'; +import { cache } from 'react'; +import { z } from 'zod'; import prismadb from '@/lib/prismadb'; +import { errorResponses } from '@/lib/error-responses'; + +// Кэширование запроса на получение магазина +const getStore = cache(async (userId: string) => { + return prismadb.store.findFirst({ + where: { userId }, + select: { + id: true, + name: true, + createdAt: true, + }, + }); +}); + +// Схема валидации для userId +const userIdSchema = z.string().min(1, 'ID пользователя обязателен'); export default async function SetupLayout({ children }: { children: React.ReactNode }) { - const { userId }: { userId: string | null } = await auth(); + try { + const { userId } = await auth(); - if (!userId) { - redirect('/sign-in'); - } + // Валидация userId + const validationResult = userIdSchema.safeParse(userId); - const store = await prismadb.store.findFirst({ - where: { - userId, - }, - }); + if (!validationResult.success) { + redirect('/sign-in'); + } - if (store) { - redirect(`/${store.id}`); - } + // Используем кэшированную функцию для получения магазина + const store = await getStore(validationResult.data); - return <>{children}; + if (store) { + redirect(`/${store.id}`); + } + + return
{children}
; + } catch (error) { + console.error('[SETUP_LAYOUT]', error); + redirect('/error'); + } } diff --git a/app/api/[storeId]/billboards/[billboardId]/route.ts b/app/api/[storeId]/billboards/[billboardId]/route.ts index 2d02f3d..8f831bb 100644 --- a/app/api/[storeId]/billboards/[billboardId]/route.ts +++ b/app/api/[storeId]/billboards/[billboardId]/route.ts @@ -22,11 +22,7 @@ const getBillboards = cache(async (storeId: string) => { // Расширенная схема валидации для создания билборда const billboardSchema = z.object({ - label: z - .string() - .min(1, 'Укажите метку') - .max(100, 'Метка слишком длинная') - .trim(), + label: z.string().min(1, 'Укажите метку').max(100, 'Метка слишком длинная').trim(), imageUrl: z .string() .url('Укажите корректный URL изображения') @@ -108,9 +104,9 @@ export async function POST(request: Request, props: { params: Promise<{ storeId: status: 201, headers: { 'Cache-Control': 'no-cache, no-store, must-revalidate', - 'Pragma': 'no-cache', - 'Expires': '0', - } + Pragma: 'no-cache', + Expires: '0', + }, }); } catch (error) { console.error('[BILLBOARDS_POST]', error); diff --git a/app/api/[storeId]/sizes/[sizeId]/route.ts b/app/api/[storeId]/sizes/[sizeId]/route.ts index 86d6133..6e9e5fc 100644 --- a/app/api/[storeId]/sizes/[sizeId]/route.ts +++ b/app/api/[storeId]/sizes/[sizeId]/route.ts @@ -17,12 +17,20 @@ export async function GET(request: Request, props: { params: Promise<{ sizeId: s const params = await props.params; if (!params.sizeId) { - return new NextResponse('Необходим идентификатор размера.', { status: 400 }); + return new NextResponse('Необходим идентификатор размера.', { + status: 400, + }); } const size = await prismadb.size.findUnique({ where: { id: params.sizeId }, - select: { id: true, name: true, value: true, createdAt: true, updatedAt: true }, + select: { + id: true, + name: true, + value: true, + createdAt: true, + updatedAt: true, + }, }); if (!size) { @@ -50,7 +58,9 @@ export async function DELETE( } if (!params.sizeId) { - return new NextResponse('Необходим идентификатор размера.', { status: 400 }); + return new NextResponse('Необходим идентификатор размера.', { + status: 400, + }); } const size = await prismadb.$transaction(async (tx) => { @@ -95,7 +105,9 @@ export async function PATCH( } if (!params.sizeId) { - return new NextResponse('Необходим идентификатор размера.', { status: 400 }); + return new NextResponse('Необходим идентификатор размера.', { + status: 400, + }); } // Валидация входных данных diff --git a/app/api/webhook/route.ts b/app/api/webhook/route.ts index a0385d6..1a3cd46 100644 --- a/app/api/webhook/route.ts +++ b/app/api/webhook/route.ts @@ -39,7 +39,9 @@ export async function POST(request: Request) { // Validate session and metadata if (!session?.metadata?.orderId) { - return new NextResponse('Missing order ID in session metadata', { status: 400 }); + return new NextResponse('Missing order ID in session metadata', { + status: 400, + }); } const orderId = session.metadata.orderId;