Ecommerce Template
Next.js·React 등으로 만든 최신 전자상거래 템플릿
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기A dynamic website for business pages and profiles.
저장소 README에서 가져옴
README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
A marketplace for verified Kenyan students: free storefronts, safe payments, human-reviewed ID verification.
Phase 1 scope: students only, one campus first. Suppliers, finance suite, chama, external clients etc. are deferred (see below).
You need Node 20+ and a PostgreSQL database. Easiest: create a free project at neon.tech or supabase.com and copy its connection string.README 전문 보기 ↗A marketplace for verified Kenyan students: free storefronts, safe payments, human-reviewed ID verification. Phase 1 scope: students only, one campus first. Suppliers, finance suite, chama, external clients etc. are deferred (see below).
You need Node 20+ and a PostgreSQL database. Easiest: create a free project at neon.tech or supabase.com and copy its connection string.
npm install
cp .env.example .env.local # then fill in DATABASE_URL, NEXTAUTH_SECRET, ADMIN_* (see comments inside)
npx prisma migrate dev --name init
npm run db:seed # schools + your admin account
npm run dev # http://localhost:3000
With the defaults (PAYMENT_PROVIDER=mock, no SMS or Cloudinary keys) everything works offline:
/account/phone..private-uploads/, public images to public/uploads/ (both git-ignored).Try the whole loop: register two students → sign in as admin → /admin/verification-queue approve both → student A opens a storefront, sets up M-Pesa collection (till/paybill/phone) at /seller/storefront, and adds a product → student B orders it via DIRECT_TRANSFER → marks it paid (with a reference code) → seller confirms they received it → confirms → ready → delivered → buyer confirms receipt.
npm test # unit tests: fees, payment rules, order state machine, validation, redirect safety
npm run typecheck
npm run build
| Piece | Where |
|---|---|
| Business rules (fees, windows, limits) — the single source of truth | src/lib/constants/platform.ts |
| Money math + which orders need escrow | src/lib/money.ts |
| Order state machine (who may do what, when) | src/lib/order-state.ts |
| Orders, escrow, payouts, disputes — the only code that moves money state | src/lib/services/orders.ts |
| Payment provider seam (mock + IntaSend) | src/lib/payments/ |
| Auth (email/phone + password, lockout after 5 fails) | src/lib/auth.ts, src/middleware.ts, src/lib/session.ts |
| Private ID photos | src/lib/storage.ts |
| Schema | prisma/schema.prisma |
ID verification is mandatory; phone verification is not. Signup needs your student ID photo + a selfie, nothing else — no SMS/OTP step, so joining never depends on an SMS provider being configured. A human approves the ID in /admin/verification-queue; until then you can browse but not buy, sell or list. Phone verification is separate and optional, any time, at /account/phone — it doesn't gate anything, it's just a "we confirmed you own this number" checkmark (worth doing since it's the number M-Pesa prompts and payouts use).
Three payment modes, decided per order by allowedPaymentModes() in src/lib/money.ts:
PAYMENT_PROVIDER=intasend is actually configured (isEscrowAvailable() in src/lib/payments/index.ts)./seller/storefront), confirms with a reference code and/or screenshot, and the seller confirms receipt before accepting. This is a stand-in for ESCROW until IntaSend is integrated — it's the only way services/bigger orders are payable at all right now, since ESCROW isn't live. No fee, because the platform never touches the money — and no money-safety guarantee either: if either side lies, the only recourse is the admin dispute system (mediation, not a forced refund). The UI says this plainly to buyers; don't let it be mistaken for escrow.Fee comes out of the seller's payout on ESCROW only; buyers never pay extra.
src/lib/payments/intasend.ts). Run the full flow in the IntaSend sandbox (INTASEND_TEST_MODE=true) first and fix any field name that differs. Set the webhook to https://<domain>/api/webhooks/payments with the same challenge as INTASEND_WEBHOOK_CHALLENGE. Until this is done, ESCROW stays unavailable and DIRECT_TRANSFER is the only option for services/bigger orders — see Payment & verification model above.src/lib/sms.ts). Only used for /account/phone verification codes now, not for signup, so it's lower-stakes than before — but try it in their sandbox before relying on it.fees.escrowRate (5%). If they exceed it, raise the rate or the minimum escrow amount — otherwise every escrow order loses money./terms and /privacy are placeholders — get them reviewed (Data Protection Act 2019 covers the ID photos). DIRECT_TRANSFER doesn't involve the platform holding money, but get advice on whether facilitating it (showing payment details, mediating disputes) carries its own obligations.GET /api/cron/maintenance every ~5 min with Authorization: Bearer $CRON_SECRET. It reconciles payments whose webhook never arrived, expires unpaid orders, and auto-completes delivered orders after 72h. (Vercel Hobby only allows daily crons — use cron-job.org.)/admin/payouts for a manual retry after you check the provider dashboard.PAYMENT_PROVIDER=intasend in production — the mock refuses to run there.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME, CLOUDINARY_API_KEY, CLOUDINARY_API_SECRET). src/lib/storage.ts refuses to fall back to the local filesystem in production — Vercel's functions can't write to process.cwd() outside /tmp, so without Cloudinary, every ID-photo and product-image upload fails loudly instead of silently losing data.If you're on Supabase: use the Transaction pooler (port 6543) with ?pgbouncer=true&connection_limit=1 for the app's DATABASE_URL, not the Session pooler (port 5432). Two separate real incidents, both hit while building this app, both reproduced and confirmed fixed:
prisma db push/migrate) start failing with EMAXCONNSESSION. Confirmed via pg_stat_activity that the session pool was full of idle connections matching this app's own query patterns. Keep using the session pooler (or a direct connection) only for one-off schema commands from your own machine, run one at a time.?pgbouncer=true corrupts queries under concurrent load, surfacing as PostgresError 42P05: prepared statement "s0" already exists — on whatever query happens to lose the race, which in practice meant login (authorize()'s prisma.user.findFirst), the posts feed, and server-rendered pages all failing intermittently at once. Reproduced directly: 12 concurrent fresh PrismaClients (simulating separate serverless cold starts) against the bare :6543 URL failed 11/12 times with that exact error; the identical test against the URL with ?pgbouncer=true&connection_limit=1 passed 20/20. pgbouncer=true stops Prisma from using named prepared statements, which is what collides when PgBouncer's transaction mode hands different logical connections the same backend. connection_limit=1 stops each function instance from also holding multiple connections against a pooler that's already multiplexing./api/orders and uploads).services/orders.ts) against a disposable test DB — it's been exercised manually against the live DB (see commit history), but there's no CI-run suite for it yet./<slug> on the main domain; per-business subdomains (slug.domain) would need wildcard DNS + middleware.verificationExpiresAt is stored) but nothing yet forces re-verification.Full designs are kept in docs/ (schema.full-vision.prisma, api-map.full-vision.ts) and the unfinished pages in src/_deferred/ (outside routing, excluded from typecheck).