PIXEL MART
게임을 좋아하는 사람을 위한 픽셀아트 데스크 소품 상점
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
A production-shaped luxury e-commerce platform: a server-rendered storefront, a working
cart and Stripe checkout, and (from Phase 4) a protected admin CMS, sharing one Supabase
Postgres database.
Phases 1 and 2 are complete and building — storefront, cart, coupons, checkout,
Stripe settlement and transactional email. Read docs/status.md for exactly what is
live and what is deliberately inert; nothing in this repo pretends to work.
At supabase.com. Then, in the SQL Editor, paste
supabase/apply-all.sql and run it. That is all nine migrations plus the seed, in order,
inside one transaction.README 전문 보기 ↗A production-shaped luxury e-commerce platform: a server-rendered storefront, a working cart and Stripe checkout, and (from Phase 4) a protected admin CMS, sharing one Supabase Postgres database.
Phases 1 and 2 are complete and building — storefront, cart, coupons, checkout,
Stripe settlement and transactional email. Read docs/status.md for exactly what is
live and what is deliberately inert; nothing in this repo pretends to work.
| File | What it is |
|---|---|
PRD.md | Requirements, scope, phase plan, non-functional targets |
Goals.md | The measurable definition of done |
Design.md | The liquid-glass design system — normative, read before touching visuals |
Agents.md | The working agreement: directory contract, naming, data-access rules |
Memory.md | Decision log. Read first, update last |
docs/status.md | Live vs. stubbed, and what you must do before anything talks to a database |
npm install
At supabase.com. Then, in the SQL Editor, paste
supabase/apply-all.sql and run it. That is all nine migrations plus the seed, in order,
inside one transaction.
supabase/apply-all.sql <- paste this
It is a generated file: npm run db:bundle rebuilds it from supabase/migrations/*.sql
and supabase/seed.sql, and npm run test:db proves it applies cleanly to an empty
PostgreSQL 16 and produces the expected counts and functions. The seed gives you 14
products, 97 variants, 6 categories, 20 reviews, 4 journal articles, 3 shipping methods,
4 coupons and the full CMS content set.
If you prefer to run the files individually, they are in supabase/migrations/, numbered,
and must go in numeric order with supabase/seed.sql last.
cp .env.example .env.local
Fill in NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_ANON_KEY and
SUPABASE_SERVICE_ROLE_KEY from Project Settings → API.
For payment, add STRIPE_SECRET_KEY and STRIPE_WEBHOOK_SECRET, and forward events
while developing:
stripe listen --forward-to localhost:3000/api/webhooks/stripe
Without Stripe keys the checkout form still renders, but the pay button is disabled and says why. It never simulates a payment.
The service-role key bypasses Row Level Security. It must never be prefixed
NEXT_PUBLIC_, andlib/supabase/admin.tsimportsserver-onlyso that importing it from a Client Component is a build error rather than a leak.
npm run dev
Register through the app, then in the SQL Editor:
update profiles set role = 'SUPER_ADMIN' where email = '[email protected]';
Every read in lib/queries falls back to a curated static catalogue when Supabase is
unconfigured (Memory.md D-011), so npm run dev gives you the full storefront
immediately. Writes never fall back — they fail loudly.
The build succeeds with or without Supabase configured — verified both ways. Without it the storefront serves the built-in demo catalogue and the bag says so, which means a missing variable is a quiet degradation rather than a failed deploy. Set these in Project Settings -> Environment Variables:
| Variable | Needed for | If missing |
|---|---|---|
NEXT_PUBLIC_SUPABASE_URL | everything | demo catalogue, no bag |
NEXT_PUBLIC_SUPABASE_ANON_KEY | everything | demo catalogue, no bag |
SUPABASE_SERVICE_ROLE_KEY | cart, checkout, webhooks | bag reports it cannot connect |
NEXT_PUBLIC_SITE_URL | canonicals, OG, sitemap, Stripe return | falls back to the Vercel domain |
STRIPE_SECRET_KEY | taking payment | pay button disabled, says why |
STRIPE_WEBHOOK_SECRET | marking orders paid | webhook returns 503 |
RESEND_API_KEY | transactional email | sends are logged as SKIPPED |
ADMIN_NOTIFICATION_EMAIL | staff notices | staff email skipped |
SUPABASE_SERVICE_ROLE_KEY bypasses Row Level Security. It must not be prefixed
NEXT_PUBLIC_, and lib/supabase/admin.ts imports server-only so that importing it
into a Client Component fails the build rather than leaking it.
Point the Stripe webhook at https://your-domain/api/webhooks/stripe for
checkout.session.completed, checkout.session.expired,
checkout.session.async_payment_succeeded, checkout.session.async_payment_failed and
charge.refunded.
Before launch, replace the demo contact details in site_settings — the footer
currently shows [email protected], seeded from site.email.
vite buildBoth symptoms had one cause: this repository began life as a Vite SPA (commit
e88a5e3, now archived under legacy/), and the Vercel project still had
Root Directory: legacy saved against it.
That single setting produced both failures. Vercel built inside legacy/, whose
package.json really does say "build": "vite build" — hence vite: command not found.
Once vercel.json forced next build, Vercel ran it inside legacy/, which
.vercelignore empties. Next.js found no app/ directory, did not error, emitted a
single synthetic Pages-Router /404, and reported a successful build. Every route then
404'd while /_next/static/** still served 200.
Read the setting rather than guessing at it:
vercel link --yes --project <project>
vercel pull --yes --environment production
node -e "console.log(require('./.vercel/project.json').settings)"
and fix it with:
vercel project update <project> --auto-detect root-directory
--auto-detect also accepts build-command, install-command, output-directory and
dev-command. Never set an Output Directory on a Next.js project — it switches
Vercel to serving a static directory and breaks every server route, which is why
vercel.json pins framework and buildCommand but deliberately omits
outputDirectory.
vercel inspect --logs <deployment-url> prints the whole build log, and the route table
at the end is the fastest check that the build is real. A healthy build of this repo
prints a Route (app) table of 42 entries. A build that prints only
Route (pages)
─ ○ /404
did not find the application at all.
The log line Removed N ignored files defined in .vercelignore is also worth reading:
Vercel truncates the list to 10 entries, so the count is the only complete signal.
For this repo it must be 59. It was once 64, and the extra five were
lib/supabase/*.ts — see below.
.vercelignore patterns must start with /.vercelignore is matched with .gitignore semantics, so an unanchored supabase/
excludes a directory of that name at any depth. It was meant to drop the root SQL
directory; it also dropped lib/supabase/, and the build failed with
Module not found: Can't resolve '@/lib/supabase/server'.
Check any change to that file against git's own matcher, which uses the same rules:
git ls-files --cached --ignored --exclude-from=.vercelignore | wc -l # must be 59
| Command | What it does |
|---|---|
npm run dev | Development server |
npm run build | Production build |
npm run typecheck | tsc --noEmit, strict |
npm run lint | ESLint, next/core-web-vitals + next/typescript |
npm run test:db | 59 checkout assertions plus a proof that apply-all.sql applies |
npm run test:http | 40 routes, expected status codes. npm run test:http -- https://your.site checks a deployment |
npm run db:bundle | Rebuild supabase/apply-all.sql from the migrations and seed |
npm run images | Regenerate the SVG image set |
npm run db:types | Regenerate types/database.ts from Supabase |
Run npm run db:types after every migration. types/database.ts is generated;
the hand-written version in the repo is a Phase-1 stand-in.
app/(storefront)/ public routes — landing, catalogue, product, journal, CMS pages
app/sitemap.ts generated from live data
components/ui/ primitives only — no data fetching, no business logic
components/storefront/ composed sections
components/seo/ JSON-LD emitters
lib/queries/ every read path
lib/actions/ every write path — Server Actions, Zod-validated
lib/cart/ cart ownership: the cookie, and the one service-role exception
lib/stripe/ server-only Stripe client
lib/email/ transactional templates and the single send path
lib/validations/ Zod schemas shared by client forms and server guards
lib/supabase/ server / browser / admin clients + middleware
lib/fallback-catalog.ts generated from seed.sql — do not hand-edit
supabase/migrations/ forward-only SQL
supabase/tests/ PGlite harness — migrations + end-to-end commerce assertions
scripts/ image generation
legacy/ the original Vite prototype, kept for reference
Agents.md §3 is the enforceable version of this. Business logic never lives in a
component.
In Postgres. price_cart() computes lines, subtotal, discount, shipping, tax and total;
the cart page, the checkout summary and the order are all rendered from its output, and
create_order_from_cart() re-prices from scratch under a row lock before writing. The
browser sends variant ids and quantities — never a price. Before redirecting to Stripe,
the session total is compared with the order total; a mismatch charges nothing.
Stock is reserved at checkout and only decremented once a signature-verified webhook
confirms payment, so an abandoned checkout returns the piece to sale and a replayed
webhook is a no-op. Every movement writes an inventory_transactions row.
Three themes — Light, Dark, System — with dark as the default. The choice
lives in localStorage and is applied by a synchronous inline script in <head> before
first paint, so there is no flash. System attaches a live matchMedia listener, so
changing your OS appearance updates an open tab.
Everything keys off data-theme on <html>. Components never reference a raw colour:
they use semantic tokens (canvas, ink-*, accent-*, hairline-*, tint-*) defined
once in app/globals.css. Adding a third theme would be a block of token overrides, not
a refactor. Design.md §2 and §10 are normative.
Type is Jost (display) and Inter Tight (body), self-hosted at build time by
next/font — no request to Google on first paint.
There was no image-generation model available, and stock photography would not be exclusive to the brand — so every image is procedurally generated SVG in the house palette. They are deterministic, resolution-independent, and total ~620KB for the set.
Products are drawn as garments, not as fabric swatches. scripts/garments.ts holds
thirteen silhouettes — coat, topcoat, blazer, biker, crew, rollneck, shirt, overshirt,
trouser, scarf, wrap, belt, bag — with the construction that makes each one recognisable
at thumbnail size: notch lapels, plackets, ribbed hems, pressed creases, zips, buckles,
handles. scripts/generate-images.ts supplies the material, the raking light and the
obsidian ground. The second plate for each product is the same garment moved closer, so
the weave and stitch gauge scale the way a macro lens would.
This is illustration and does not pretend to be photography — see Memory.md D-026 for what rendering it taught, including why a lapel drawn as one outline reads as a leaf.
To swap in real photography, see supabase/images.sql. Images live in the
product_images table, so it is a data change, not a code change.
Because media lives in the product_images table, replacing them with real photography
later is a data change, not a code change: upload to Supabase Storage and update the
url column. scripts/generate-images.ts is where the pipeline lives if you want to
point it at an AI image model instead.
This folder previously held a Vite + React SPA, and then a partial Next.js scaffold
configured for Tailwind v3 + shadcn CLI. This build uses Tailwind v4, which is
configured in CSS (@theme at the top of app/globals.css) rather than in a config file
— see Memory.md D-002.
Two leftovers from that scaffold are now stubs and safe to delete:
tailwind.config.ts — replaced with a comment explaining that v4 does not read it.eslintrc.json — superseded by eslint.config.mjs (flat config)legacy/ holds the original Vite prototype. Delete it whenever you no longer want the
reference.