尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證製作者的介紹僅有上面的一句話。 更多詳情請閱讀儲存庫 README。 儲存庫 README ↗
正在載入完整 README。
檢視完整 README ↗Daily Lunch is Teko's internal web app for organizing the office's daily lunch order. Every day, an admin publishes the menu (parsed from a pasted text list via Gemini AI), teammates pick their food and price tier before the cutoff time, and admins can look up, edit, and get monthly spending stats per person — with an automatic announcement posted to Google Chat / Slack once the menu is confirmed.
/admin/parse; Google Gemini extracts and cleans it into a structured food list, which is then saved as "today's menu"./orders lets anyone browse past orders grouped by day or by person. A "today's orders" modal shows/edits/removes orders in real time and can copy the day's order list to the clipboard./manage aggregates each person's order count and total spend for a selected month.next-themes) with a toggle switch in the navbar, respecting the system preference by default.class-variance-authority), next-themes for dark mode@google/generative-ai) for parsing free-text menus into structured food listssonner (toasts), framer-motion/motion (animations), date-fnsDefined in prisma/schema.prisma:
id, name, shortName, email, avatarUrl).Food to a calendar date, i.e. "this dish is on today's menu".price tier and optional note.Order.User by email, falling back to the email's local part vs shortName, since User.email is null for people created from the order form.src/
app/
page.tsx # Home: ordering form or Tết landing hero
orders/ # Order lookup/history page
manage/ # Monthly stats for admins
admin/parse/ # AI menu parsing + save + announce
account/ # User profile page
login/ # Email + Google login
api/
foods/today/ # Today's configured menu
orders/ # Create/list/filter/manage orders
admin/ # Parse-food (Gemini), save foods, announce
chat/bot/ # Google Chat app endpoint (subscribe/unsubscribe)
users/ # User profile CRUD
components/ # NavBar, OrderSelection, modals, ThemeToggle, ui/...
contexts/UserContext.tsx # Current Supabase user + profile metadata
store/useCartStore.ts # Zustand store for the order-in-progress
lib/
prisma.ts # Prisma client singleton
googleChat.ts # Chat API client (service account) + menu card
googleChatEvents.ts # Verifies + normalizes inbound Chat events
chatNotifications.ts # Per-user DMs for app events (order confirmation)
supabase/ # Client/server/middleware Supabase helpers
prisma/ # Prisma schema + migrations
npm install
Create a .env (database) and .env.local (app secrets) with:
| Variable | Purpose |
|---|---|
DATABASE_URL | PostgreSQL connection string used by Prisma |
NEXT_PUBLIC_SUPABASE_URL | Supabase project URL |
NEXT_PUBLIC_SUPABASE_ANON_KEY | Supabase anon/public key |
NEXT_PUBLIC_SITE_URL / SITE_URL | Public base URL, used for OAuth redirects and announcement links |
GOOGLE_CLIENT_SECRET | Google OAuth client secret (configured in Supabase auth provider) |
GEMINI_API_URL / GEMINI_API_KEY | Google Gemini endpoint + key for menu parsing |
GOOGLE_CHAT_WEBHOOK | Incoming webhook URL for the Google Chat space announcement |
GOOGLE_CHAT_SERVICE_ACCOUNT | Service account JSON for the Chat bot (raw JSON or base64), enables per-user DMs |
GOOGLE_CHAT_PROJECT_NUMBER | Cloud project number. Required if the app is a Chat add-on (it identifies the add-ons service agent that signs requests), and also serves as the audience if Authentication Audience is Project Number |
GOOGLE_CHAT_SERVICE_AGENT_EMAIL | (optional) Explicit sender address to trust, instead of deriving it from the project number |
GOOGLE_CHAT_ENDPOINT_URL | Set if the Authentication Audience is HTTP endpoint URL (recommended); must match the configured URL exactly |
SLACK_WORKFLOW_WEBHOOK | (optional) Slack workflow webhook for announcements |
Each announcement channel is independent: set only GOOGLE_CHAT_WEBHOOK and you get the space post, set only the two GOOGLE_CHAT_* bot vars and you get DMs, set all three and you get both.
Incoming webhooks can only post to a space — DMing a person requires a real Chat app. One-time setup:
GOOGLE_CHAT_SERVICE_ACCOUNT (raw JSON, or base64 it to survive Vercel's
env-var UI: base64 -w0 key.json). No domain-wide delegation is needed —
the bot only ever posts as itself.@mention it.https://<your-domain>/api/chat/bot.GOOGLE_CHAT_ENDPOINT_URL to that exact same URL, or
Project Number → set GOOGLE_CHAT_PROJECT_NUMBER instead.GOOGLE_CHAT_PROJECT_NUMBER to your Cloud project number. Google signs
requests as one of two senders depending on how the app is built:
[email protected] for a classic Chat app, or
service-<PROJECT_NUMBER>@gcp-sa-gsuiteaddons.iam.gserviceaccount.com when
the app is a Chat add-on. The second is project-specific, so it has to be
pinned to your project — trusting the whole gcp-sa-gsuiteaddons domain
would let any Google Cloud project post to this endpoint. (If Google ever
sends a different address, GOOGLE_CHAT_SERVICE_AGENT_EMAIL overrides the
derived one; the rejection log names the address it saw.)GET /api/chat/bot reports how the running deployment is configured — audience
mode, expected audience, and accepted senders — which is the fastest way to
check that Vercel actually picked up the variables (it does not apply new
environment variables to an existing deployment; you must redeploy).
Then anyone can find the bot in Google Chat, start a DM, and they're subscribed automatically. In a DM the bot understands:
| Command | Effect |
|---|---|
subscribe / dk / đăng ký | Start receiving the daily menu DM |
unsubscribe / huỷ | Stop receiving it |
status | Check whether you're subscribed |
help | Show the command list |
Removing the app unsubscribes you. If a DM later fails because the app was removed, that subscriber is deactivated automatically on the next announcement.
npm run prisma:generate # generate the Prisma client
npm run prisma:push # push the schema to your database
npm run dev
Open http://localhost:3000 to see the app.
| Command | Description |
|---|---|
npm run dev | Start the dev server (Turbopack) |
npm run build | Production build |
npm run start | Run the production build |
npm run lint | Lint the project |
npm run prisma:generate | Regenerate the Prisma client |
npm run prisma:push | Push the Prisma schema to the database |
npm run prisma:studio | Open Prisma Studio |
Deployed on Vercel. See the Next.js deployment docs for details.