Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
59bbfbc
Prepare Android release 1.0.7
May 20, 2026
4593af0
feat(theme): dark-first navy/aqua palette + Space Grotesk + glow tokens
May 30, 2026
779dbaf
feat(ui): gradient buttons, glass cards, AI spark + thinking states
May 30, 2026
c8b955a
feat(screens): navy/aqua AI redesign of core screens
May 30, 2026
c134d7e
feat(ui): brand-align shared library and remaining screens
May 30, 2026
f22b3c4
feat(motion): Skia aurora, pseudo-3D globe, Lottie animations
May 30, 2026
9842ebf
fix(eas): set explicit runtimeVersion 1.0.7 for bare-workflow EAS builds
May 30, 2026
0921187
feat(connect): free user↔agency lead flow + direct contact
Jun 1, 2026
902e0b7
feat(connect): agency telegram end-to-end + new-lead email notification
Jun 1, 2026
25dece2
feat(integration): merge team platform-updates with redesign + free-m…
Jun 8, 2026
4aecf46
feat(portals): rebuild agency & admin panels with page routing
Jun 11, 2026
695c7a7
fix(admin): readable sidebar nav on dark aside + correct stats shape
Jun 11, 2026
3d01438
feat(portals): flush collapsible sidebars, no-scroll auth, step wizards
Jun 11, 2026
1434ecc
fix(auth-pages): clean agency hero copy + compact no-scroll admin login
Jun 11, 2026
d9df549
fix(agency-auth): brand text rendered as plain text, white nowrap on …
Jun 11, 2026
4faf734
fix(agency): onboarding topbar brand text plain + nowrap
Jun 11, 2026
5e554f9
feat(agency-ux): wizard slide transitions, uz date format, image comp…
Jun 11, 2026
98a2089
fix(agency): collapse button scoped styles, drop first-link always-li…
Jun 11, 2026
e544592
feat(agency): post-change email notice to old+new address
Jun 11, 2026
240bc14
feat(backend): dotenv override mode — local .env keys win over exec env
Jun 11, 2026
b1837ea
style(agency): file inputs as dashed dropzone with green pill button
Jun 11, 2026
7fa12c5
fix(agency-security): detach googleId on login email change
Jun 12, 2026
cfddc41
feat(admin): dismantle monolith content page into dedicated pages + r…
Jun 12, 2026
55008e2
feat(admin): merge reports into dashboard + fix hero slides page
Jun 12, 2026
5b81a81
feat(mobile): guest-friendly booking UX + production-apk profile
Jun 12, 2026
8002307
fix(android): allow EAS-injected release signing (skip MYAPP_UPLOAD g…
Jun 12, 2026
ec74e5b
chore(android): versionCode 25, local appVersionSource for Play release
Jun 12, 2026
f5f99e8
feat(tours): port Codex package-details onto portal v2 base
Jun 12, 2026
940c26a
feat(agency-auth): restore Google sign-in lost in Codex base + AGENTS…
Jun 12, 2026
812891d
fix(mobile): localized booking status, drop dead checkout/payment scr…
Jun 13, 2026
7db49f0
feat(push): booking-status push notifications (Firebase FCM)
Jun 13, 2026
e8ed400
fix(push): wire savePushToken import/export + correct token regex
Jun 13, 2026
73a6d59
chore(android): versionCode 26 + FCM google-services for push build
Jun 13, 2026
61e6d12
feat(push): direct FCM v1 from backend (service account JWT), native …
Jun 13, 2026
6f82254
feat(mobile): split Tours into own tab, planner = AI builder only
Jun 13, 2026
33275c8
feat(planner): Claude (Anthropic) AI itinerary generator for empty-PO…
Jun 13, 2026
0871657
feat(theme): Minimal Green palette (forest/sage) o'rniga ko'kimtir na…
Jun 14, 2026
aa06070
feat(theme): crisp LIGHT default (white bg + vivid emerald), de-muddy…
Jun 14, 2026
cce1089
feat(planner): Gemini full from-scratch itinerary generation (shared …
Jun 14, 2026
bbf436e
chore(mobile): versionCode 27 for Yandex MapKit build
Jun 14, 2026
c619679
fix(planner): Gemini generation - disable thinking + 50s timeout
Jun 14, 2026
9c13994
refactor(mobile): remove burger/side-menu (redundant with tabs + prof…
Jun 14, 2026
8fb4403
fix(tours): robust Turlar tab - cache + refetch on focus (never stuck…
Jun 14, 2026
a23646e
feat(mobile): auth-first launch (skippable) + auth-gated booking, rem…
Jun 15, 2026
003e1c7
feat(tours): 'Qayerga' davlat dropdown + Qidirish tugmasi (yo'nalish …
Jun 16, 2026
d768288
feat(tours): structured tour form v2 (from/to dropdowns, nights+days,…
Jun 16, 2026
cb947d1
security+chore: JWT secrets fail-closed in prod; remove dead code
Jun 16, 2026
c205e0a
chore(planner): remove 46 dead style keys from old tours-view (1639->…
Jun 16, 2026
92b2071
refactor(planner): quieter hero per impeccable critique — drop decora…
Jun 16, 2026
369d1f8
fix(i18n): force Uzbek default (uz-only) — auth/all screens were show…
Jun 17, 2026
1e4a5e4
fix(i18n): force Uzbek at boot even with stale saved en/ru pref (uz-o…
Jun 17, 2026
4d512f1
fix(auth): enforce one-email-one-role across user/agency
Jun 17, 2026
afd8dd7
perf(auth): non-blocking verification/reset emails + SMTP timeouts & …
Jun 17, 2026
83f63f9
perf(agency): non-blocking verification/email-change code emails
Jun 17, 2026
f9ec69c
feat(website): full Uzbek UI + dedicated /tours browse page
Jun 17, 2026
a49468c
feat(website): /tours country+search filters; finish admin Uzbek
Jun 17, 2026
4bd1e8a
feat(website): editable profile/preferences, wishlist remove, AI plan…
Jun 17, 2026
dcb547c
chore(mobile): bump to v1.0.8 (versionCode 28) for production AAB
Jun 19, 2026
7712072
feat(website): premium marketing foundation + new HOME
Jun 20, 2026
520ea4d
feat(website): premium /tours catalog with Region→City dependent filters
Jun 20, 2026
bd089ca
feat(website): premium About, Contact, Privacy, Terms pages (Uzbek)
Jun 20, 2026
40d1467
feat(website): tour detail+booking, signature auth, partners, my-trip…
Jun 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# TravelorAI — Agent Rules (Codex, Claude, etc.)

**READ THIS FIRST. These rules are mandatory for all AI agents working in this repo.**

## 1. Git: which code is current

- The source of truth is the **`integration/full-merge`** branch (mirrored to local `main`).
- **Before ANY work:** `git fetch origin && git log --oneline -3 origin/integration/full-merge` — make sure your working tree contains those commits. If your checkout is behind, **STOP and update first**. Working on a stale base has already destroyed a day of work once.
- Never commit directly to `main` (it is protected on GitHub; changes go through PR).
- Do not leave work uncommitted. Commit to a feature branch and push.

## 2. Architecture facts (do not regress these)

- **Website agency portal is multi-page (portal v2):** `app/agency/{page,leads,tours,tours/new,tours/[id],profile}` + `components/agency/{AgencyShell,AuthScreen,OnboardingScreen,TourEditor,LeadsBoard,...}` + `lib/agency/{api,session,types}`.
- `components/agency/AgencyPortal.tsx` (old 1500-line monolith) is **DELETED**. Never recreate or edit it.
- **Admin panel is multi-page:** `app/admin/{page,moderation,leads,agencies,places,stories,hero}` with `components/admin/AdminShell`. `LandingContentAdmin.tsx` is orphaned — do not extend it.
- Backend `server.js` uses `dotenv {override:true}`; runtime secrets live in the container's `/app/.env` (Google client allowlist, company SMTP). Do not remove.
- Agency Google login: backend route `POST /agency/auth/google` + `googleAuthSchema` + `AgencyAccount.googleId` — required for the website Google button.

## 3. Production deploy (178.18.245.174) — STRICT

- **Containers run under PODMAN**: `travelorai_website` (port 3100), `voyageai_backend` (4000). Postgres+redis run under snap-docker (moby) — do not touch.
- **NEVER create new containers, never `docker run`/`compose up` new instances.** The `docker` CLI on the server is symlinked to podman. Creating a second container on the same port causes a restart-policy port war that silently swallows deploys (this happened on 2026-06-12 and wiped live fixes).
- Correct website deploy:
1. `podman cp <src.tar.gz> travelorai_website:/tmp/` then `podman exec travelorai_website sh -c "cd /app && tar xzf /tmp/src.tar.gz"`
2. Build inside: `podman exec -e NODE_ENV=production -e NEXT_PUBLIC_SITE_URL=https://travelorai.com -e NEXT_PUBLIC_API_URL=https://travelorai.com/api/v1 -e NEXT_PUBLIC_GOOGLE_WEB_CLIENT_ID=401741517790-11c61fghvchvld521kdi0fu4ee1mo2af.apps.googleusercontent.com travelorai_website sh -c "cd /app && node node_modules/next/dist/bin/next build"`
3. `podman restart travelorai_website`
4. Also sync the same files to `/opt/voyageai/website/` (host copy for future image builds).
- Correct backend deploy: same pattern with `voyageai_backend`; run `npx prisma generate && npx prisma migrate deploy` inside the container before restart. Keep `/app/.env` intact.
- After deploy ALWAYS verify: `curl -s localhost:4000/api/v1/health`, key pages return 200, and `podman ps` + `ctr -a /run/snap.docker/containerd/containerd.sock -n moby tasks list` show no duplicate port holders.
- `NEXT_PUBLIC_GOOGLE_WEB_CLIENT_ID` must be `401741517790-11c61f...` (NOT `65326209075-...` — that project is inaccessible).

## 4. Mobile

- Expo bare workflow. JS-only changes ship via OTA: `npx eas update --channel production --platform android -m "msg"` (runtimeVersion must stay `1.0.7` unless a new binary is released).
- Release AAB: `eas build -p android --profile production` (EAS keystore == Play upload key). versionCode is local-source in `android/app/build.gradle` — bump it for every Play upload.
- Never run local Gradle release builds — the upload keystore is EAS-managed.

## 5. Language & product

- UI text in Uzbek only (no i18n yet — explicit product decision). Free lead model: no payments, no commissions; booking must work for guests (name+phone, no forced auth).
2 changes: 1 addition & 1 deletion backend/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ app.use('/uploads', express.static(path.join(__dirname, 'uploads'), {
fallthrough: false,
maxAge: process.env.NODE_ENV === 'production' ? '30d' : 0,
}));
app.use(express.json({ limit: '12mb' }));
app.use(express.json({ limit: '16mb' }));
app.use(loggerMiddleware);

app.get(['/account-deletion', '/delete-account'], (req, res) => {
Expand Down
1 change: 1 addition & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"test": "jest --coverage --passWithNoTests"
},
"dependencies": {
"@anthropic-ai/sdk": "^0.104.1",
"@prisma/client": "^5.7.0",
"axios": "^1.6.2",
"bcryptjs": "^2.4.3",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-- Add telegram contact handle for tour agencies (free user↔agency connection)
ALTER TABLE "TourAgency" ADD COLUMN "telegram" TEXT;

-- Email is now optional for tour booking leads (phone OR email is enough)
ALTER TABLE "TourBooking" ALTER COLUMN "customerEmail" DROP NOT NULL;
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
ALTER TYPE "AuthCodeType" ADD VALUE IF NOT EXISTS 'EMAIL_CHANGE';

ALTER TABLE "AgencyAccount"
ADD COLUMN IF NOT EXISTS "pendingEmail" TEXT,
ADD COLUMN IF NOT EXISTS "emailChangeResendCount" INTEGER NOT NULL DEFAULT 0,
ADD COLUMN IF NOT EXISTS "emailChangeRequestedAt" TIMESTAMP(3);

CREATE UNIQUE INDEX IF NOT EXISTS "AgencyAccount_pendingEmail_key"
ON "AgencyAccount"("pendingEmail");

ALTER TABLE "AgencyApplication"
ADD COLUMN IF NOT EXISTS "imageUrl" TEXT;

ALTER TABLE "Tour"
ADD COLUMN IF NOT EXISTS "responseTimeMinutes" INTEGER NOT NULL DEFAULT 45;

ALTER TABLE "TourBooking"
ADD COLUMN IF NOT EXISTS "responseDeadlineAt" TIMESTAMP(3);

UPDATE "TourBooking" AS booking
SET "responseDeadlineAt" =
booking."createdAt" + make_interval(mins => COALESCE(tour."responseTimeMinutes", 45))
FROM "Tour" AS tour
WHERE booking."tourId" = tour."id"
AND booking."responseDeadlineAt" IS NULL;
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
ALTER TYPE "AuthCodeType" ADD VALUE IF NOT EXISTS 'ACCOUNT_DELETE';

ALTER TABLE "User"
ADD COLUMN "pendingEmail" TEXT,
ADD COLUMN "emailChangeResendCount" INTEGER NOT NULL DEFAULT 0,
ADD COLUMN "emailChangeRequestedAt" TIMESTAMP(3),
ADD COLUMN "accountDeleteResendCount" INTEGER NOT NULL DEFAULT 0,
ADD COLUMN "accountDeleteRequestedAt" TIMESTAMP(3);

CREATE UNIQUE INDEX "User_pendingEmail_key" ON "User"("pendingEmail");
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
-- Backfill older approved agency tours so the mobile/home public APIs can see them.
UPDATE "TourAgency"
SET
"active" = TRUE,
"approvedAt" = COALESCE("approvedAt", "updatedAt"),
"rejectedAt" = NULL
WHERE "approvalStatus" = 'approved';

UPDATE "Tour"
SET
"active" = TRUE,
"badge" = CASE
WHEN lower(COALESCE("badge", '')) = 'popular' THEN 'Popular'
ELSE 'Latest'
END,
"approvedAt" = COALESCE("approvedAt", "updatedAt"),
"rejectedAt" = NULL
WHERE "approvalStatus" = 'approved';

UPDATE "TourAgency" AS agency
SET "toursCount" = counts."approvedCount"
FROM (
SELECT "agencyId", COUNT(*)::INTEGER AS "approvedCount"
FROM "Tour"
WHERE "agencyId" IS NOT NULL
AND "active" = TRUE
AND "approvalStatus" = 'approved'
GROUP BY "agencyId"
) AS counts
WHERE agency."id" = counts."agencyId";

UPDATE "TourAgency" AS agency
SET "toursCount" = 0
WHERE NOT EXISTS (
SELECT 1
FROM "Tour" AS tour
WHERE tour."agencyId" = agency."id"
AND tour."active" = TRUE
AND tour."approvalStatus" = 'approved'
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
-- Ensure approved tours without uploaded covers still render with a useful image in mobile/web.
UPDATE "Tour"
SET "imageUrl" = 'https://images.unsplash.com/photo-1512453979798-5ea266f8880c?auto=format&fit=crop&w=1400&q=80'
WHERE "approvalStatus" = 'approved'
AND ("imageUrl" IS NULL OR trim("imageUrl") = '')
AND (
lower(COALESCE("title", '')) LIKE '%dubai%'
OR lower(COALESCE("title", '')) LIKE '%dubay%'
OR lower(COALESCE("city", '')) LIKE '%dubai%'
OR lower(COALESCE("city", '')) LIKE '%dubay%'
OR lower(COALESCE("city", '')) LIKE '%uae%'
);

UPDATE "Tour"
SET "imageUrl" = 'https://images.unsplash.com/photo-1469854523086-cc02fe5d8800?auto=format&fit=crop&w=1400&q=80'
WHERE "approvalStatus" = 'approved'
AND ("imageUrl" IS NULL OR trim("imageUrl") = '');
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
-- Structured package details for agency tours. All new fields are nullable/defaulted
-- so existing tours stay intact and can be completed later from the agency panel.
ALTER TABLE "Tour"
ADD COLUMN IF NOT EXISTS "priceCurrency" TEXT,
ADD COLUMN IF NOT EXISTS "priceBasis" TEXT,
ADD COLUMN IF NOT EXISTS "departureCity" TEXT,
ADD COLUMN IF NOT EXISTS "destinationCountry" TEXT,
ADD COLUMN IF NOT EXISTS "tourGroup" TEXT,
ADD COLUMN IF NOT EXISTS "nights" INTEGER,
ADD COLUMN IF NOT EXISTS "hotelIncluded" BOOLEAN NOT NULL DEFAULT FALSE,
ADD COLUMN IF NOT EXISTS "hotelName" TEXT,
ADD COLUMN IF NOT EXISTS "hotelCategory" TEXT,
ADD COLUMN IF NOT EXISTS "hotelLocation" TEXT,
ADD COLUMN IF NOT EXISTS "roomType" TEXT,
ADD COLUMN IF NOT EXISTS "mealPlan" TEXT,
ADD COLUMN IF NOT EXISTS "mealPlanLabel" TEXT,
ADD COLUMN IF NOT EXISTS "childPolicy" TEXT,
ADD COLUMN IF NOT EXISTS "flightSeatStatus" TEXT,
ADD COLUMN IF NOT EXISTS "availabilityStatus" TEXT,
ADD COLUMN IF NOT EXISTS "instantConfirmation" BOOLEAN NOT NULL DEFAULT FALSE,
ADD COLUMN IF NOT EXISTS "stopSale" BOOLEAN NOT NULL DEFAULT FALSE,
ADD COLUMN IF NOT EXISTS "promo" BOOLEAN NOT NULL DEFAULT FALSE,
ADD COLUMN IF NOT EXISTS "priceIncludes" TEXT[] NOT NULL DEFAULT ARRAY[]::TEXT[],
ADD COLUMN IF NOT EXISTS "priceExcludes" TEXT[] NOT NULL DEFAULT ARRAY[]::TEXT[];

UPDATE "Tour"
SET
"priceIncludes" = COALESCE("priceIncludes", ARRAY[]::TEXT[]),
"priceExcludes" = COALESCE("priceExcludes", ARRAY[]::TEXT[]),
"hotelIncluded" = COALESCE("hotelIncluded", FALSE),
"instantConfirmation" = COALESCE("instantConfirmation", FALSE),
"stopSale" = COALESCE("stopSale", FALSE),
"promo" = COALESCE("promo", FALSE);

CREATE INDEX IF NOT EXISTS "Tour_mealPlan_idx" ON "Tour"("mealPlan");
CREATE INDEX IF NOT EXISTS "Tour_hotelCategory_idx" ON "Tour"("hotelCategory");
CREATE INDEX IF NOT EXISTS "Tour_availabilityStatus_idx" ON "Tour"("availabilityStatus");
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-- AgencyAccount.googleId (prod DB da allaqachon bor bo'lishi mumkin)
ALTER TABLE "AgencyAccount" ADD COLUMN IF NOT EXISTS "googleId" TEXT;
CREATE UNIQUE INDEX IF NOT EXISTS "AgencyAccount_googleId_key" ON "AgencyAccount"("googleId");
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE "User" ADD COLUMN IF NOT EXISTS "expoPushToken" TEXT;
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
-- Tour form v2: from/to dropdowns, nights+days, hotel/flight checkboxes, discount, price basis people, price lock
ALTER TABLE "Tour" ADD COLUMN IF NOT EXISTS "days" INTEGER;
ALTER TABLE "Tour" ADD COLUMN IF NOT EXISTS "flightIncluded" BOOLEAN NOT NULL DEFAULT false;
ALTER TABLE "Tour" ADD COLUMN IF NOT EXISTS "discount" TEXT;
ALTER TABLE "Tour" ADD COLUMN IF NOT EXISTS "priceBasisPeople" INTEGER;
ALTER TABLE "Tour" ADD COLUMN IF NOT EXISTS "priceLockMinutes" INTEGER;
ALTER TABLE "Tour" ADD COLUMN IF NOT EXISTS "priceLockUntil" TIMESTAMP(3);
Loading