Commit Graph

100 Commits

Author SHA1 Message Date
0bd9165e66 feat(web): show engine code in summary for all decode sources
The collapsed summary's "Motor kodu" read only PL24's rawData.engineCode, so it
was blank for emex/pcat/vin-api. Resolve it per source: PL24 engineCode, emex
parsedOptions.engine_type, vin-api engineModel, or the pcat "engine" parameter.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 03:48:10 +03:00
8fe731b7e0 feat(web): add emex + vin-api vehicle attribute extraction
Extend the source dispatcher to the remaining decode sources. emex surfaces its
parsedOptions codes (engine_type/gearbox_type/vehicle_type → Motor tipi /
Şanzıman tipi / Araç tipi), led by any readable DB columns (Vites, Kasa).
vin-api surfaces its flat fields (bodyClass, engineModel, transmissionStyle,
plantCountry → Kasa / Motor / Vites / Üretim ülkesi). Both replace the bare
3-field DB fallback these vehicles showed before.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 03:47:42 +03:00
1549c14a09 feat(web): source-aware vehicle attributes — surface pcat parameters
Vehicle info only ever read PL24's vinfoBasic segment, so emex/pcat/vin-api
vehicles fell back to 3-4 sparse DB columns. Introduce a getDisplayAttributes
dispatcher keyed on the decode source. First source added: parts-catalogs,
which ships a structured pcatCar.parameters list (Year, Region, Steering,
Transmission type/code, Engine, Type, Configuration…) — sorted by sortOrder
with English names mapped to Turkish. A shared header-dedup drops the
brand/model/year rows that already live in the h2 and the collapsed summary.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 03:47:01 +03:00
dd95641497 feat(web): surface decoded equipment/PR-code list in vehicle card
The PL24 VIN decode already ships a readable description for every PR code in
the prNr segment (187 here for the VW Golf) — surface it instead of guessing a
dictionary. The expanded accordion now shows a "Donanım kodları" section: each
row pairs the code as a mono badge with its decoded Turkish description (paint
and color entries included), in a scrollable two-column list. Raw \r\n and \-
escapes are cleaned.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 03:28:52 +03:00
2075631c65 feat(web): make vehicle info card a collapsible accordion
Collapse the Araç Bilgileri card by default. The always-visible header now
shows an at-a-glance summary (Model, Model yılı, Motor kodu); expanding reveals
the full attribute grid, which also gains the decoded drive type (Çekiş /
"Önden çekiş") from rawData. The loading skeleton now matches the collapsed
header so there's no layout jump when data lands.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 03:27:55 +03:00
531ab35a89 polish(web): tidy dashboard shell — emoji, dead bell, footer links, dvh
Remove the emoji from the header greeting and the duplicate avatar/name block
(the sidebar already shows the user). Remove the bell button that had no handler
or notification system behind it. Turn the footer's plain "Gizlilik Politikası,
Kullanım Koşulları" text into real /privacy and /terms links (matching the
landing footer). Switch min-h-screen to min-h-[100dvh] so the layout doesn't
jump on mobile Safari.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 03:13:54 +03:00
08aa3ac88f feat(web): add error/not-found state + safe back nav to vehicle page
The vehicle detail page had no error handling: a failed /vehicles/:id (404 or
network) rendered a broken header ("  ()") with no retry, while only the
categories sub-page had a proper error block. Now a failed load shows a
not-found vs. generic-error alert with retry (mirroring the category page), and
404s skip retries so the state appears immediately. The back button now uses
the router history and falls back to /dashboard/search on deep links instead of
window.history.back() leaving the site.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 03:09:20 +03:00
45ac5783b5 feat(web): make vehicle info card readable instead of raw VIN codes
The Araç Bilgileri card showed only raw PL24 VIN-decoded codes (Satış tipi
1J10E4, Donanım 82, Motor kodu BCB…) and fully suppressed the friendlier DB
fields. Now it leads with the readable engine description + body type, then the
decoded codes; drops the Model/Model-yılı rows that duplicate the h2 header;
maps the known mojibake labels to clean Turkish; drops the uppercase transform
(which corrupted the Turkish dotted/dotless i); and lays out 2 cols on mobile
with long values spanning the full row.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 03:07:38 +03:00
c2960013f0 feat(web): speculative pre-decode on a valid VIN (faster counter lookups)
The moment the VIN reaches 17 valid chars, warm the full decode in the background
(350ms debounce, deduped per VIN, fire-and-forget) so the "Şase Çöz" click hits
the backend lock / 24h positive cache and returns near-instantly. If the warm is
still in flight when the dealer clicks, the button's decode lock-waits on it and
navigates the moment it lands.

No analytics/state/navigation from the pre-decode — vin_decoded and the
success/candidates/error events still fire only from the button path, so the CRO
funnel stays = real intent. Raw fetch + AbortController (api.post takes no signal),
mirroring the existing 17-char preview effect.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 00:54:22 +03:00
70ee3d637f fix(web): fire checkout_started when the payment step is reached (all plans)
checkout_started only fired for the "full" plan (plus a mostly-unreachable
sticky-CTA branch), so non-full-plan users reached payment without it ever
firing — PostHog showed fewer checkout_started (1 person) than the downstream
payment_initiated (3), which is impossible in a clean funnel.

Move it to a step-driven effect that fires once when the payment step is
reached, via any path (plan card, sticky CTA, or a prefilled pending plan),
re-arming if the user returns to plan selection. Fixes the Monetization and
Ödeme funnels at the checkout step and keeps plan -> checkout meaningful
(brand-selection drop-off now shows up instead of a trivial 100% step).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 23:34:43 +03:00
195b3204c3 feat(web): land new signups on VIN search, activation as a modal
New signups were redirected to /dashboard/subscription (the paywall):
91% of them hit it and 78% bounced without ever searching a VIN (PostHog,
30d). Acquisition is strong (homepage -> register) but activation was the
cliff — only 13% of new users ever reached the search page.

- register: redirect signups to /dashboard/search?welcome=1 (value-first),
  carrying the landing-page VIN + referral code across.
- search: read welcome/vin/ref params; prefill a carried VIN and auto-decode
  it once the activation modal is dismissed (no re-typing); strip params after.
- WelcomeOnboardingModal: the old full-page provisioning view is now a modal
  over the search page. It applies the referral (covers the Google OAuth
  signup path), starts the trial, plays the Remotion onboarding motion, then
  steps aside so the user lands on their vehicle's parts.
- trial_started now fires for every eligible new user (was a rarely-clicked
  button) — this populates the Onboarding funnel, which previously read 0.
- tests: add useSearch to the router stub; refresh the stale retry-CTA tests
  to the shipped submit-doubles-as-retry behavior (retry CTA dropped in a33deef).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 23:24:53 +03:00
49891a8df6 chore(web): contact address label 'Hak Sahibi' → 'Firma Adı'
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 20:04:20 +03:00
ea96e7fc25 chore(web): update contact page address to THINXTRA LLC
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 19:58:13 +03:00
6f4d6eff5c fix(web): consistent SiteHeader across all public pages
Blog and contact (and about/pricing/legal) pages rendered a stripped-down
header (logo + auth buttons only) with no nav — the site nav "disappeared".
Extracted the landing header into a shared <SiteHeader> (logo, nav, theme
toggle, auth-aware actions, mobile menu) used on every public page for
consistent UX.

- new components/site-header.tsx (nav links work from any route via /#…)
- landing nav gains Blog + İletişim so all pages share the same 5-link nav
- about, contact, pricing, blog, blog/$slug, kvkk, privacy, terms → SiteHeader
- demo keeps its purpose-built demo header (sticky + theme + CTA)

typecheck + biome clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 19:45:21 +03:00
e5c852b537 feat(blog): data-driven blog API + DB for content-pipeline publishing
Makes the blog publishable from the Süper Panel content pipeline (via n8n).
Backend mirrors the changelog module; frontend keeps the existing hand-authored
posts and merges in API-backed ones (no content migration, no regression).

API (apps/api):
- blog_posts Drizzle table (slug unique, title, meta_description, body_markdown,
  tags, cover_image, status, source, published_at)
- blog module: GET /blog/posts (public list), GET /blog/posts/:slug (public),
  POST /blog/posts/internal (Bearer BLOG_AUTOMATION_TOKEN, mirrors
  changelog/internal) — returns { ...post, url }
- BlogService: Drizzle + Redis cache, defensive Turkish-aware slugify +
  uniqueness; registered in app.module

Web (apps/web):
- use-blog hooks (list + by-slug, react-query, 30m staleTime)
- blog list: static + API merged, dedup by slug, newest first
- blog detail: static post renders as before; API post renders body_markdown
  via react-markdown (remark-gfm) styled to match existing prose
- add react-markdown + remark-gfm

Deploy: set BLOG_AUTOMATION_TOKEN (and optional PUBLIC_WEB_URL) on the api,
run drizzle-kit db:push to create blog_posts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 01:48:10 +03:00
1b2cfd4a0f feat(dashboard): kategori sayfalarında ana alanı tam genişlik yap
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
Araç ve katalog model detay sayfalarındaki max-w-4xl cap'i kaldırıldı;
kategori grid'i geniş monitörde xl:4 / 2xl:5 sütuna çıkıyor. Geniş
ekranlarda yanlardaki boşluk dolduruluyor ve kategori isimleri artık
kısaltılmadan sığıyor.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 00:23:06 +03:00
8e57d20eaf chore(telemetry): tag vehicle + category interactions for Faro
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
Only the search route was emitting data-faro-user-action-name attributes, so
the vehicle detail and category detail pages were dark in our Faro user-action
funnels even though they sit on the critical post-decode path.

Added attributes for:
- Vehicle page back button (`vehicle-back`)
- Category page back button (`category-back`)
- Category error retry button (`category-retry`)
- Breadcrumb root vehicle link (`breadcrumb-vehicle`)
- Breadcrumb intermediate category links (`breadcrumb-category`)
- View toggle buttons (`category-view-grid|tree|columns`) — emitted from the
  shared component so any future consumer inherits the tagging.

No behavioral change; pure observability.

Phase 8/8 of the UX audit follow-up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 20:55:13 +03:00
b110b917b6 fix(vehicle-page): proper dl semantics, layout-matched skeleton, empty state
- VehicleAttributes wrapped in <dl> (was emitting <dt>/<dd> with no list
  parent). Both the rich vinfoBasic branch and the DB-fallback branch now
  share one layout instead of diverging into span-based and dt/dd-based
  trees — easier to style and screen-reader friendly.
- New explicit empty state ("Bu araç için ayrıntı bilgisi bulunamadı") when
  both branches resolve to nothing. Previously rendered an empty grid.
- Loading skeleton now mirrors the actual page structure (header chip + logo
  + 2-line title, info card with 6 attribute slots, categories card with 8
  rows). The previous three plain rectangles caused a layout jump on resolve.
- Attribute rows use small-caps muted labels and tabular-nums values for
  scannable data (engine codes, mileages, years).

Phase 7/8 of the UX audit follow-up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 20:54:10 +03:00
a33deef040 fix(error-states): retry CTA on category error, drop duplicate retry on search
Category page: the previous error UI was a single flat red block with no
recovery path — users had to refresh the browser. Now it uses the standard
alert pattern (heading + detail + action), surfaces the actual error message
when one is available, and offers a Tekrar dene button wired to refetch().

Search page: removed the dedicated "Tekrar Dene" button inside the error
banner. The main Şase Çöz submit button sits immediately above and remains
enabled after an error — having two near-identical CTAs stacked on top of
each other was just noise. The error banner is now informational only.

Also drops the now-unused handleRetry function and RotateCcw import in the
search route.

Phase 6/8 of the UX audit follow-up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 20:52:54 +03:00
cfe090b45c fix(search): left-aligned header, drop the hero card, debounce auto-fix toast
Two unrelated polish items in the VIN search page bundled into one commit
because they touch the same file.

1. Hero card removed. The entire form lived inside a generic
   `rounded-2xl border bg-background p-6 sm:p-8` container on a page that had
   no other content competing for attention — the card added no hierarchy,
   only a frame. The header is also no longer centered: icon sits to the left
   of a left-aligned h2 + subtitle, breaking the AI-default centered hero.

2. I/O/Q auto-correction toast is now debounced. Each keystroke that produced
   a correction fired its own toast, so a user holding the I key or pasting
   "IIO" stacked three toasts on top of each other. Corrections now collect
   in a ref-backed Set and a single consolidated toast fires 400ms after the
   last edit, with cleanup on unmount.

Phase 5/8 of the UX audit follow-up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 20:51:19 +03:00
25300a339a refactor(category-view): extract shared toggle component with a11y
Both the vehicle detail page and the category detail page shipped their own
copy of a three-button view-mode toggle (Grid/Tree/Columns). They diverged
subtly and shared the same accessibility gaps: 28px tap targets, only `title`
attributes for screen readers, no `aria-pressed`, no focus ring.

- New `CategoryViewToggle` (apps/web/src/components/categories/) renders a
  proper `role="group"` segmented control with `aria-pressed`, `aria-label`
  per option, 36px tap targets, and a focus-visible ring.
- Active state is conveyed by an elevated background pill rather than just a
  hover-grey, so the selected mode is legible without color contrast guessing.
- Both pages collapse to a single line: `<CategoryViewToggle … />`.

Phase 4/8 of the UX audit follow-up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 20:49:38 +03:00
34ad77b2a1 feat(category-page): add breadcrumb trail + safer back navigation
Addresses long-term fix from insight cmpifhxfs000lvze9cy6ne0tx: users in
deep category trees had no orientation cue beyond a single back button.

- Breadcrumb derived from the cached /categories/tree/<vehicleId> response.
  When the cache is warm (user came from the vehicle page), the full ancestry
  renders; otherwise it falls back gracefully to "Vehicle → Current".
- Vehicle label fetched via the same query key already used by the vehicle
  page, so the request is deduped.
- Back button now prefers the breadcrumb parent (resolved synchronously from
  cache) over `data.parentId` (which is async). Clicking back before the
  category payload loads no longer dumps the user to the vehicle root.
- H1 placeholder during load swapped from a stale "Kategori Detayı" string
  to an inline pulse — prevents the title from briefly displaying wrong text
  before the real name resolves. Uses a span-based pulse (Skeleton is a div
  and would be invalid HTML inside an h1).

Phase 3/8 of the UX audit follow-up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 20:48:15 +03:00
3478144c87 fix(dashboard): restore Turkish diacritics in vehicle + category pages
Strings displayed to users were missing Turkish characters (ö, ç, ğ, ş, ı, ü).
Affects vehicle detail header/info card, category detail page, view-mode tooltips,
and the "category not found" empty state in the tree component.

Phase 1/8 of the UX audit follow-up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 20:44:28 +03:00
b4f1b2bd10 feat(ads): wire Meta Pixel for ad attribution
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
Pixel ID 1904240520247944 (sase.tr) created on ad account
act_1227112768351770. Lazy-injected like PostHog so it stays off
when VITE_META_PIXEL_ID is unset. Tracks PageView on every route
change, CompleteRegistration on email/Google signup, and
InitiateCheckout on Stripe button click (value in TRY).

Coolify env: set VITE_META_PIXEL_ID=1904240520247944 before next
deploy so it gets baked into the Vite build.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 23:28:40 +03:00
Fusion
5118c8e5e9 feat(FN-415): add locator-stability + hit-target verification tests
Commits merged:
- feat(FN-415): add locator-stability + hit-target verification tests

Files changed:
apps/api/src/telemetry/__tests__/telemetry.spec.ts |  8 +++
 .../src/routes/__tests__/dashboard-search.test.tsx | 67 ++++++++++++++++++++++
 2 files changed, 75 insertions(+)

Fusion-Task-Id: FN-415

Fusion-Task-Lineage: 01e6f3d2-cd2e-4023-b538-ea54926b4f87
2026-05-17 06:17:51 +00:00
Fusion
65daf99b2e chore(payments): remove EFT/bank-transfer method, keep Stripe only
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
Removes the EFT (havale) payment surface across API, web, i18n, and docs.
Card payment via Stripe is now the only checkout path.

API (apps/api/src):
- Delete payments/bank-accounts/ module (controller + service + module)
- payments.service: drop EFT methods (createEftPayment, uploadEftReceipt,
  approveEft, rejectEft, getActiveBankInfo, getPendingEftPayments) and the
  BankAccountsService dependency
- payments.controller: drop /payments/eft, /payments/eft/:id/{receipt,approve,reject},
  /payments/bank-info, /payments/pending; keep /payments/me
- payments.module: drop BankAccountsModule import
- admin.service: drop pending EFT counter from getDashboardStats; drop
  getPendingPayments (admin EFT approval list)
- admin.controller: drop /admin/payments/pending endpoint
- admin.service.spec: drop the getPendingPayments + pendingPayments assertions

Web (apps/web/src):
- Delete components/payment/bank-transfer-card.tsx
- Delete routes/dashboard/admin/payments.tsx (EFT approval page); regenerate
  routeTree.gen.ts
- subscription/index.tsx: drop EFT tab from PaymentMethodSection, drop the
  paymentMethod state + handleEftCompleted, simplify ConfirmationCard to the
  Stripe-only path, drop unused imports (Tabs, BankTransferCard, Building2)
- billing.tsx: drop "eft" from method filter chips; keep methodLabels.eft and
  the legacy receipt download so historical EFT records still display
- dashboard.tsx + admin/index.tsx: drop /dashboard/admin/payments from the
  sidebar + admin quick-link grid; trim unused icon imports; KEYS_6 → KEYS_5
  for the now-5-card admin stats skeleton
- messages/{tr,en}.json: strip every EFT-only key under payment.* (eftTransfer,
  eftConfirmationDescription, eftPaid, eftStatus, bank.*, uploadReceipt* etc.)

Schema (apps/api/src/database/schema):
- bankAccounts table + payments.bankAccountId column are kept as-is: legacy
  EFT payments remain visible on the billing page and the schema preserves
  historical records.

Docs:
- README.md: "Stripe (kart) + EFT" → "Stripe (kart)"
- CLAUDE.md: stack table + PaymentsModule row + payments table note + route
  list updated; admin EFT approval route removed

Verification:
- pnpm typecheck: green (api + web + shared + config + ui)
- pnpm lint: green
- pnpm test: 20 web tests pass, 173 api tests pass
2026-05-16 11:03:30 +03:00
Fusion
e274e02c23 feat(FN-367): add inline retry affordance after VIN decode failure (FN-367, gitea #11)
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
Commits merged:
- fix(web): add inline retry affordance after VIN decode failure (FN-367, gitea #11)

Files changed:
apps/api/src/telemetry/__tests__/telemetry.spec.ts |   2 +-
 .../src/routes/__tests__/dashboard-search.test.tsx | 172 +++++++++++++++++++++
 apps/web/src/routes/dashboard/search.tsx           | 145 ++++++++++-------
 3 files changed, 266 insertions(+), 53 deletions(-)

Fusion-Task-Id: FN-367

Fusion-Task-Lineage: 5dccf49f-5c77-4fe4-ae2d-0273016d517d
2026-05-14 13:55:36 +00:00
Fusion
a0deefd4b2 chore(remotion): acknowledge Remotion license on Player instances
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
Suppresses the 'license required' console warning on the homepage.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 10:40:57 +00:00
Fusion
5bd5325367 feat(FN-356): add checkout_started PostHog event to subscription checkout flow (+1 more)
Commits merged:
- chore(FN-356): verify checkout_started event is in place
- feat(FN-356): add checkout_started PostHog event to subscription checkout flow

Files changed:
apps/web/src/routes/dashboard/subscription/index.tsx | 4 ++++
 1 file changed, 4 insertions(+)

Fusion-Task-Id: FN-356
2026-05-14 10:00:40 +00:00
Fusion
906eef148d feat(FN-346): add CLS regression test for skeleton plan grid (+1 more)
Commits merged:
- feat(FN-346): add grid-parity integration test for skeleton CLS regression
- feat(FN-346): add CLS regression test for skeleton plan grid

Files changed:
.../subscription/__tests__/cls-regression.test.tsx | 128 +++++++++++++++++++
 .../subscription/__tests__/grid-parity.test.tsx    | 141 +++++++++++++++++++++
 .../src/routes/dashboard/subscription/index.tsx    |   4 +-
 3 files changed, 271 insertions(+), 2 deletions(-)

Fusion-Task-Id: FN-346
2026-05-14 05:09:47 +00:00
Fusion
aaf2e763cb feat(FN-345): fix skeleton layout shift, dialog overflow, and safe-area padding (+1 more)
Commits merged:
- fix(FN-345): add mobile-safe max-width to cancel confirm dialog
- fix(FN-345): fix skeleton layout shift, dialog overflow, and safe-area padding

Files changed:
apps/web/src/routes/dashboard/subscription/index.tsx | 15 +++++++++------
 1 file changed, 9 insertions(+), 6 deletions(-)

Fusion-Task-Id: FN-345
2026-05-14 03:05:45 +00:00
Fusion
561b02b3d2 feat(FN-343): remove lingering iyzico references after Stripe migration (+1 more)
Commits merged:
- chore(FN-343): remove lingering iyzico references from docs, config, and scripts
- feat(FN-343): remove lingering iyzico references after Stripe migration

Files changed:
CLAUDE.md                                    | 10 +++++-----
 README.md                                    |  2 +-
 apps/api/src/database/schema/core.ts         |  1 +
 apps/web/src/messages/en.json                |  1 -
 apps/web/src/messages/tr.json                |  1 -
 apps/web/src/routes/dashboard/billing.tsx    |  6 +++---
 docker-compose.coolify.yml                   |  5 ++---
 docs/INDEX.md                                | 27 ++++++++++++-------------
 knowledge.md                                 | 30 ++++++++++++++--------------
 packages/shared/src/constants/error-codes.ts |  1 -
 packages/shared/src/index.ts                 |  1 -
 packages/shared/src/types/payment.ts         | 12 +----------
 scripts/fn342-pw-verify.mjs                  |  2 +-
 scripts/validate-env.sh                      |  3 +--
 14 files changed, 43 insertions(+), 59 deletions(-)

Fusion-Task-Id: FN-343
2026-05-14 02:31:42 +00:00
Sase Dev
02bf7342a1 fix(subscription): recover stepper when a pending checkout is abandoned
Some checks failed
Deploy / Deploy to Production (push) Has been cancelled
A user who picked a plan + brands, clicked the checkout button to
create the pending subscription, then closed the tab without paying
came back to a frozen "Mevcut Plan · Bekliyor" card with no payment
button and no way to edit. Status was "pending", which my conditional
treated as an active subscription and routed away from the stepper.

Frontend
- hasActiveSub narrowed to active|cancelled only. Pending users now
  see the stepper again.
- New effect pre-fills selectedPlanKey, selectedBrandIds,
  billingPeriod from the pending row on first render and jumps
  straight to the payment step.
- New PendingPaymentBanner at the top of the stepper with
  "Odemeye devam et" (scrolls + sets step=payment) and "Vazgec"
  (calls cancel-pending then resets selection).

Backend
- subscriptions.service.create() auto-expires existing pending
  rows for the user before inserting a new one — abandoned checkouts
  no longer accumulate and a fresh attempt with a different plan
  doesn't 409.
- New PATCH /subscriptions/cancel-pending + service method for
  the banner's explicit cancel action.

i18n: subscription.pendingBanner.* (tr/en).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 22:57:13 +00:00
Sase Dev
6a0b36a7a0 feat: replace Iyzico with Stripe + single-page subscription stepper
Subscription/checkout flow rewritten end-to-end. The plan-card "Devam Et"
button silently wiped brand selection on re-click and the actual proceed
button lived offscreen — fixed by collapsing plan/brands/payment/confirm
into a single vertical stepper with one sticky CTA.

Backend
- Stripe Hosted Checkout (`/payments/stripe/checkout`) + webhook
  (`/payments/stripe/webhook`, raw body) replacing the stubbed Iyzico
  module. Webhook activates subscription on `checkout.session.completed`,
  expires the pending subscription on cancel/expire so users can retry.
- New `bank_accounts` table — multiple rows, single `is_active` enforced
  by a partial unique index. Admin CRUD under `/admin/bank-accounts`
  with multipart QR upload to MinIO; atomic `/activate` swap in a
  transaction; `GET /payments/bank-info` returns the active row.
- `payments` gains `stripe_session_id`, `stripe_payment_intent_id`,
  `bank_account_id`. EFT flow now reads the active bank account at
  payment time and stores the FK for reconciliation.
- Env: `IYZICO_*` removed, `STRIPE_*` added (validated by zod schema).
- `main.ts` `rawBody: true` for Stripe signature verification.
- Drizzle 0003 snapshot id collision fixed (VIEW-only migration shared
  prevId with 0002, blocking new generates).

Frontend
- `/dashboard/subscription` rewritten as a 4-step vertical stepper with
  step-aware sticky bottom CTA; plan re-selection is idempotent and
  preserves brand state. `/dashboard/subscription/pay` deleted; Stripe
  returns to the same page via `?stripe=success|cancelled` and the UI
  polls `/subscriptions/me` until the webhook activates the row.
- New components: `bank-transfer-card.tsx` (DB-driven IBAN + Kolay Adres
  + uploaded QR image + receipt upload) and `stripe-checkout-button.tsx`.
- Active subscription view, trial onboarding/urgency banner, downgrade
  and cancel dialogs preserved.
- TR/EN i18n: new `subscription.steps.*`, `subscription.stickyCta.*`,
  `payment.stripe.*`, `payment.bank.*`; provider label updated.

PostHog: `method: "iyzico"` → `"stripe"`; new events `iban_copied`,
`kolay_adres_copied`, `qr_viewed`, `eft_initiated`,
`stripe_redirect_returned`.

Deploy runs `db:migrate` which applies 0004_hot_quicksilver
(additive: new table + nullable columns; safe to apply on prod).
Operator must add `STRIPE_SECRET_KEY`, `STRIPE_PUBLISHABLE_KEY`,
`STRIPE_WEBHOOK_SECRET` to env and create the first
`bank_accounts` row via the admin endpoint before the bank tab works.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 22:42:28 +00:00
Sase Dev
0b6d4bb0a7 feat(analytics): enrich PostHog VIN/parts/subscription events (SASE-PH-001 Faz 0/1 additive subset)
Per sase-posthog-events-prd.md, additive-only pass (no renames, no dead-feature events):

- search.tsx: add search_input_focused, search_input_validation_failed,
  search_paste_detected, search_history_item_selected; track query_source
  (manual|paste|history) and propagate to vin_decoded/success/error; add
  response_time_ms, source, status_code on success/error; add selected_index,
  candidates_count, time_to_select_ms to vin_decode_candidate_selected.
- parts-panel.tsx: add parts_panel_viewed with vehicle_id, category_id,
  parts_count, has_prices, available_groups_count (one-shot per mount).
- subscription/index.tsx: enrich plan_selected (period, from_plan, status),
  checkout_started (from_plan, brands_count, trial_available, status),
  subscription_cancelled (from_plan, billing_period, status_before_cancel,
  days_since_start, had_downgrade_offer).
2026-05-13 22:05:50 +00:00
Fusion
4cb715f1b4 feat(FN-300): add trial urgency banner to subscription page
Some checks failed
Sync dev → Gitea / Mirror dev to Gitea (push) Has been cancelled
Commits merged:
- feat(FN-300): complete Step 1 — add trial urgency banner to subscription page

Files changed:
.../src/routes/dashboard/subscription/index.tsx    | 107 +++++++++++++++++++++
 1 file changed, 107 insertions(+)

Fusion-Task-Id: FN-300
2026-05-13 08:13:26 +00:00
Fusion
3dd4dc2f77 feat(FN-297): add social proof impression tracking to landing page testimonials (+2 more)
Some checks failed
Sync dev → Gitea / Mirror dev to Gitea (push) Has been cancelled
Commits merged:
- docs(FN-297): update FN-275 audit with social proof event locations
- fix(FN-297): format IntersectionObserver mock in test file
- feat(FN-297): add social proof impression tracking to landing page testimonials

Files changed:
apps/web/src/routes/__tests__/index.test.tsx |   7 ++
 apps/web/src/routes/index.tsx                | 111 +++++++++++++++++++++++++--
 docs/product/funnel-audit-p1-cro-2026-05.md  |  19 ++++-
 3 files changed, 127 insertions(+), 10 deletions(-)

Fusion-Task-Id: FN-297
2026-05-13 07:28:34 +00:00
Fusion
c0fef5921b feat(FN-282): add social proof impression tracking to testimonial and trust components
Some checks failed
Sync dev → Gitea / Mirror dev to Gitea (push) Has been cancelled
Commits merged:
- feat(FN-282): add social proof impression tracking to testimonial and trust components

Files changed:
apps/web/src/routes/_auth.tsx                      | 42 +++++++++++-
 .../src/routes/dashboard/subscription/index.tsx    | 80 ++++++++++++++++++++--
 2 files changed, 117 insertions(+), 5 deletions(-)

Fusion-Task-Id: FN-282
2026-05-13 06:36:16 +00:00
Fusion
d855b564bf feat(FN-281): add payment_success and payment_failed PostHog events (+1 more)
Some checks failed
Sync dev → Gitea / Mirror dev to Gitea (push) Has been cancelled
Commits merged:
- fix(FN-281): make result search param truly optional to fix typecheck
- feat(FN-281): add payment_success and payment_failed PostHog events

Files changed:
apps/api/package.json                              |    1 +
 apps/api/src/app.module.ts                         |    2 +
 apps/api/src/payments/payments.service.spec.ts     |   99 +-
 apps/api/src/payments/payments.service.ts          |   40 +
 apps/api/src/posthog/posthog.module.ts             |    9 +
 apps/api/src/posthog/posthog.service.ts            |   64 ++
 .../web/src/components/payment/payment-content.tsx |   24 +-
 apps/web/src/routeTree.gen.ts                      | 1110 ++++++++++----------
 apps/web/src/routes/dashboard/subscription/pay.tsx |   24 +-
 packages/config/src/index.ts                       |    4 +
 pnpm-lock.yaml                                     |   30 +
 11 files changed, 827 insertions(+), 580 deletions(-)

Fusion-Task-Id: FN-281
2026-05-13 06:30:58 +00:00
Fusion
7707443be6 feat(FN-278): add yearly_toggle_clicked PostHog events and 4-col skeleton grid
Some checks failed
Sync dev → Gitea / Mirror dev to Gitea (push) Has been cancelled
- Track yearly_toggle_clicked PostHog event on billing period toggle (monthly/yearly)
- Update skeleton grid from 2-col to 4-col layout with additional skeleton cards
- Add regression tests verifying analytics tracking and skeleton grid rendering

Fusion-Task-Id: FN-278
2026-05-13 05:55:25 +00:00
Fusion
cc2bfbe2b0 feat(FN-211): normalize typography on subscription page
Some checks failed
Sync dev → Gitea / Mirror dev to Gitea (push) Has been cancelled
Commits merged:
- fix(FN-211): normalize typography on subscription page

Files changed:
apps/web/src/routes/dashboard/subscription/index.tsx | 20 +++++++++++---------
 1 file changed, 11 insertions(+), 9 deletions(-)

Fusion-Task-Id: FN-211
2026-05-13 05:30:19 +00:00
Fusion
fa49c20d09 feat(FN-263): normalize typography on subscription page (+2 more)
Some checks failed
Sync dev → Gitea / Mirror dev to Gitea (push) Has been cancelled
Commits merged:
- feat(FN-263): normalize typography on subscription page
- chore(FN-263): apply Biome formatting fix for long className lines
- feat(FN-263): normalize typography on subscription page

Files changed:
.../src/routes/dashboard/subscription/index.tsx    | 116 +++++++++++----------
 1 file changed, 62 insertions(+), 54 deletions(-)

Fusion-Task-Id: FN-263
2026-05-13 04:51:56 +00:00
Fusion
f0bfe6ec8b feat(FN-280): add cancel-save flow PostHog events — cancel_flow_viewed and cancel_save_clicked
Some checks failed
Sync dev → Gitea / Mirror dev to Gitea (push) Has been cancelled
Commits merged:
- feat(FN-280): add cancel-save flow PostHog events — cancel_flow_viewed and cancel_save_clicked

Files changed:
apps/web/src/routes/dashboard/subscription/index.tsx | 17 ++++++++++++++++-
 1 file changed, 16 insertions(+), 1 deletion(-)

Fusion-Task-Id: FN-280
2026-05-13 03:58:39 +00:00
Fusion
6ae26e7a62 feat(FN-213): a11y audit — fix plan card double-focus trap, trust bar ARIA, color contrast
Some checks failed
Sync dev → Gitea / Mirror dev to Gitea (push) Has been cancelled
Commits merged:
- feat(FN-213): a11y audit — fix plan card double-focus trap, trust bar ARIA, color contrast

Files changed:
apps/web/src/routes/dashboard/subscription/__tests__/index.test.tsx | 1 +
 1 file changed, 1 insertion(+)

Fusion-Task-Id: FN-213
2026-05-13 01:54:30 +00:00
Fusion
e94c1e4267 feat(FN-271): implement a11y fixes (plan card focus, trust bar ARIA, color contrast)
Some checks failed
Sync dev → Gitea / Mirror dev to Gitea (push) Has been cancelled
Commits merged:
- feat(FN-271): complete Step 1 — implement a11y fixes (plan card focus, trust bar ARIA, color contrast)

Files changed:
.../subscription/__tests__/index.test.tsx          | 87 ++++++++++++--------
 .../src/routes/dashboard/subscription/index.tsx    | 94 ++++++++++++++++++----
 2 files changed, 133 insertions(+), 48 deletions(-)

Fusion-Task-Id: FN-271
2026-05-13 00:32:58 +00:00
Fusion
03e401b133 feat(FN-217): lazy-load canvas-confetti via dynamic import to avoid bloating subscription bundle (+2 more)
Some checks failed
Sync dev → Gitea / Mirror dev to Gitea (push) Has been cancelled
Commits merged:
- fix(FN-217): fix import sort order (lint)
- fix(FN-217): convert @sase/shared to emit ESM for Vite/Rollup build compatibility
- fix(FN-217): lazy-load canvas-confetti via dynamic import to avoid bloating subscription bundle

Files changed:
apps/web/src/routes/dashboard/subscription/index.tsx | 13 +++++++------
 apps/web/src/routes/pricing.tsx                      |  2 +-
 packages/shared/package.json                         |  5 +++--
 packages/shared/tsconfig.json                        |  4 ++--
 4 files changed, 13 insertions(+), 11 deletions(-)

Fusion-Task-Id: FN-217
2026-05-12 22:57:32 +00:00
Fusion
3dfd0de0c6 feat(FN-216): simplify plan card click handler — remove duplicate Button.onClick
Some checks failed
Sync dev → Gitea / Mirror dev to Gitea (push) Has been cancelled
Commits merged:
- fix(FN-216): simplify plan card click handler — remove duplicate Button.onClick

Files changed:
apps/web/src/routes/dashboard/subscription/index.tsx | 4 ----
 1 file changed, 4 deletions(-)

Fusion-Task-Id: FN-216
2026-05-12 22:31:53 +00:00
Fusion
479000d4a1 feat(FN-215): use shared formatTRY singleton instead of per-call Intl.NumberFormat
Some checks failed
Sync dev → Gitea / Mirror dev to Gitea (push) Has been cancelled
Commits merged:
- perf(FN-215): use shared formatTRY singleton instead of per-call Intl.NumberFormat

Files changed:
apps/web/src/components/payment/payment-content.tsx  |  9 +--------
 apps/web/src/routes/dashboard/admin/index.tsx        | 11 ++---------
 apps/web/src/routes/dashboard/admin/payments.tsx     | 11 ++---------
 apps/web/src/routes/dashboard/admin/users.tsx        | 11 ++---------
 apps/web/src/routes/dashboard/billing.tsx            |  9 ++-------
 apps/web/src/routes/dashboard/subscription/index.tsx | 10 +---------
 apps/web/src/routes/pricing.tsx                      | 10 +---------
 7 files changed, 11 insertions(+), 60 deletions(-)

Fusion-Task-Id: FN-215
2026-05-12 22:28:23 +00:00
Fusion
e97aab6fe4 feat(FN-214): extract brandLimit 999 magic number to FULL_PLAN_BRAND_LIMIT constant in @sase/shared (+1 more)
Some checks failed
Sync dev → Gitea / Mirror dev to Gitea (push) Has been cancelled
Commits merged:
- fix(FN-214): add missing import for FULL_PLAN_BRAND_LIMIT in subscription/index.tsx
- feat(FN-214): extract brandLimit 999 magic number to FULL_PLAN_BRAND_LIMIT constant in @sase/shared

Files changed:
apps/web/src/routes/dashboard/subscription/index.tsx | 16 +++++++++++-----
 apps/web/src/routes/pricing.tsx                      |  3 ++-
 packages/shared/src/constants/plans.ts               |  3 +++
 packages/shared/src/index.ts                         |  2 +-
 4 files changed, 17 insertions(+), 7 deletions(-)

Fusion-Task-Id: FN-214
2026-05-12 22:21:14 +00:00
Fusion
1a90641529 feat(FN-210): add compact testimonials strip below trust bar on landing page
Some checks failed
Sync dev → Gitea / Mirror dev to Gitea (push) Has been cancelled
Commits merged:
- feat(FN-210): add compact testimonials strip below trust bar on landing page

Files changed:
apps/web/src/routes/index.tsx | 55 +++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 55 insertions(+)

Fusion-Task-Id: FN-210
2026-05-12 22:12:23 +00:00