diff --git a/apps/web/src/app/stores/new/actions.ts b/apps/web/src/app/stores/new/actions.ts new file mode 100644 index 0000000..86165c4 --- /dev/null +++ b/apps/web/src/app/stores/new/actions.ts @@ -0,0 +1,73 @@ +'use server'; + +import { redirect } from 'next/navigation'; +import { createPrismaClient } from '@startover/database'; +import { auth } from '@/lib/auth'; +import { createStoreDraftService } from '@/services/store-service'; +import type { CreateStoreDraftInput } from '@startover/domain'; + +export type StoreFormState = { + error?: string; +}; + +export async function createStoreDraftAction( + _prevState: StoreFormState, + formData: FormData, +): Promise { + const session = await auth(); + if (!session?.user?.dbId) { + return { error: '로그인이 필요합니다.' }; + } + + const listingTitle = (formData.get('listingTitle') as string | null)?.trim() ?? ''; + const regionClusterCode = (formData.get('regionClusterCode') as string | null) ?? ''; + const industryLeafCode = (formData.get('industryLeafCode') as string | null) ?? ''; + const roadAddress = (formData.get('roadAddress') as string | null)?.trim() ?? ''; + const depositAmount = formData.get('depositAmount') as string | null; + const monthlyRentAmount = formData.get('monthlyRentAmount') as string | null; + const premiumAmount = formData.get('premiumAmount') as string | null; + const remainingLeaseMonths = formData.get('remainingLeaseMonths') as string | null; + const exclusiveAreaSqm = formData.get('exclusiveAreaSqm') as string | null; + const floorLevel = formData.get('floorLevel') as string | null; + const kitchenEquipmentSummary = + (formData.get('kitchenEquipmentSummary') as string | null)?.trim() ?? ''; + + const input: CreateStoreDraftInput = { + ownerUserId: session.user.dbId, + listingTitle, + industryLeafCode, + regionClusterCode, + roadAddress, + ...(depositAmount || monthlyRentAmount || premiumAmount || remainingLeaseMonths + ? { + lease: { + depositAmount: depositAmount ? Number(depositAmount) : 0, + monthlyRentAmount: monthlyRentAmount ? Number(monthlyRentAmount) : 0, + premiumAmount: premiumAmount ? Number(premiumAmount) : 0, + remainingLeaseMonths: remainingLeaseMonths + ? parseInt(remainingLeaseMonths, 10) + : undefined, + }, + } + : {}), + ...(exclusiveAreaSqm || floorLevel || kitchenEquipmentSummary + ? { + facility: { + exclusiveAreaSqm: exclusiveAreaSqm ? Number(exclusiveAreaSqm) : 0, + seatCount: 0, + floorLevel: floorLevel ? parseInt(floorLevel, 10) : undefined, + kitchenEquipmentSummary: kitchenEquipmentSummary || undefined, + }, + } + : {}), + }; + + const prisma = createPrismaClient(); + const result = await createStoreDraftService(prisma, input); + + if (!result.ok) { + return { error: result.error.message }; + } + + redirect(`/stores/${result.value.publicId}`); +} diff --git a/apps/web/src/app/stores/new/page.tsx b/apps/web/src/app/stores/new/page.tsx index a964e39..39adcaf 100644 --- a/apps/web/src/app/stores/new/page.tsx +++ b/apps/web/src/app/stores/new/page.tsx @@ -1,83 +1,19 @@ +'use client'; + +import { useActionState } from 'react'; import Link from 'next/link'; -import { redirect } from 'next/navigation'; -import { createPrismaClient } from '@startover/database'; -import { auth } from '@/lib/auth'; -import { createStoreDraftService } from '@/services/store-service'; -import type { CreateStoreDraftInput } from '@startover/domain'; +import { createStoreDraftAction, type StoreFormState } from './actions'; -export const dynamic = 'force-dynamic'; +const initialState: StoreFormState = {}; -async function createStoreDraftAction(formData: FormData) { - 'use server'; +export default function NewStorePage() { + const [state, formAction, isPending] = useActionState(createStoreDraftAction, initialState); - const session = await auth(); - if (!session?.user?.dbId) { - redirect('/auth/signin'); - } - - const listingTitle = (formData.get('listingTitle') as string | null)?.trim() ?? ''; - const regionClusterCode = (formData.get('regionClusterCode') as string | null) ?? ''; - const industryLeafCode = (formData.get('industryLeafCode') as string | null) ?? ''; - const roadAddress = (formData.get('roadAddress') as string | null)?.trim() ?? ''; - const depositAmount = formData.get('depositAmount') as string | null; - const monthlyRentAmount = formData.get('monthlyRentAmount') as string | null; - const premiumAmount = formData.get('premiumAmount') as string | null; - const remainingLeaseMonths = formData.get('remainingLeaseMonths') as string | null; - const exclusiveAreaSqm = formData.get('exclusiveAreaSqm') as string | null; - const floorLevel = formData.get('floorLevel') as string | null; - const kitchenEquipmentSummary = - (formData.get('kitchenEquipmentSummary') as string | null)?.trim() ?? ''; - - const input: CreateStoreDraftInput = { - ownerUserId: session.user.dbId, - listingTitle, - industryLeafCode, - regionClusterCode, - roadAddress, - ...(depositAmount || monthlyRentAmount || premiumAmount || remainingLeaseMonths - ? { - lease: { - depositAmount: depositAmount ? Number(depositAmount) : 0, - monthlyRentAmount: monthlyRentAmount ? Number(monthlyRentAmount) : 0, - premiumAmount: premiumAmount ? Number(premiumAmount) : 0, - remainingLeaseMonths: remainingLeaseMonths - ? parseInt(remainingLeaseMonths, 10) - : undefined, - }, - } - : {}), - ...(exclusiveAreaSqm || floorLevel || kitchenEquipmentSummary - ? { - facility: { - exclusiveAreaSqm: exclusiveAreaSqm ? Number(exclusiveAreaSqm) : 0, - seatCount: 0, - floorLevel: floorLevel ? parseInt(floorLevel, 10) : undefined, - kitchenEquipmentSummary: kitchenEquipmentSummary || undefined, - }, - } - : {}), - }; - - const prisma = createPrismaClient(); - const result = await createStoreDraftService(prisma, input); - - if (!result.ok) { - redirect(`/stores/new?error=${encodeURIComponent(result.error.message)}`); - } - - redirect(`/stores/${result.value.publicId}`); -} - -export default function NewStorePage({ - searchParams, -}: { - searchParams: Promise<{ error?: string }>; -}) { return (
- ← 매장 목록으로 + ← 매장 목록으로
@@ -86,9 +22,13 @@ export default function NewStorePage({ 매장 정보를 등록하면 창업자, 철거업체, 인테리어업체와 매칭됩니다

- + {state.error && ( +
+ {state.error} +
+ )} -
+ {/* 기본 정보 */}

기본 정보

@@ -234,9 +174,10 @@ export default function NewStorePage({
); } - -async function ErrorBanner({ searchParams }: { searchParams: Promise<{ error?: string }> }) { - const params = await searchParams; - if (!params.error) return null; - return ( -
- {params.error} -
- ); -}