Commit Graph

354 Commits

Author SHA1 Message Date
72473bc5c5 chore(36-02): regenerate routeTree.gen.ts with /admin and /admin/ routes 2026-04-19 20:49:19 +02:00
8f62edc91d feat(36-02): add conditional Admin link to UserMenu for admin users 2026-04-19 20:49:03 +02:00
7a3dca768a feat(36-02): add /admin layout route and placeholder index
- admin.tsx: createFileRoute('/admin') with sidebar shell (Items, Tags disabled with Soon badge)
- admin/index.tsx: createFileRoute('/admin/') placeholder with shield icon
- useEffect guard redirects non-admin users to /
2026-04-19 20:48:53 +02:00
080838ecb5 feat(36-02): add isAdmin to AuthState interface in useAuth.ts 2026-04-19 20:48:30 +02:00
48381105b5 feat(36-01): add /api/admin placeholder route with requireAuth + requireAdmin middleware 2026-04-19 20:47:36 +02:00
18883fb9f0 feat(36-01): surface isAdmin in /api/auth/me response 2026-04-19 20:47:12 +02:00
34c7d27ee5 feat(36-01): add requireAdmin middleware to auth.ts
- Import eq from drizzle-orm and users from schema
- Export requireAdmin(c, next) that returns 401 if userId not in context, 403 if user.isAdmin is falsy
2026-04-19 20:47:06 +02:00
23cdb25063 feat(36-01): add isAdmin column to users table schema and generate migration
- Add isAdmin boolean(is_admin) NOT NULL DEFAULT false to users table
- Generate migration 0009_spotty_lord_tyger.sql
- NOTE: db:push requires DATABASE_URL with correct credentials to apply
2026-04-19 20:46:51 +02:00
7e684176ab fix(35): WR-04 use startsWith/slice for brand-stripping to avoid mid-string matches
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 20:14:28 +02:00
93c273d266 fix(35): WR-03 add onError to GearImage to dismiss skeleton on broken images
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 20:14:07 +02:00
65f25e5964 fix(35): WR-02 close FAB menu before opening catalog search overlay
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 20:13:33 +02:00
d58f7fab40 fix(35-03): add cursor-pointer to FabMenu and BottomTabBar buttons
- Add cursor-pointer to FabMenu menu item buttons (motion.button)
- Add cursor-pointer to FabMenu main FAB button (motion.button)
- Add cursor-pointer to BottomTabBar anonymous collection tab button
- Add cursor-pointer to BottomTabBar anonymous setups tab button
- Add cursor-pointer to BottomTabBar search tab button
2026-04-19 19:50:38 +02:00
e1d516cfe8 fix(35-03): add cursor-pointer to ItemCard navigable case
- Add cursor-pointer to the non-null linkTo branch of the outer button
- Preserve cursor-default for the null linkTo branch (setup cards)
2026-04-19 19:49:44 +02:00
88db308a16 feat(35-02): add image skeleton loading states to all card types
- Add useState(false) loaded state to ItemCard, CandidateCard, GlobalItemCard
- Show bg-gray-100 animate-pulse skeleton overlay while image loads
- Fade in image via transition-opacity duration-200 on onLoad callback
- No-image placeholders (icon on bg-gray-50) unchanged
- Add import { useState } from react to all three files with correct Biome import order
2026-04-19 19:47:11 +02:00
2d2259a0db feat(35-02): add loading=lazy and onLoad prop to GearImage
- Add optional onLoad prop to GearImageProps interface
- Destructure onLoad in function signature
- Forward loading="lazy" and onLoad to all three img render paths (cover, hasCrop, default)
2026-04-19 19:45:01 +02:00
053d56236f fix(35-01): replace login page card UI with immediate useEffect redirect (FIX-04)
- Remove auth check, useNavigate, useTranslation, and full card UI
- LoginPage now renders only "Signing in..." and immediately navigates to server /login
- Server /login route handles Logto OIDC redirect; no client-side logic needed
2026-04-19 19:41:57 +02:00
b43a932217 fix(35-01): extend ItemWithCategory with image and currency fields (FIX-02)
- Add imageUrl, dominantColor, cropZoom, cropX, cropY, priceCurrency to interface
- Server already returns these fields via withImageUrls(); type was just incomplete
2026-04-19 19:41:44 +02:00
7fca92985a fix(35-01): wire Add Candidate button to CatalogSearchOverlay, delete AddCandidateModal
- Replace setAddCandidateOpen(true) with openCatalogSearch("thread") + setCatalogSessionThreadId
- Remove addCandidateOpen useState
- Delete entire AddCandidateModal component (~300 lines of dead code)
- Remove imports only used by the deleted modal: useCreateCandidate, useCurrency, ImageUpload, CategoryPicker
2026-04-19 19:41:33 +02:00
beaea46e92 fix: use CategoryPicker in AddToThreadModal new-thread create mode
Replaces plain <select> with CategoryPicker for consistency with
ManualEntryForm, CreateThreadModal, and other thread creation flows.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 17:00:03 +02:00
5f63e6f75d fix: resolve Bun mock isolation contamination across test files
Some checks failed
CI / ci (push) Failing after 1m35s
CI / e2e (push) Has been skipped
CI / deploy (push) Has been skipped
- storage.service.ts: use dynamic import() inside each function so the
  current @aws-sdk mock is always picked up regardless of module load order
- images.test.ts + image.service.test.ts: replace module-level storage.service
  mock with @aws-sdk/client-s3 mock to avoid contaminating storage.service.test.ts
- routes/auth.test.ts: remove unnecessary oauth.service mock (no test uses
  verifyAccessToken) which was contaminating oauth.service.test.ts
- middleware/auth.test.ts: complete oauth.service mock shape with all exports

All 464 tests now pass in a single bun test run.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 16:19:39 +02:00
4ccbb2b070 fix: wire catalog add buttons, fix Trans bold rendering, lint cleanup
Some checks failed
CI / ci (push) Failing after 1m44s
CI / e2e (push) Has been skipped
CI / deploy (push) Has been skipped
- CatalogSearchOverlay: replace handleAddStub with real openAddToCollection/openAddToThread routing based on catalogSearchMode
- ConfirmDialog + __root.tsx: swap t() for Trans component on deleteItemMessage, deleteCandidateMessage, pickWinnerMessage — fixes <bold> rendering as literal text
- Biome format pass: fix 23 lint/format errors across scripts, services, tests
- Planning: mark all UAT and verification gaps resolved for phases 07, 11, 16, 20, 21, 22, 24, 32, 34; close debug sessions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 15:36:16 +02:00
44b1eac0ba feat(catalog): migrate dev seed data to manufacturer-slug-based global items
Replace brand text field with manufacturerSlug in DEV_GLOBAL_ITEMS,
global-items-seed.json, and seed-global-items.ts. Add DEV_MANUFACTURERS
for dev-only brands not in SEED_MANUFACTURERS. Expand SEED_MANUFACTURERS
with 8 additional manufacturers referenced by seed JSON (Nemo, Therm-a-Rest,
Toaks, Katadyn, HydraPak, Nitecore, Outdoor Research, Exposure Lights).
Update dev-seed.ts to resolve slug→id before insert and use manufacturerId
as the deduplication key.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 16:37:27 +02:00
0b4715b80c fix: update all tests and MCP catalog tool for manufacturerId schema migration 2026-04-18 16:30:11 +02:00
a508773809 feat: all services join manufacturers for global item brand display 2026-04-18 16:24:24 +02:00
2924c2269c feat: item service joins manufacturers for brand display 2026-04-18 16:22:10 +02:00
12b3f8e380 feat: upsertGlobalItemSchema — brand → manufacturerSlug 2026-04-18 16:21:32 +02:00
5037350aa0 feat: global-item service uses manufacturerSlug, joins manufacturers for brand 2026-04-18 16:21:25 +02:00
8ff680ef92 feat: migrate globalItems — drop brand text, add manufacturerId FK 2026-04-18 16:19:31 +02:00
f868bbdecf feat: seed manufacturers list, update seedGlobalItems to resolve by name
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 16:16:52 +02:00
ec27df1d0f feat: manufacturers route — list, get, create
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 16:16:27 +02:00
8c1b19f07d feat: manufacturer service with list, get, create
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 16:15:40 +02:00
7de3e9e957 feat: add manufacturers table to schema
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 14:54:23 +02:00
bea386e7db style(i18n): fix lint — formatting and import ordering across 21 files
All checks were successful
CI / ci (push) Successful in 1m21s
CI / e2e (push) Has been skipped
CI / deploy (push) Successful in 1m15s
Biome auto-fix for formatting (line length, ternary wrapping) and
import organization in files touched by phase 34 i18n work.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-18 14:49:10 +02:00
dbab84ef2a fix(i18n): wire useTranslation into SetupsView — close verification gap
Replace hardcoded English strings in SetupsView.tsx with t() calls
using existing setups namespace keys. Closes the 1 gap found during
phase 34 verification.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-18 14:41:23 +02:00
31297a3921 fix(34-05): add missing German translation keys to collection namespace
- Add form.msrp, form.purchasePrice, form.itemNamePlaceholder, form.optionalNotes
- Fixes key parity test failure in tests/i18n/locales.test.ts
2026-04-18 14:08:51 +02:00
bf64b8f6a5 chore: merge executor worktree (worktree-agent-a1291d63 — plan 34-02) 2026-04-18 14:04:14 +02:00
2aa156a6b7 feat(34-02): extract hardcoded strings from modals, routes, and catalog
- AddToCollectionModal: all labels, placeholders, toast messages
- collection/index.tsx: tab labels (Gear/Planning)
- threads/$threadId/index.tsx: thread detail page and AddCandidateModal
- items/$itemId.tsx: back links, action buttons, field labels, metadata
- setups/$setupId.tsx: all setup detail strings and confirm dialog
- users/$userId.tsx: public profile page strings
- global-items/index.tsx: discover/catalog filter UI strings
- Added catalog.json namespace (en + de) and registered in i18n.ts
- Extended en/de threads, setups, collection, common locales with missing keys
2026-04-18 14:01:09 +02:00
6fd8874970 feat(34-02): extract hardcoded strings from thread/candidate components
- CandidateCard: replace all hardcoded titles and badge text with t()
- CandidateListItem: add useTranslation, replace winner/delete/open labels and +/- Notes badge
- CandidateForm: add useTranslation, replace all form labels, placeholders, validation errors, submit button
- ComparisonTable: move STATUS_LABELS inside component with t(), replace all ATTRIBUTE_ROWS labels, View button, impact row labels
- StatusBadge: refactor STATUS_CONFIG to STATUS_ICONS + runtime STATUS_LABELS via t()
- CreateThreadModal: replace title, thread name label, category label, placeholder, cancel/submit buttons, error messages
- AddToThreadModal: replace modal titles, labels, placeholders, back/cancel/submit buttons, error messages
- threads.json: extend candidateForm with category, notes, pros, cons, product link labels and all placeholders
2026-04-18 13:44:26 +02:00
c5af1247c0 feat(34-02): i18n collection and item components
- CollectionView: t() for empty state, stats labels, filter text
- ItemCard: t() for tooltip title attributes
- ItemForm: t() for all form labels, placeholders, error messages, buttons
- CategoryPicker: t() for search placeholder, create button, no results
- CategoryFilterDropdown: t() for all categories label, search placeholder
- CategoryHeader: t() for save/cancel buttons, item count
- WeightSummaryCard: t() for title, legend labels, view mode toggle
- ItemPicker: t() for panel title, empty state, action buttons
- ManualEntryForm: t() for all form labels, error messages, submit button
- LinkToGlobalItem: t() for all UI chrome strings
- ProfileSection: t() for all form labels, messages, buttons
- collection.json: added new keys for categoryPicker, categoryFilter, weightSummary, itemPicker, categoryHeader, linkToGlobal, manualEntry, profileSection, itemCard
2026-04-18 13:35:59 +02:00
23172f794f fix(34-08): add 58 missing German translations to 5 de/*.json locale files
- de/common.json: add home, imageUpload, profile sections (34 keys)
- de/settings.json: add currency.suggestion, currency.switch, showConversions (4 keys)
- de/threads.json: add card.candidates, card.candidates_one, planning section (11 keys)
- de/setups.json: add card.by, card.anonymous, impact.compareWith (3 keys)
- de/collection.json: add tabs.setups, totals, classificationBadge (6 keys)
- Fixed JSON syntax error: replaced smart quotes in dangerZoneDescription with single quotes
- All German text uses proper Unicode umlauts throughout
- bun test tests/i18n/locales.test.ts: 19 pass, 0 fail
2026-04-18 13:29:12 +02:00
1963faea84 fix(34-07): replace ASCII umlaut fallbacks with proper Unicode in all German locale files
- common.json: Löschen, Schließen, Zurück, Bestätigen, Änderungen, Überspringen, Gegenstände, etc.
- collection.json: Ausrüstung, Gegenstände, Zusätzliche, Hinzufügen
- threads.json: wählen, Kategorie, hinzufügen, Sammlung, hinzugefügt
- setups.json: Ausrüstung, Gegenstände, Öffentlich, Läuft, können, Zurückschalten
- onboarding.json: Ausrüstung, Gegenstände, wählen, fügen, überspringen, prüfen, Stöbern
- settings.json: Schlüssel, Währung, Wählen, Ändern, Gegenstände, Ausrüstung
2026-04-17 20:30:48 +02:00
480abdd17f feat(34-06): wire useTranslation into 10 remaining components
- ThreadTabs: tab labels (gear, planning, setups) via collection namespace
- PlanningView: section title, tab labels, empty state steps, CTAs via threads namespace
- TotalsBar: 'Sign in' link via common.auth.signIn
- ThreadCard: resolved badge and candidate count (plural) via threads namespace
- PublicSetupCard: by/anonymous and item count (plural) via setups namespace
- SetupImpactSelector: compare dropdown placeholder via setups.impact.compareWith
- ClassificationBadge: base/worn/consumable labels via collection.classificationBadge
- ImpactDeltaBadge: add mode label via setups.impact.adding
- ImageUpload: click-to-add, error messages via common.imageUpload
- DashboardCard: skipped (renders props only, no hardcoded UI strings)
- Add card, planning keys to en/de threads.json
- Add classificationBadge, tabs, totals keys to en/de collection.json
- Add card.by, card.anonymous, impact.compareWith to en/de setups.json
- Add imageUpload keys to en/de common.json
- Build passes, all 19 i18n parity tests pass
2026-04-17 20:26:50 +02:00
755c0ab89f feat(34-06): wire useTranslation into routes and settings currency suggestion
- Add useTranslation to routes/index.tsx: home section headings use t()
- Add useTranslation to routes/profile.tsx: all profile/security/danger zone strings use t()
- Wire currency suggestion banner in settings.tsx with t() interpolation
- Wire showConversions section title/description in settings.tsx
- Add home and profile keys to en/common.json
- Add currency.suggestion, currency.switch, showConversions to en/settings.json
- Add corresponding German translations with proper umlauts to de/common.json and de/settings.json
2026-04-17 20:21:54 +02:00
28dfef555c feat: wire currency conversion into price display
All checks were successful
CI / ci (push) Successful in 1m22s
CI / e2e (push) Has been skipped
CI / deploy (push) Successful in 14s
useFormatters().price() now accepts an optional sourceCurrency param.
When showConversions is enabled and the source differs from the user's
currency, it converts via ECB rates and shows dual format:
"€200.00 (~$218.00)". ItemCard and CollectionView pass priceCurrency
through from API data. Setup detail items also pass priceCurrency.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 21:44:32 +02:00
c4ddc573d4 fix: price labels use user's selected currency instead of hardcoded $
All checks were successful
CI / ci (push) Successful in 1m21s
CI / e2e (push) Has been skipped
CI / deploy (push) Successful in 15s
Replaced hardcoded "Price ($)" labels across 6 components and 2 locale
files to display the user's selected currency (EUR, GBP, USD, etc.).
AddToCollectionModal also updated to show correct currency.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 21:33:32 +02:00
23027551b4 fix: currency suggestion uses region detection, seed adds market prices
All checks were successful
CI / ci (push) Successful in 1m24s
CI / e2e (push) Has been skipped
CI / deploy (push) Successful in 15s
- Currency auto-suggestion now uses locale region subtag (en-US → US → USD,
  en-DE → DE → EUR) instead of language prefix. Fixes wrong suggestion for
  users with English browser locale in European countries.
- Added dismiss button (X) to suggestion banner
- Dev seed script now clears existing dev data before re-seeding (safe to
  run repeatedly without manual DB cleanup)
- Added DEV_MARKET_PRICES with multi-market UVP data for 10 global items
  (EU/US/UK prices) and community prices for 5 owned items

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 21:27:57 +02:00
51c8703a3d fix: share modal UX improvements and creator name fallback
All checks were successful
CI / ci (push) Successful in 1m26s
CI / e2e (push) Has been skipped
CI / deploy (push) Successful in 21s
- Share links section always visible (not just in link/public mode),
  supporting future write-access link shares on public setups
- Link list layout improved: URL and expiration stacked vertically,
  action buttons have hover backgrounds, trash icon replaces X
- Public setup cards show "by Anonymous" when creator has no display name

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 20:49:28 +02:00
4c80e9aa3c fix: allow unauthenticated access to /items/* with setup context
All checks were successful
CI / ci (push) Successful in 1m23s
CI / e2e (push) Has been skipped
CI / deploy (push) Successful in 15s
Items accessed via ?setup= or ?share= query params are now treated as
public routes, preventing the auth redirect to /login.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 20:34:13 +02:00
4b26a6c88e feat: public item detail view for shared and public setups
All checks were successful
CI / ci (push) Successful in 1m23s
CI / e2e (push) Has been skipped
CI / deploy (push) Successful in 15s
Items in shared/public setups are now viewable without auth. Clicking
an item in a shared setup navigates to /items/:id?setup=:setupId&share=token
which fetches the item via a public endpoint authorized by the setup's
visibility or share token. Read-only mode hides all owner controls.

- Added getSetupItemById service function
- Added GET /api/shared/:token/items/:itemId endpoint
- Added GET /api/setups/:setupId/items/:itemId/public endpoint
- Added usePublicSetupItem and useSharedSetupItem hooks
- Item detail page detects setup context and switches to public fetch
- Back link returns to setup instead of collection in setup context

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 20:17:54 +02:00
731d677da6 fix: shared setup items link to catalog instead of requiring auth
Items with a globalItemId now link to /global-items/:id (public) in
shared and public setup views. Items without a catalog link are not
clickable. Owner view behavior unchanged.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 20:10:02 +02:00