Ecommerce Template
Next.js·React 등으로 만든 최신 전자상거래 템플릿
Barbara J Demers: 동물·자연 소재 표현적 사실주의 회화
Original expressive realism paintings of animals, insects, and the natural world — created with bold color, symbolic detail, and narrative titles by Barbara J Demers.
커머스TypeScript · CSSGitHub @fonemstr온라인운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기Portfolio and shop for Barbara Demers, artist
저장소 README에서 가져옴
README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
Portfolio + shop site for Barbara J Demers, a painter of animal subjects. Built with Next.js 16 (App Router, React 19), Tailwind CSS v4, Stripe Checkout, and Resend for transactional email. Paintings are defined in a typed data file so adding inventory is a quick code edit today; the data model is shaped so this can be swapped for a CMS later without touching the UI.README 전문 보기 ↗Portfolio + shop site for Barbara J Demers, a painter of animal subjects. Built with Next.js 16 (App Router, React 19), Tailwind CSS v4, Stripe Checkout, and Resend for transactional email. Paintings are defined in a typed data file so adding inventory is a quick code edit today; the data model is shaped so this can be swapped for a CMS later without touching the UI.
@tailwindcss/typographynext/font/admin for paintings + journal posts@payloadcms/db-postgres (Neon / Vercel Postgres)content/blog/*.mdx) as a fallback when Payload is not connectedpnpm install
cp .env.local.example .env.local # then fill in keys
pnpm dev
Open http://localhost:3000.
| Key | Required? | Notes |
|---|---|---|
PAYLOAD_SECRET | for admin | Any long random string. openssl rand -base64 32 is fine. |
POSTGRES_URL | for admin | Postgres connection string. Free tier on https://neon.tech. |
BLOB_READ_WRITE_TOKEN | production | Vercel Blob token for uploaded images. Local dev uses filesystem. |
STRIPE_SECRET_KEY | for checkout | Use sk_test_... during development. |
STRIPE_WEBHOOK_SECRET | for sale webhook | Get via stripe listen --forward-to localhost:3000/api/stripe-webhook. The Budderlee Post also needs the subscription, invoice, and customer events enabled on the endpoint (see DEPLOY.md). |
RESEND_API_KEY | for commissions + newsletter | https://resend.com/api-keys |
RESEND_FROM_EMAIL | for commissions + newsletter | Must be a verified sender on Resend. |
RESEND_TO_EMAIL | for commissions + newsletter | Where inquiries land — Barbara's inbox. |
RESEND_SEGMENT_ID | optional | Resend segment that newsletter signups are added to and that newsletters are sent to. Without it, signups land as plain account-level contacts and sends pick a segment by name (see Newsletter). |
META_PAGE_ID, META_IG_USER_ID, META_PAGE_ACCESS_TOKEN | for social posting | Facebook Page + linked Instagram account. Setup steps in SOCIAL.md. Without them, those platforms report a clear failure and everything else works. |
PINTEREST_APP_ID, PINTEREST_APP_SECRET, PINTEREST_REFRESH_TOKEN, PINTEREST_BOARD_ID | for social posting | Pinterest app credentials and the board that receives pins. See SOCIAL.md. |
CRON_SECRET | for scheduled social posts | Any random string; the same value goes in the GitHub repo secret CRON_SECRET so the scheduler workflows can call /api/cron/social-posts and /api/cron/budderlee-post-payments. |
NEXT_PUBLIC_SITE_URL | in production | Used as the origin for Stripe success/cancel URLs. |
Graceful fallbacks:
POSTGRES_URL — gallery reads from the static seed in src/data/paintings.ts and the journal reads from content/blog/*.mdx. The /admin route will error. This lets you run the site with zero database setup.Once PAYLOAD_SECRET and POSTGRES_URL are set, visit http://localhost:3000/admin. The first visit prompts you to create an admin user — use Barbara's email and any password; it's stored hashed in Postgres.
Collections:
The admin also has two custom pages: Analytics (site traffic) and Fulfillment (/admin/fulfillment: subscriber counts, and per issue the generate-list, CSV download, and mark-shipped actions for The Budderlee Post).
/budderlee/postSUBSCRIPTION.mdImage uploads land on the local filesystem in dev (./media/) and on Vercel Blob in production. The frontend picks up both automatically.
There's no auto-seed yet. When Barbara logs in for the first time she can add her paintings directly. The sample paintings in src/data/paintings.ts are a development fallback, not a migration — they render only when Postgres is not connected.
If you'd rather skip Payload and manage inventory in code, edit src/data/paintings.ts. Each entry looks like:
{
slug: "red-fox-in-winter",
title: "Red Fox in Winter",
subject: "Red fox",
year: 2026,
medium: "Oil on canvas",
widthIn: 24,
heightIn: 30,
priceCents: 145_000, // $1,450
sizeTier: "large", // drives shipping rate
featured: true, // appears on the home page
description: "...",
images: ["/paintings/red-fox-in-winter.jpg"],
}
public/paintings/.paintings array.sizeTier maps to flat-rate shipping in SHIPPING_RATES at the top of the same file. Adjust those to match what Barbara actually pays to pack and ship.
Two options:
sold: true on the painting in src/data/paintings.ts (or tick the checkbox in /admin). The gallery card will show a Sold badge and the detail page hides the Buy button./api/stripe-webhook receives checkout.session.completed events and marks the painting sold in Payload, revalidating the affected pages. Print sales never mark the original sold.Each painting can offer archival giclée prints in any number of sizes — set them per painting in /admin under Print options, or in code via the prints array (id, widthIn, heightIn, priceCents). Prints:
PRINT_SHIPPING_RATE in src/data/paintings.ts), with quantity adjustable in Stripe Checkout;Fulfillment is manual by design to start: when the email arrives, order the print from a local fine-art print shop (or print in-studio) and ship it. A print-on-demand API can be wired into the webhook later without changing the storefront.
A named collection of 5×5 in character portraits — animal residents of the fictional village of Budderlee — with its own landing page at /budderlee. To add a resident, create a painting in /admin and set Collection to "The Residents of Budderlee"; two extra fields appear for the character's name (e.g. Maisie) and role in the village (e.g. The Pie Maker). Residents appear on the Budderlee page (sold ones stay listed — prints keep them purchasable) and in the main gallery like any painting. All the usual machinery applies: pricing, print options, sold state, checkout.
Posts go straight to Instagram, Facebook, and Pinterest through the platforms' own free APIs (src/lib/social-direct.ts) — no third-party posting service. The one-time credential setup (Meta app, Pinterest app, scheduler secret) is in SOCIAL.md. Two ways to post:
/admin and save — the site composes the caption (Budderlee arrivals get the "new resident" treatment), attaches the painting's image, and posts to Instagram, Facebook, and Pinterest (Facebook only if the painting has no image). Fires once per tick; the delivery report lands in Social Posts./admin — message, optional image, platform selection, and an optional schedule date. Set status to Send and save: it posts now (status → Posted) or is queued (status → Scheduled) and delivered by /api/cron/social-posts, which a GitHub Actions workflow pings every 15 minutes. Failures show the reason in the result field; fix and set Send again to retry.Notes: Instagram and Pinterest require an image. Very tall or wide images are padded with white for Instagram (it only accepts 4:5 to 1.91:1); Facebook and Pinterest get the original. A scheduled post that is more than 24 hours past its time when the scheduler finds it is marked Failed with a "Missed window" note instead of being sent late. A platform whose credentials are missing never blocks a save — it reports FAILED (…not set) in the delivery report. The Pinterest refresh token lasts about a year; when pins start failing with token refresh failed, redo the authorize steps in SOCIAL.md.
Signup forms live on the home page and /budderlee. Each signup is stored as a contact in Resend (plus a heads-up email to the studio). Addresses collected before this existed (from the old notification-only emails) need a one-time manual add under Contacts in Resend.
Announcements — a new painting, a new Budderlee resident — are written and sent from Newsletters in /admin, no Resend dashboard needed:
email size; pictures uploaded before that size existed are sent as their original file.RESEND_TO_EMAIL) and returns to Draft. The unsubscribe link is a placeholder in tests.The target segment is RESEND_SEGMENT_ID if set; otherwise a Resend segment named like "General" or "Collector", falling back to the first segment. At least one segment must exist in Resend (Audience → Segments). A send also fails if a picture in the body has since been deleted from Media.
The commissions page has a two-step flow: the inquiry form (emails Barbara via Resend, as before), and a Reserve your spot section for clients whose commission Barbara has already accepted. They pick the agreed size tier, describe the subject, and pay a deposit via Stripe Checkout; the webhook emails the studio a "Commission deposit received" note with the payer and subject. Tiers, guide prices, and deposit amounts live in src/data/commissions.ts; the deposit is applied toward the final price and the balance is collected on completion (invoice from the Stripe dashboard, or however Barbara prefers).
Create a file in content/blog/:
---
title: Studio notes, April
date: 2026-04-18
excerpt: A line or two for the index page.
cover: /paintings/some-image.jpg
---
Body written in MDX.
The journal index and individual post routes pick it up automatically on the next build.
# Terminal 1
pnpm dev
# Terminal 2 — forwards Stripe events into the local app
stripe login
stripe listen --forward-to localhost:3000/api/stripe-webhook
Copy the whsec_... the CLI prints into STRIPE_WEBHOOK_SECRET, restart pnpm dev, and go to any available painting's detail page. Use card 4242 4242 4242 4242, any future expiry, any CVC, any ZIP. You'll land back on /checkout/success and the terminal running stripe listen will log the event.
POSTGRES_URL automatically.BLOB_READ_WRITE_TOKEN automatically..env.local.example (PAYLOAD_SECRET, Stripe, Resend, NEXT_PUBLIC_SITE_URL)./admin once live to create Barbara's admin account.https://your-domain.com/api/stripe-webhook in the Stripe dashboard. Copy the signing secret into Vercel's STRIPE_WEBHOOK_SECRET and redeploy.src/
app/
layout.tsx # shell with header/footer
page.tsx # home (hero + featured + newsletter)
gallery/
page.tsx # all paintings grid
[slug]/page.tsx # painting detail + Buy button
commissions/page.tsx # commission inquiry form
about/page.tsx # artist bio
blog/
page.tsx # journal index
[slug]/page.tsx # MDX post renderer
checkout/success/page.tsx
api/
checkout/route.ts # creates Stripe Checkout Session
stripe-webhook/route.ts # receives Stripe events
commissions/route.ts # emails Barbara via Resend
newsletter/route.ts # emails Barbara via Resend
components/ # painting card, forms, buy button
data/paintings.ts # the catalog + shipping tiers
lib/ # stripe, resend, blog, utils
content/blog/ # MDX posts
public/paintings/ # painting images (placeholders today)
public/paintings/