尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证trip27 - flight booking site (Next.js + Duffel)
翻译准备中
取自仓库 README
正在加载完整 README。
查看完整 README ↗A flights-only booking site (search → results & filters → fare options → travellers → seats → baggage → payment → confirmation), built with Next.js 16, React 19, Tailwind CSS 4 and the Duffel flights API. English, prices in AED.
npm install
cp .env.example .env.local # then edit
npm run dev # http://localhost:3000
With no DUFFEL_ACCESS_TOKEN, the site runs in demo mode on built-in sample flights (about 80 airports), so you can click through the full flow straight away.
duffel_test_…)..env.local as DUFFEL_ACCESS_TOKEN=duffel_test_… and restart.Before going live:
CKO_* variables set, customers pay by card through Checkout.com Flow (card data never touches this server). The flow is: /api/payments/session prices the booking on the server and opens a payment session (authorise only) → the customer pays in Flow → /api/payments/complete checks the authorised amount, books with the airline, then captures. If the airline booking fails the payment is voided, so the customer is never charged for a failed booking. Airlines are paid from your Duffel balance. Without CKO_* the checkout shows a demo card form that charges nothing.duffel_live_… token, bookings are blocked until you set ALLOW_LIVE_BOOKINGS=true.FX_RATES_TO_AED, and the supplier is always paid the exact original amount. Use a live FX feed in production, or ask Duffel to bill in AED.SERVICE_FEE_AED adds a per-booking fee to the displayed total, which you collect through your gateway.RESEND_API_KEY and EMAIL_FROM are set (see lib/email.ts). To email any customer, verify your own domain in Resend. A failed email never fails the booking./login with their email: they get an 8-character code (and a one-click link) by email, with no password and no database. A signed, httpOnly session cookie lasts 30 days. My bookings (/account/bookings) lists every order whose contact email matches, upcoming first. New orders store the email in Duffel order metadata (contact_email). Older orders are matched by passenger email. Set AUTH_SECRET in production, and verify your sending domain in Resend so codes reach every customer. Without email configured, the demo site shows the code on screen./booking/[orderId] shows any order by ID. Add a check against email or last name before exposing it publicly.Jinko fares can appear next to Duffel's. Add JINKO_API_KEY_SANDBOX and/or JINKO_API_KEY_PROD (keys from dashboard.gojinko.com), then choose which one runs on the admin page (below). Search runs every enabled supplier in parallel and merges the results; if one fails or times out, the others' fares still show.
How Jinko fares differ:
JINKO_CURRENCY (USD by default). The AED price is an estimate, and SERVICE_FEE_AED doesn't apply, because trip27 never handles that money. Your earnings depend on Jinko commissions, which you arrange with Jinko./booking/jnkb_…, which checks the trip every 5 seconds: awaiting payment → issuing ticket → confirmed (with the airline PNR) or not completed. Jinko emails the e-ticket confirmation.jnko_…, jnkb_<trip>~…), including which Jinko environment it came from. Set AUTH_SECRET in production: changing it invalidates those links.ALLOW_LIVE_BOOKINGS=true.In the sandbox, Jinko's test airline is Jinko Test Air (ZZ). Some ZZ flights simulate price changes, sell-outs and ticketing failures (see docs.gojinko.com/guides/testing-in-sandbox).
The back office at /admin (and at the root of admin., e.g. admin.trip27.me) switches suppliers on and off at runtime: Duffel on/off, and Jinko Off / Sandbox / Production (one Jinko environment at a time, so test and live fares never mix). Changes apply within seconds, with no redeploy. Keys stay in environment variables; the page only switches suppliers.
Setup, once:
GLOBAL_CONFIG.VERCEL_API_TOKEN (plus VERCEL_TEAM_ID if the store belongs to a team).ADMIN_EMAILS (comma-separated). Admins sign in with the normal email code.admin.trip27.me under the project's Domains and create the DNS record Vercel shows.Without Global Config the page is read-only and the site runs with defaults: Duffel and Jinko Sandbox on if their keys are set, Jinko Production off. A supplier without a key can't be switched on. Bookings already in progress keep using the supplier and Jinko environment they started with, even if an admin switches suppliers meanwhile.
A chat button on every page (except checkout, payment, sign-in and admin) lets customers plan a trip in plain words: "cheapest weekend in Tbilisi next month", "non-stop to London, back a week later, 2 adults". The model searches with trip27's own code and shows the best itineraries as cards; the customer books on the usual checkout.
lib/chat/llm.ts). Add GEMINI_API_KEY (Google AI Studio, has a free tier) and/or ANTHROPIC_API_KEY, and pick one with CHAT_PROVIDER=gemini|anthropic (empty = Gemini if its key is set). CHAT_MODEL overrides the model (defaults gemini-3.8-flash / claude-sonnet-5-5). Without a key the chat button doesn't appear. Note: on Gemini's free tier Google may use prompts to improve its products; switch to a paid tier before real customer traffic.lib/chat/tools.ts) call searchAll, getOffer and the grouping/filter code the results page uses, so the admin supplier switches, AED pricing and SERVICE_FEE_AED all apply, and Duffel and Jinko fares show side by side.find_airports, search_flights, get_fare_details (re-prices a fare and reads its rules) and start_checkout, which only shows a button to /flights/checkout/[offerId]. Payment stays in Checkout.com Flow (or Jinko's page for Jinko fares). The assistant is told never to ask for card or passport details.app/api/chat/route.ts) re-validates it and streams the reply as NDJSON. Fares are referred to by short refs (F1, F2…) so long supplier IDs stay out of the model's context./api/chat allows 120 s.Vercel Web Analytics (@vercel/analytics) counts visitors and page views; see the project's Analytics tab in Vercel. It's added in components/SiteAnalytics.tsx, which keeps the back office out of the stats, replaces booking and fare IDs in URLs with placeholders, and drops query strings except UTM tags. Analytics must be enabled for the project in Vercel (Analytics tab → Enable).
Push to GitHub and import into Vercel (or run npm run build && npm start on any Node 20+ host). Add the environment variables in the host's settings. Search can take 20–25 s on some routes, so allow at least 60 s for API functions (maxDuration is already set).
app/
page.tsx Home + search widget
flights/page.tsx Results (filters, sorting, fare families)
flights/checkout/[offerId]/ Travellers → Seats → Extras → Payment
booking/[orderId]/ Confirmation / itinerary
manage/ Find a booking by order number
login/ Email-code sign-in
account/bookings/ My bookings (signed-in customers)
admin/ Back office: supplier switches
api/places | search | offers/[id] | offers/[id]/seats | orders | orders/[id]
api/jinko/checkout Creates the Jinko trip and returns its payment page
api/chat Chat assistant (Gemini or Claude + trip27 search tools, streaming)
api/auth/request | verify | link | logout | me
lib/
auth.ts Signed cookies, sign-in codes, session helpers
providers/duffel.ts Duffel API integration (server-only)
providers/jinko.ts Jinko API integration (search, hosted checkout, status)
suppliers.ts Supplier switches (Global Config) and supplier credentials
admin.ts Admin allowlist (ADMIN_EMAILS)
chat/ Chat assistant: model adapters, tools, system prompt, wire format
providers/demo.ts Demo data provider
types.ts Shared, provider-agnostic types
group.ts Grouping fares by itinerary, sort & filters
components/ UI (search, results, checkout, flight views)
Every supplier call runs on the server, so the Duffel token never reaches the browser. Prices for seats and bags are re-checked against Duffel when the order is created and are never trusted from the browser.