尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證正在載入完整 README。 以下為已儲存的摘錄。
Portfolio site plus a small studio back office: client proofing galleries with
access codes and per-photo notes, final delivery with downloads, order tracking,
and Stripe payments. Runs entirely on free tiers.檢視完整 README ↗Portfolio site plus a small studio back office: client proofing galleries with access codes and per-photo notes, final delivery with downloads, order tracking, and Stripe payments. Runs entirely on free tiers.
| Area | Route | Notes |
|---|---|---|
| Public site | /, /portfolio, /pricing, /about, /contact | Portfolio grid with lightbox and category filter, packages from the database, contact form → inquiries |
| Client gallery | /g/[slug] | Access-code gate (30-day signed cookie). Proof galleries: favorites + notes per photo. Final galleries: per-photo and zip-all downloads. Photos stream through /api/photo/[id] after an access check |
| Payments | /pay/[orderId] | Client signs the agreement, pays the deposit (or in full), later the balance. Card form is embedded (Stripe Elements). Amounts always come from the database; /api/stripe/webhook and /pay/success both record payments (idempotent). Final galleries unlock downloads once the balance is paid. |
| Studio (admin) | /admin | Dashboard, inquiries → clients, orders with payment links, galleries (upload, reorder, codes, publish, reply to notes), portfolio manager, packages, Lightroom tokens |
| Lightroom API | /api/lr/* | Bearer-token API used by the Lightroom Classic publish plugin in lightroom/ |
| Auth | /login, /auth/signout | Single admin from ADMIN_EMAIL / ADMIN_PASSWORD_HASH |
Schema: db/schema.sql. Starter packages: db/seed.sql.
npm install
cp .env.example .env.local
DATABASE_URL in .env.local.npm run db:seed # runs db/schema.sql then db/seed.sql
(Or paste both files into Neon’s SQL Editor.)galleries, access Private → it adds BLOB_READ_WRITE_TOKEN to the projectportfolio, access Public → in Advanced Options set the env var prefix to PORTFOLIO so it adds PORTFOLIO_READ_WRITE_TOKEN (the code also accepts PORTFOLIO_BLOB_READ_WRITE_TOKEN)DATABASE_URL, NEXT_PUBLIC_SITE_URL, the admin and session values from step 3, and the Stripe keys from step 4.meilechbiller.com and www.meilechbiller.com and create the DNS records Vercel shows you.To run locally with the same stores: vercel env pull writes the tokens into .env.local.
npm run hash-password -- "a long strong password" # prints ADMIN_PASSWORD_HASH=...
openssl rand -base64 32 # SESSION_SECRET
Set ADMIN_EMAIL, ADMIN_PASSWORD_HASH and SESSION_SECRET locally and in Vercel. Sign in at /login.
STRIPE_SECRET_KEY and NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY (test keys until launch).https://meilechbiller.com/api/stripe/webhook with events
checkout.session.completed, checkout.session.async_payment_succeeded, checkout.session.async_payment_failed.
Copy the signing secret to STRIPE_WEBHOOK_SECRET.stripe listen --forward-to localhost:3000/api/stripe/webhook, test card 4242 4242 4242 4242.NEXT_PUBLIC_GA_MEASUREMENT_ID. The tag loads on the public site and client galleries only, never on /admin or /login.GA_API_SECRET. The server sends the conversion events with it: generate_lead when a contact inquiry is saved, purchase when a Stripe payment is recorded (transaction id = Checkout Session id), gallery_unlock when a client opens a gallery. The pricing page also sends book_cta_click from the browser.generate_lead and purchase as key events (Admin → Events).lightroom/meilechbiller.lrplugin is a Lightroom Classic publish service. One published collection = one client gallery.
/admin/integrations), create an API token, and download the plugin zip (or use the folder in this repo).meilechbiller.lrplugin.What happens: the plugin asks the site for a presigned upload URL per photo, PUTs the full-size JPEG straight to the private Blob store, then the site builds the 1600px preview and records the photo (/api/lr/*, bearer-token auth, hashes only in api_tokens). Re-publishing an edited photo replaces the files and keeps client notes. Client notes and favorites appear in Lightroom's Comments panel; favorites are tagged with the keyword “Client Favorite”. Removing a photo from the collection deletes it; deleting the collection archives the gallery.
After editing plugin files run node scripts/plugin-zip.mjs to refresh public/downloads/meilechbiller-lightroom.zip.
Everything about a person lives on their page under /admin/clients. Stages (New lead → Awaiting payment → Booked → Proofs out → Delivered) are worked out from the data; nothing is set by hand except Archive.
Portfolio images for the public site are managed at /admin/portfolio (featured images feed the home page hero and “Recent sessions”).
/api/photo/[id], which checks the gallery cookie (or the admin session) right before streaming the blob, and lets the browser cache with ETag revalidation.next/image handles thumbnails.mailto: links. Every attempt is listed in Admin → Emails. Resend or Postmark would automate “your proofs are ready” emails.src/lib/types.ts are hand-written to match db/schema.sql.$VAR inside .env* files, so secrets containing $ must be escaped as \$ locally. The generated password hash uses : separators to avoid this.npm run dev # start locally
npm run lint # eslint
npm run typecheck # tsc --noEmit
npm run build # production build
npm run db:migrate # apply db/schema.sql to DATABASE_URL (also runs automatically at the start of `npm run build`, so every Vercel deploy keeps the database schema current)
npm run db:seed # schema + starter packages
npm run hash-password -- "password"
Emails are optional. Without them the site still saves inquiries and shows mailto links in the admin.
RESEND_API_KEY in Vercel.EMAIL_FROM to an address on the verified domain, for example Meilech Biller <[email protected]>.INQUIRY_NOTIFY_EMAIL if new-inquiry alerts should go somewhere other than ADMIN_EMAIL.What gets sent: a notification to you and an automatic confirmation to the sender for every inquiry, the gallery link and access code when you press the button on a gallery, and a receipt when a client pays.
Service-area pages live at /headshots/<town> and are generated from src/lib/areas.ts. Business details for structured data (address, phone, social profiles) come from src/lib/site.ts; fill in the street address and phone when they are final. After the real domain is live: verify the site in Google Search Console and Bing Webmaster Tools, submit /sitemap.xml, and create a Google Business Profile with the same name, address and phone.