From 64e8a4427d21df97925752cf47a8f5a92fa067f7 Mon Sep 17 00:00:00 2001 From: deonisii Date: Fri, 17 Apr 2026 16:10:29 +0300 Subject: [PATCH] =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B0=20=D1=84=D0=BE=D1=80=D0=BC=D0=B0=20=D0=B7=D0=B0?= =?UTF-8?q?=D1=8F=D0=B2=D0=BA=D0=B8=20=D1=82=D0=B5=D1=81=D1=82=D0=BE=D0=B2?= =?UTF-8?q?=D0=B0=D1=8F,=20=D1=82=D0=B5=D1=81=D1=82=D0=BE=D0=B2=D0=B0?= =?UTF-8?q?=D1=8F=20=D1=81=D1=82=D1=82=D1=80=D0=B0=D0=BD=D0=B8=D1=86=D0=B0?= =?UTF-8?q?=20crm=20=D0=B1=D0=B0=D0=B7=D0=B0=20=D0=B4=D0=B0=D0=BD=D0=BD?= =?UTF-8?q?=D1=8B=D1=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 + Dockerfile | 2 + app/admin/leads/page.tsx | 56 + app/api/leads/route.ts | 58 + app/api/test/route.ts | 10 + app/page.tsx | 25 +- components/lead-form.tsx | 89 + docker-compose.yml | 21 +- lib/prisma.ts | 21 + package-lock.json | 1750 ++++++++++++++++- package.json | 5 + prisma.config.ts | 12 + .../20260417121735_init_leads/migration.sql | 16 + prisma/migrations/migration_lock.toml | 3 + prisma/schema.prisma | 26 + 15 files changed, 2051 insertions(+), 45 deletions(-) create mode 100644 app/admin/leads/page.tsx create mode 100644 app/api/leads/route.ts create mode 100644 app/api/test/route.ts create mode 100644 components/lead-form.tsx create mode 100644 lib/prisma.ts create mode 100644 prisma.config.ts create mode 100644 prisma/migrations/20260417121735_init_leads/migration.sql create mode 100644 prisma/migrations/migration_lock.toml create mode 100644 prisma/schema.prisma diff --git a/.gitignore b/.gitignore index 5ef6a52..45254b6 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,5 @@ yarn-error.log* # typescript *.tsbuildinfo next-env.d.ts + +/app/generated/prisma diff --git a/Dockerfile b/Dockerfile index 05c40ef..530d907 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,6 +9,7 @@ FROM base AS builder WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY . . +RUN npx prisma generate RUN npm run build FROM base AS runner @@ -19,6 +20,7 @@ ENV PORT=3000 ENV HOSTNAME=0.0.0.0 COPY --from=builder /app/public ./public +COPY --from=builder /app/prisma ./prisma COPY --from=builder /app/.next/standalone ./ COPY --from=builder /app/.next/static ./.next/static diff --git a/app/admin/leads/page.tsx b/app/admin/leads/page.tsx new file mode 100644 index 0000000..3e4593d --- /dev/null +++ b/app/admin/leads/page.tsx @@ -0,0 +1,56 @@ +import { prisma } from "@/lib/prisma"; + +export const dynamic = "force-dynamic"; + +export default async function AdminLeadsPage() { + const leads = await prisma.lead.findMany({ + orderBy: { createdAt: "desc" }, + }); + + return ( +
+
+

Заявки

+ +
+ + + + + + + + + + + + + {leads.map((lead) => ( + + + + + + + + + ))} + + {leads.length === 0 && ( + + + + )} + +
ДатаКомпанияТелефонСообщениеСтатусИсточник
+ {new Date(lead.createdAt).toLocaleString("ru-RU")} + {lead.company}{lead.phone} + {lead.message || "—"} + {lead.status}{lead.source}
+ Пока заявок нет +
+
+
+
+ ); +} \ No newline at end of file diff --git a/app/api/leads/route.ts b/app/api/leads/route.ts new file mode 100644 index 0000000..06c5dc7 --- /dev/null +++ b/app/api/leads/route.ts @@ -0,0 +1,58 @@ +import { NextResponse } from "next/server"; +import { prisma } from "@/lib/prisma"; + +type LeadPayload = { + company?: string; + phone?: string; + message?: string; +}; + +export async function POST(request: Request) { + try { + const body = (await request.json()) as LeadPayload; + + const company = body.company?.trim(); + const phone = body.phone?.trim(); + const message = body.message?.trim() || ""; + + if (!company || !phone) { + return NextResponse.json( + { error: "Компания и телефон обязательны" }, + { status: 400 } + ); + } + + const lead = await prisma.lead.create({ + data: { + company, + phone, + message, + source: "website", + }, + }); + + return NextResponse.json({ success: true, leadId: lead.id }, { status: 201 }); + } catch (error) { + console.error("POST /api/leads error:", error); + return NextResponse.json( + { error: "Не удалось сохранить заявку" }, + { status: 500 } + ); + } +} + +export async function GET() { + try { + const leads = await prisma.lead.findMany({ + orderBy: { createdAt: "desc" }, + }); + + return NextResponse.json(leads); + } catch (error) { + console.error("GET /api/leads error:", error); + return NextResponse.json( + { error: "Не удалось получить заявки" }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/app/api/test/route.ts b/app/api/test/route.ts new file mode 100644 index 0000000..1176f2c --- /dev/null +++ b/app/api/test/route.ts @@ -0,0 +1,10 @@ +import { prisma } from "@/lib/prisma"; + +export async function GET() { + const leads = await prisma.lead.findMany(); + + return Response.json({ + ok: true, + count: leads.length, + }); +} \ No newline at end of file diff --git a/app/page.tsx b/app/page.tsx index e505167..5f398f6 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,5 +1,6 @@ import Link from "next/link"; import Image from "next/image"; +import LeadForm from "@/components/lead-form"; import { ArrowRight, Camera, @@ -305,28 +306,8 @@ export default function Home() {

-
- - -