尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證eBazar — a multi-vendor marketplace (Next.js, Neon Postgres, Drizzle, Auth.js, Stripe). 8x take-home.
翻譯準備中
取自儲存庫 README
正在載入完整 README。 以下為已儲存的摘錄。
A multi-vendor marketplace: independent sellers apply to open a store, an admin approves them, and shoppers buy from many stores in one checkout. Each store then fulfils its own part of the order.
Built for the 8x take-home. The original brief was an Amazon clone; it was later changed to "keep the idea and the backend, design your own interface". The backend from that first version (Neon Postgres, Drizzle, Auth.js, Stripe) was carried over and extended for multiple vendors. The UI was rebuilt from scratch with its own visual identity.檢視完整 README ↗A multi-vendor marketplace: independent sellers apply to open a store, an admin approves them, and shoppers buy from many stores in one checkout. Each store then fulfils its own part of the order.
Built for the 8x take-home. The brief was later revised to "keep the idea and the backend, design your own interface". The backend from the first version (Neon Postgres, Drizzle, Auth.js, Stripe) was carried over and extended for multiple vendors. The UI was rebuilt from scratch with its own visual identity.
Stack: Next.js 16 (App Router, Server Components, Server Actions) · TypeScript · Tailwind CSS v4 · Neon serverless Postgres · Drizzle ORM · Auth.js v5 (credentials, JWT) · Stripe Checkout (test mode) · Cloudinary (optional image uploads)
| Role | Can do |
|---|---|
| Customer | Browse, search, buy from several stores in one checkout, track each package, review delivered products |
| Seller | Everything a customer can, plus Seller Center: product CRUD with images, stock, publish/draft, fulfil orders (paid → packed → shipped → delivered), edit the store profile |
| Admin | Approve/reject store applications, suspend/reinstate stores, manage the category tree, block listings, hide reviews, see all orders and users |
Signup always creates a customer. Becoming a seller means applying at /sell; the account only gets the seller role once an admin approves the store. Admins are only created by the seed script.
After npm run db:seed, every account uses the password Password123.
| What you'll see | |
|---|---|
[email protected] | Admin console — one pending application to approve |
[email protected] | Seller Center for Voltline Electronics, with orders to fulfil |
[email protected] | Customer with order history, packages in transit, and a delivered item they can still review |
[email protected] | A store application awaiting review |
[email protected] | A rejected application that can be edited and re-submitted |
[email protected] | A suspended seller — store page and listings are hidden |
Stripe test card: 4242 4242 4242 4242, any future expiry, any CVC.
Data model (src/db/schema.ts)
users.role is customer | seller | admin. Each user owns at most one store (stores.owner_id is unique).stores.status is pending → active | rejected, and active ⇄ suspended.categories is a two-level tree. Products always sit in a leaf category.products belong to a store and have draft | active | blocked status (blocked is set by admins only). product_images holds an ordered gallery.orders row is what the buyer pays for (one Stripe session). Each store involved gets a sub_orders row with its own fulfilment status. order_items hang off the sub-order and snapshot the title, image and price.reviews are unique per (product, user). products.rating_avg and review_count are denormalised and recomputed when a review is written or hidden.Authorization
src/proxy.ts only checks that a session exists on protected paths.src/lib/dal.ts does the real checks. The JWT carries just the user id; role and store status are read from the database on each request, so an approval or suspension takes effect immediately. requireSeller() requires role seller and an active store.store_id, which comes from the session and never from the request. Calling a server action directly with another store's product id changes nothing.active and its store is active (isVisible in src/lib/catalog.ts). Suspending a store hides every listing at once.Checkout (src/app/api/checkout/route.ts)
db.batch(), which is atomic on the neon-http driver.markOrderPaid runs from both the Stripe webhook and the confirmation page, whichever arrives first. A conditional status = 'pending' update makes it idempotent, so stock and sold counts are only changed once.SELLER_TRANSITIONS). The update compares and sets the current status, so two tabs can't race each other.Reviews: only a buyer with a delivered sub-order containing the product can review it. Every review on the site is therefore a verified purchase.
Images: nothing is stored on our server. Sellers either upload to Cloudinary or paste any public https image URL.
/api/uploads/sign checks the session, fixes the folder and allowed formats in a SHA-1 signature, and the browser then uploads straight to Cloudinary.remotePatterns and would turn the deployment into an open image proxy. Cloudinary images are resized by Cloudinary through URL transforms instead (src/lib/images.ts).cp .env.example .env.local # fill in DATABASE_URL, AUTH_SECRET, Stripe test keys
npm install
npm run db:push # create tables
npm run db:seed # demo data (wipes existing data)
npm run dev
For Stripe webhooks locally: stripe listen --forward-to localhost:3000/api/webhooks/stripe, then put the printed whsec_… in STRIPE_WEBHOOK_SECRET. Without the webhook, the confirmation page still confirms payment directly with Stripe.
src/
app/(shop)/ storefront: home, search, category, deals, product, store, cart, checkout, account, sell
app/seller/ Seller Center (role-gated layout)
app/admin/ Admin console (role-gated layout)
app/api/ auth, checkout, Stripe webhook, Cloudinary signing
lib/ dal (auth checks), catalog / orders / reviews / admin / seller queries, server actions
components/ UI building blocks
db/ schema + seed
.agent-logs/ verbatim prompt/response logs of the AI-assisted build (captured by .claude/hooks)
This project was built with Claude Code. .claude/hooks/ records every prompt and response pair into .agent-logs/, so the full working session is reviewable, including design decisions, course corrections, and points where I pushed back on or changed the agent's suggestions.
eBazar is a demo built for an assignment. It isn't affiliated with any real marketplace. Product photos are from Unsplash.