Easy-to-manage touch screens
무료 오픈소스 터치스크린으로 USB 설정 가이드, 드래그‑드롭 편집기, 로컬 제어를 제공하며 Home Assistant용으로 제작되었습니다
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기공방 클래스 예약 서비스 MVP — 회원가입 없는 회차 예약 신청과 운영자 승인·클래스 관리 (Next.js · Supabase)
저장소 README에서 가져옴
README 전체를 불러오는 중입니다.
README 전문 보기 ↗회원가입 없이 공방 클래스를 예약 신청하고, 운영자가 승인·거절·취소와 클래스·회차를 관리하는 포트폴리오용 MVP입니다.
예약 신청 → 운영자 승인 → 고객 조회 → 고객 취소 → 자리 복구를 핵심 흐름으로 구현했습니다. 신청 완료는 승인 대기이며, 운영자 승인 후 참여가 확정됩니다.
배포 URL: https://workshop-booking-rho.vercel.app (운영자 로그인은 데모 정보 참고)
| 고객 · 클래스 목록 | 고객 · 클래스 상세와 예약 신청 |
|---|---|
| 운영자 · 예약 관리 | 운영자 · 클래스 편집과 회차 관리 |
| 고객 · 예약 조회 결과 (360px) | 운영자 · 예약 관리 (360px) |
Next.js 16 App Router·React 19·TypeScript strict mode를 사용합니다. UI는 Tailwind CSS 4·shadcn/ui·Lucide, 자동 테스트는 Vitest·React Testing Library(jsdom)로 구성했습니다.
| 결정 | 선택과 이유 |
|---|---|
| ADR-001 · Next.js App Router | 고객·운영자 화면과 REST API를 한 프로젝트에서 제공합니다. 공개 목록·상세는 서버 렌더링으로 검색 노출과 공유 미리보기를 지원합니다. |
| ADR-002 · 순수 규칙과 처리 순서 | rules.ts는 규칙, db.ts는 조회 → 검증 → 저장을 담당합니다. 서버 컴포넌트도 db.ts를 직접 호출해 HTTP 왕복을 줄입니다. |
| ADR-003 · Supabase | Postgres·Storage를 사용하고 테이블·함수·버킷·샘플을 SQL 파일 하나로 준비합니다. Postgres 함수의 트랜잭션과 행 잠금으로 정원을 보장합니다. |
| ADR-004 · 테스트 먼저 작성 | 구현 파일 옆에 테스트를 두고 가드로 작성 순서를 확인합니다. 규칙은 순수 함수로, 외부 호출은 모킹으로 검증하고 실제 DB 동작은 배포 환경에서 시연합니다. |
| ADR-005 · 요청 시각 전달 | 요청마다 한 번 읽은 시각을 규칙과 SQL 함수에 함께 전달합니다. 시간 경계를 고정 시각으로 테스트할 수 있습니다. |
| ADR-006 · HMAC 서명 쿠키 | 단일 운영자는 비밀번호로 로그인하고 12시간 유효한 httpOnly 쿠키로 인증합니다. 별도 세션 저장소와 사용자 테이블을 두지 않습니다. |
| ADR-007 · TanStack Query | 예약 조회·운영자 화면의 조회·변경·관련 쿼리 무효화를 관리합니다. 서버 상세의 회차·남은 자리는 router.refresh()로 갱신합니다. |
| ADR-008 · 수동 승인 | 승인 대기도 자리를 차지해 승인 시 정원이 초과되지 않습니다. 운영자가 처리하며 자동 만료는 없습니다. |
| ADR-009 · 비회원 예약 조회 | 예약번호와 전화번호 뒷자리 4자리로 조회·취소합니다. 일회성 예약에 고객 계정이 필요하지 않습니다. |
| ADR-010 · SQL 재실행으로 초기화 | 샘플을 다시 준비하는 절차를 SQL 파일 하나로 유지합니다. 시연 직전에 직접 초기화하며 정기 초기화나 조회 시 일정 보정은 하지 않습니다. |
| ADR-011 · 운영자 API로 이미지 업로드 | 운영자 인증과 형식·크기 검증 후 서버에서 Storage에 저장합니다. 브라우저는 자체 REST API만 호출합니다. |
각 결정의 트레이드오프는 ADR, 처리 구조와 상태 갱신은 아키텍처에 정리했습니다.
MVP 구현 범위:
MVP 제외 사항:
후속 범위는 실제 DB 연동·동시 신청(R-15)의 자동 검증입니다. 현재 자동 테스트는 실제 DB에 접속하지 않습니다. 정원 보장은 Postgres 함수의 행 잠금으로 구현했으며 배포 환경 시연으로 확인합니다. 정원 변경 경합, 회차 취소의 전체 반영, 삭제·시각 변경의 이력 보호에 대한 DB 연동 자동 검증도 후속 범위입니다.
기능 개요는 PRD에 정리했습니다.
테스트 이름의 R-xx는 아래 정책에 대응합니다. 신청·승인·고객 취소의 시간 제한은 서버가 업무 요청 처리를 시작할 때 한 번 읽은 요청 시각으로 판정하고, 상태·남은 자리는 저장 시점에 다시 확인합니다. 화면의 일정과 취소 기한은 항상 한국 시각(Asia/Seoul)으로 표시합니다.
| 내부 상태 | 표시명 |
|---|---|
pending | 승인 대기 |
confirmed | 참여 확정 |
rejected | 거절됨 |
cancelled | 취소됨 |
| 규칙 ID | 정책 요약 |
|---|---|
| R-01 | 차지된 자리는 승인 대기·참여 확정 예약 인원의 합입니다. |
| R-02 | 남은 자리는 정원에서 차지된 자리를 뺀 값입니다. |
| R-03 | 거절·취소 예약은 자리를 차지하지 않으며 해당 인원만큼 자리가 복구됩니다. |
| R-10 | 신청 성공 시 승인 대기 상태와 고객용 예약번호를 발급합니다. |
| R-11 | 신청 인원이 남은 자리보다 많으면 신청할 수 없습니다. |
| R-12 | 한 예약의 인원은 1~4명의 정수입니다. |
| R-13 | 취소된 회차에는 신청할 수 없습니다. |
| R-14 | 요청 시각이 회차 시작 시각 이상이면 신청할 수 없습니다. |
| R-15 | 동시 신청과 정원 변경이 경합해도 차지된 자리는 정원을 넘지 않습니다. |
| R-16 | 같은 회차·전화번호의 승인 대기·참여 확정 예약이 있으면 추가 신청할 수 없습니다. 공백·하이픈은 제거하며 거절·취소 후 재신청할 수 있습니다. |
| R-17 | 시작 전 승인·거절하며 자동 만료는 없습니다. 시작 후 미처리 건은 운영자가 거절·취소로 정리합니다. |
| R-18 | 고객은 예약 정보를 직접 수정할 수 없습니다. 기한 내 취소 후 재신청하며 기존 자리는 보장되지 않습니다. |
| R-19 | 예약번호 분실 시 공방에 문의하며 운영자가 이름·전체 전화번호로 검색해 안내합니다. |
| R-20 | 승인 대기 → 참여 확정·거절됨·취소됨, 참여 확정 → 취소됨만 허용하며 처리 주체와 기한을 검사합니다. |
| R-21 | 거절·취소는 되돌릴 수 없는 최종 상태입니다. |
| R-22 | 요청 시각이 회차 시작 시각 이상이면 승인할 수 없습니다. |
| R-30 | 예약번호·전화번호 뒷자리 4자리가 모두 일치해야 조회·취소할 수 있으며 틀린 항목을 구분하지 않습니다. |
| R-31 | 승인 대기는 시작 전까지, 참여 확정은 시작 24시간 전 정각까지 고객이 취소할 수 있습니다. 거절·취소 상태는 다시 취소할 수 없습니다. |
| R-32 | 운영자는 승인 대기·참여 확정 예약을 시간 제한 없이 취소할 수 있습니다. |
| R-33 | 고객은 조회 화면에서 상태와 신청자(일부 가림)·일시·장소·인원(예상 비용)·결제 방법·문의처를 직접 확인합니다. 시작 후 승인 대기는 참여 미확정·공방 문의를 안내합니다. |
| R-40 | 정원을 차지된 자리보다 적게 줄일 수 없습니다. |
| R-41 | 상태와 관계없이 예약 이력이 있는 회차는 삭제할 수 없으며 운영 중단은 회차 취소로 처리합니다. |
| R-42 | 회차 취소 시 승인 대기·참여 확정 예약을 함께 취소하며 거절·취소 이력과 고객 조회를 유지합니다. |
| R-43 | 예약 이력이 전혀 없는 회차는 삭제할 수 있습니다. |
| R-44 | 예약 이력이 있으면 시작 시각을 바꿀 수 없습니다. 이력이 없으면 미래 시각으로 변경하고, 이력이 있는 일정 변경은 취소·새 회차 생성으로 처리합니다. |
| R-45 | 취소된 회차는 다시 열 수 없으며 새 회차를 만들어야 합니다. |
| R-46 | 회차는 미래 시작 시각과 1 이상의 정수 정원으로 생성하며 소속 클래스는 바꿀 수 없습니다. |
| R-50 | 회차가 없는 클래스는 삭제할 수 있습니다. |
| R-51 | 회차가 있는 클래스는 삭제할 수 없으며 삭제 요청이 자동 비공개로 바뀌지 않습니다. |
로컬 검증 환경은 Node.js 22.14.0, npm 10.9.2입니다.
npm install
cp .env.example .env.local
.env.local에 아래 환경 변수를 설정하고, 아래 DB 준비 절차를 마친 뒤 실행합니다.
npm run dev
개발 서버는 http://localhost:3000에서 확인합니다. 검증 명령은 다음과 같습니다. 프로덕션 빌드에는 TypeScript 검사도 포함됩니다.
npm run test
npm run lint
npm run build
값은 로컬의 .env.local과 배포 환경에 설정합니다. 서버 전용 키·비밀번호·서명 키는 브라우저에 노출하지 않습니다.
| 이름 | 용도 |
|---|---|
SUPABASE_URL | Supabase 프로젝트 주소 |
SUPABASE_SERVICE_ROLE_KEY | 서버 전용 Supabase 서비스 역할 키: DB·Storage 접근 |
ADMIN_PASSWORD | 운영자 로그인 비밀번호 |
ADMIN_SESSION_SECRET | 운영자 세션 쿠키의 HMAC 서명 키 |
NEXT_PUBLIC_SITE_URL | OG·canonical 절대 주소 생성 |
MOCK_DB | 1이면 개발 서버에서 읽기 전용 목 데이터 모드 사용 (production에서는 무시) |
.env.local에 MOCK_DB=1과 ADMIN_PASSWORD·ADMIN_SESSION_SECRET만 설정하고 npm run dev를 실행하면, 아래 샘플 데이터와 같은 클래스·회차·예약으로 고객·운영자 화면을 조회할 수 있습니다. 샘플 회차 시각은 개발 서버 시작 시각 기준입니다. 신청·취소·승인과 클래스·회차 관리 같은 변경은 MOCK_READ_ONLY 오류로 거부합니다. 프로덕션 빌드(npm run build && npm start)에서는 켜지지 않습니다(ADR-012).
session_stats 뷰·class-images 공개 버킷·샘플 데이터를 준비합니다.public/samples/class-1.jpg~class-4.jpg(순서대로 도자기·가죽·꽃다발·캔들)는 저장소에 포함되어 있어 따로 준비하지 않습니다. 출처는 샘플 이미지 출처에 있습니다.주의: 다시 실행하면 시연 중 만든 예약·클래스·회차를 포함한 기존 DB 데이터를 모두 지우고 다시 만듭니다. 업로드한 Storage 파일은 초기화 후에도 남습니다.
샘플 회차 시각은 SQL 실행 시점 기준(now() + interval)으로 계산합니다. 지원서 제출 등 시연 직전에 직접 실행해야 합니다. 시연 회차와 목록용 클래스의 마지막 회차는 실행 57~66일 뒤에 있어, 실행 후 두 달 가까이는 다시 초기화하지 않아도 시연할 수 있습니다. 다만 시작 24시간 이내 취소 제한을 보여 주는 BK-DEMO04의 회차는 실행 20시간 뒤에 시작합니다. 시간이 지나면 시연 회차가 이미 시작했거나 방문자 조작으로 예약·남은 자리 등이 달라질 수 있습니다. 정기 자동 초기화나 서버 시작·고객 조회 시 일정 보정은 없습니다(ADR-010).
운영자 로그인 경로: /admin/login
운영자 비밀번호: demo1234
공개 데모용 비밀번호라 누구나 로그인해 데이터를 바꿀 수 있습니다. 예약·클래스 상태가 아래 표와 다르면 다른 방문자가 바꾼 것입니다.
샘플의 이름·주소·전화번호는 시연용 가상 데이터입니다. 아래는 SQL 샘플 주석 표이며 예상 상태는 초기화 직후 기준입니다. 전화번호 뒷자리는 앞자리 0을 포함한 네 자리로 입력합니다.
A는 도자기, B는 가죽, C는 회차가 없어 공개할 수 없는 비공개 꽃다발(공개 차단 시연), D는 비공개 캔들 클래스입니다. 상대 시각은 SQL 실행 시점을 기준으로 합니다. 이 밖에 목록을 채우는 공개 클래스 EN 10개가 있습니다. SQL 실행일(서울) 기준 918일 뒤와 57~66일 뒤 정각에 회차가 하나씩 있고 예약은 없습니다.
| 예약번호 | 전화번호 뒷자리 | 클래스·회차 | 예상 상태 | 시연 용도 |
|---|---|---|---|---|
| BK-DEMO01 | 0001 | A 도자기·63일 후 | pending | 핵심 시연(정원 6, 점유 2, 남은 자리 4) |
| BK-DEMO02 | 0002 | A 도자기·64일 후 | pending | 만석 회차의 승인 대기 2명 |
| BK-DEMO03 | 0003 | A 도자기·64일 후 | confirmed | 만석 회차의 참여 확정 2명 |
| BK-DEMO04 | 0004 | A 도자기·20시간 후 | confirmed | 24시간 이내 고객 직접 취소 제한 |
| BK-DEMO05 | 0005 | A 도자기·2시간 전 | pending | 시작 후 참여 미확정·공방 문의 |
| BK-DEMO06 | 0006 | A 도자기·65일 후(취소) | rejected | 취소된 회차의 거절 이력 조회 |
| BK-DEMO07 | 0007 | A 도자기·65일 후(취소) | cancelled | 취소된 회차의 취소 이력 조회 |
| BK-DEMO08 | 0008 | B 가죽·66일 후 | rejected | 거절 상태·자리 미점유 |
| BK-DEMO09 | 0009 | B 가죽·66일 후 | cancelled | 취소 상태·자리 복구 |
| BK-DEMO10 | 0010 | B 가죽·66일 후 | pending | 운영자 승인·거절·회차 취소 |
| BK-DEMO11 | 0011 | D 비공개 캔들·65일 후 | pending | 비공개 클래스 기존 예약 조회·취소 |
샘플 이미지는 Unsplash License로 제공되는 사진을 1200×900px로 잘라 저장소에 넣었습니다.
| 파일 | 클래스 | 사진 |
|---|---|---|
class-1.jpg | A 도자기 | Katja Vogt |
class-2.jpg | B 가죽 | Joel Lee |
class-3.jpg | C 꽃다발 | Yura Batiushyn |
class-4.jpg | D 캔들 | Mediamodifier |
목록용 클래스 중 E 부케·G 캔들·J 가죽 키링·L 물레 접시는 각각 class-3·class-4·class-2·class-1을 다시 쓰고, 나머지 6개는 이미지 없이 기본 아이콘으로 표시합니다.
/admin/login으로 로그인합니다. 회차·상태 필터로 해당 예약을 찾아 승인합니다./bookings/lookup에서 예약번호·전화번호 뒷자리로 재조회해 참여 확정을 확인합니다. 상세를 다시 조회했을 때 남은 자리는 승인 전과 같아야 합니다.BK-DEMO04의 시작 24시간 이내 확정 예약 취소 제한·공방 문의 안내를 확인합니다.자동 테스트는 Vitest·React Testing Library(jsdom)를 사용합니다. 규칙을 검증하는 테스트 이름에는 위 표의 규칙 ID를 포함하고, 시간 경계에는 고정 시각을 전달합니다.
| 대상 | 자동 검증 범위 |
|---|---|
src/lib/rules.ts | 자리 계산·신청·상태 전이·취소 기한 경계·입력 정규화·클래스/회차 관리·이미지 검증 |
src/lib/auth.ts | 비밀번호 비교·쿠키 서명·만료·변조·빈 값 거부 |
src/lib/db.ts | Supabase 모킹: 단일 요청 시각 전달·입력 오류 시 DB 미호출·오류 변환·고객 인증·Storage 정리 |
src/lib/api.ts, src/lib/http.ts | fetch 응답·네트워크 오류, JSON 파싱·인증·에러의 HTTP 응답 변환 |
src/app/api/**/route.ts | db 함수 모킹: 요청 파싱·HTTP 상태·운영자 401·허용하지 않는 예약 동작 404 |
src/components/*.tsx | 필드 오류·제출 중 비활성·실패 시 입력 유지·만석 시 상세 갱신·취소/삭제 확인·상태 텍스트·검색 |
src/lib/format.ts, QueryProvider | 한국 시각 표시·입력 변환, 쿼리 제공자 동작 |
src/proxy.ts | 쿠키 없음·무효 시 로그인 이동, 유효 쿠키 통과·로그인 경로 제외 |
실제 DB·Postgres 함수는 자동 테스트하지 않으며 다음 범위를 배포 환경에서 수동 확인합니다.
2026-10-24 배포 URL에서 브라우저 테스트 시나리오 BT-01~16을 실행해 모두 통과했습니다.
SESSION_FULL로 거부되며, 차지된 자리가 정원과 같습니다(R-15).created_at 하나로만 정렬했습니다. 샘플 클래스는 SQL 한 번에 만들어 created_at이 모두 같습니다. 값이 같은 행의 순서는 Postgres가 보장하지 않아 보통 저장된 순서를 따르는데, 행을 수정하면 새 버전이 다른 위치에 저장되어 순서가 바뀌었습니다.id를 두 번째 정렬 기준으로 추가했습니다. 정렬 기준 두 개를 확인하는 테스트를 먼저 넣어 실패를 확인한 뒤 구현했습니다(커밋 119fe84).src/
├── app/
│ ├── (site)/ # 고객 목록·상세·신청 완료·예약 조회
│ ├── admin/ # 운영자 로그인·예약·클래스·회차 관리
│ └── api/ # 공개 예약 API·운영자 API
├── components/ # 고객·운영자 UI와 같은 폴더의 테스트
│ └── ui/ # CLI로 추가한 shadcn/ui
├── lib/ # 순수 규칙·DB 처리·인증·HTTP·시각 표시
└── proxy.ts # 운영자 페이지 접근 보호
supabase/schema.sql # 테이블·함수·버킷·샘플 데이터
public/samples/ # 저장소에 포함한 고정 샘플 이미지
scripts/hooks/ # TDD·위험 명령·종료 전 검증 가드
docs/ # 공개 기획·설계·UI 문서
phases/ # 단계별 작업 상태·산출물 요약
import alias @/*는 ./src/*를 가리킵니다. 브라우저는 자체 REST API를 호출하고 서버 컴포넌트는 src/lib/db.ts를 직접 호출합니다. 상세 구조·데이터 흐름·상태 관리·테스트 책임은 아키텍처를 참고하세요.
| R-52 | 비공개 클래스는 고객 목록·상세·신규 신청에서 제외합니다. 기존 예약 조회·기한 내 취소와 운영자 관리는 유지합니다. |
| R-53 | 클래스는 장소·소요 시간(1 |
| R-54 | 고객 상세에는 시작 전·미취소 회차만 표시합니다. 만석은 마감으로 신청을 막고, 숨겨진 회차의 기존 예약 조회는 유지합니다. 표시할 회차가 없는 클래스는 R-59에 따라 고객에게 보이지 않습니다. |
| R-55 | 공개 목록·상세는 검색 노출을 지원하며 상세 공유 시 이름·소개 요약(소개가 없으면 장소·소요 시간·비용)·대표 이미지를 제공합니다. |
| R-56 | 비공개 클래스는 검색·공유 미리보기에서 제외하고 완료·조회·운영자 화면은 noindex입니다. 신청 폼이 있는 공개 상세는 검색 노출 대상입니다. |
| R-57 | 새 클래스는 비공개로 생성하고 안내 항목·이미지·회차를 확인한 뒤 공개합니다. |
| R-58 | 클래스 이름·안내 항목 수정은 기존 예약 조회에도 반영하며 자동 알림은 보내지 않습니다. 준비물·소개·이미지는 클래스 상세에서 확인합니다. |
| R-59 | 예정 회차(시작 전·미취소, 만석 포함)가 있어야 공개할 수 있습니다. 공개 후 예정 회차가 없어지면 공개 설정은 유지한 채 고객 목록·상세에서 숨기고, 회차를 추가하면 다시 보입니다. 기존 예약 조회·취소는 유지합니다. |