From 089d73eb5066cf97b640c6900e0bbc5b0e8b2b5a Mon Sep 17 00:00:00 2001 From: koreacomp5 Date: Thu, 9 Oct 2025 17:18:49 +0900 Subject: [PATCH] =?UTF-8?q?8.1=20=EC=B6=9C=EC=84=9D=EB=B6=80:=20=EB=8D=B0?= =?UTF-8?q?=EC=9D=BC=EB=A6=AC=20=EC=B2=B4=ED=81=AC=EC=9D=B8/=EC=A4=91?= =?UTF-8?q?=EB=B3=B5=20=EB=B0=A9=EC=A7=80/=ED=8F=AC=EC=9D=B8=ED=8A=B8=20?= =?UTF-8?q?=EC=A7=80=EA=B8=89/=EB=88=84=EC=A0=81=20=ED=86=B5=EA=B3=84=20o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/api/attendance/route.ts | 28 ++++++++++++++++++++++++++++ src/app/attendance/page.tsx | 17 +++++++++++++++++ todolist.txt | 2 +- 3 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 src/app/api/attendance/route.ts create mode 100644 src/app/attendance/page.tsx diff --git a/src/app/api/attendance/route.ts b/src/app/api/attendance/route.ts new file mode 100644 index 0000000..8353c25 --- /dev/null +++ b/src/app/api/attendance/route.ts @@ -0,0 +1,28 @@ +import { NextResponse } from "next/server"; +import prisma from "@/lib/prisma"; +import { getUserIdFromRequest } from "@/lib/auth"; + +export async function GET(req: Request) { + const userId = getUserIdFromRequest(req); + if (!userId) return NextResponse.json({ today: null, count: 0 }); + const start = new Date(); start.setHours(0,0,0,0); + const end = new Date(); end.setHours(23,59,59,999); + const today = await prisma.pointTransaction.findFirst({ + where: { userId, reason: "attendance", createdAt: { gte: start, lte: end } }, + }); + const count = await prisma.pointTransaction.count({ where: { userId, reason: "attendance" } }); + return NextResponse.json({ today: !!today, count }); +} + +export async function POST(req: Request) { + const userId = getUserIdFromRequest(req); + if (!userId) return NextResponse.json({ error: "login required" }, { status: 401 }); + const start = new Date(); start.setHours(0,0,0,0); + const end = new Date(); end.setHours(23,59,59,999); + const exists = await prisma.pointTransaction.findFirst({ where: { userId, reason: "attendance", createdAt: { gte: start, lte: end } } }); + if (exists) return NextResponse.json({ ok: true, duplicated: true }); + await prisma.pointTransaction.create({ data: { userId, amount: 10, reason: "attendance" } }); + return NextResponse.json({ ok: true }); +} + + diff --git a/src/app/attendance/page.tsx b/src/app/attendance/page.tsx new file mode 100644 index 0000000..e1ddb63 --- /dev/null +++ b/src/app/attendance/page.tsx @@ -0,0 +1,17 @@ +"use client"; +import useSWR from "swr"; + +const fetcher = (url: string) => fetch(url).then((r) => r.json()); + +export default function AttendancePage() { + const { data, mutate } = useSWR<{ today: boolean; count: number }>("/api/attendance", fetcher); + return ( +
+

출석부

+

오늘 출석: {data?.today ? "✅" : "❌"} / 누적: {data?.count ?? 0}

+ +
+ ); +} + + diff --git a/todolist.txt b/todolist.txt index 05cbaf0..bd4b9cf 100644 --- a/todolist.txt +++ b/todolist.txt @@ -52,7 +52,7 @@ 7.10 제휴업소 일반(사진) 카테고리 전용 이미지 첨부/미리보기 규칙 o [특수 페이지(카테고리)] -8.1 출석부: 데일리 체크인/중복 방지/포인트 지급/누적 통계 +8.1 출석부: 데일리 체크인/중복 방지/포인트 지급/누적 통계 o 8.2 포인트안내: 정책 안내 페이지(에디터 연동/버전 이력) 8.3 회원랭킹: 기간별 랭킹 집계/캐싱/페이지네이션/정렬 옵션 8.4 무료쿠폰: 쿠폰 등록/재고/사용 처리/만료/1인 제한/로그