尚未检测到更新。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.