**Scammer blocklist** - migration 006: scammer_blocklist table (platform + seller_id unique key, source: manual|csv_import|community) - ScammerEntry dataclass + Store.add/remove/list_blocklist methods - blocklist.ts Pinia store — CRUD, export CSV, import CSV with validation - BlocklistView.vue — list with search, export/import, bulk-remove; sellers show on ListingCard with force-score-0 badge - API: GET/POST/DELETE /api/blocklist + CSV export/import endpoints - Router: /blocklist route added; AppNav link **Migration renumber** - 002_background_tasks.sql → 007_background_tasks.sql (correct sequence after blocklist; idempotent CREATE IF NOT EXISTS safe for existing DBs) **Search + listing UI overhaul** - SearchView.vue: keyword expansion preview, filter chips for condition/ format/price, saved-search quick-run button, paginated results - ListingCard.vue: trust tier badge, scammer flag overlay, photo count chip, quick-block button, save-to-search action - savedSearches store: optimistic update on run, last-run timestamp **Tier refactor** - tiers.py: full rewrite with docstring ladder, BYOK LOCAL_VISION_UNLOCKABLE flag, intentionally-free list with rationale (scammer_db, saved_searches, market_comps free to maximise adoption) **Trust aggregator + scraper** - aggregator.py: blocklist check short-circuits scoring to 0/BAD_ACTOR - scraper.py: listing format detection, photo count, improved title parsing **Theme** - theme.css: trust tier color tokens, badge variants, blocklist badge
17 lines
583 B
TypeScript
17 lines
583 B
TypeScript
/**
|
|
* Thin fetch wrapper that redirects to login on 401.
|
|
* All stores should use this instead of raw fetch() for authenticated endpoints.
|
|
*/
|
|
|
|
const LOGIN_URL = 'https://circuitforge.tech/login'
|
|
|
|
export async function apiFetch(url: string, init?: RequestInit): Promise<Response> {
|
|
const res = await fetch(url, init)
|
|
if (res.status === 401) {
|
|
const next = encodeURIComponent(window.location.href)
|
|
window.location.href = `${LOGIN_URL}?next=${next}`
|
|
// Return a never-resolving promise — navigation is in progress
|
|
return new Promise(() => {})
|
|
}
|
|
return res
|
|
}
|