fix: wire catalog add buttons, fix Trans bold rendering, lint cleanup
- 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>
This commit is contained in:
@@ -16,6 +16,8 @@ export function CatalogSearchOverlay() {
|
||||
const catalogSearchOpen = useUIStore((s) => s.catalogSearchOpen);
|
||||
const catalogSearchMode = useUIStore((s) => s.catalogSearchMode);
|
||||
const closeCatalogSearch = useUIStore((s) => s.closeCatalogSearch);
|
||||
const openAddToCollection = useUIStore((s) => s.openAddToCollection);
|
||||
const openAddToThread = useUIStore((s) => s.openAddToThread);
|
||||
|
||||
const [searchInput, setSearchInput] = useState("");
|
||||
const [debouncedQuery, setDebouncedQuery] = useState("");
|
||||
@@ -131,9 +133,13 @@ export function CatalogSearchOverlay() {
|
||||
});
|
||||
}
|
||||
|
||||
function handleAddStub(e: React.MouseEvent) {
|
||||
function handleAdd(e: React.MouseEvent, itemId: number, itemName: string) {
|
||||
e.stopPropagation();
|
||||
// Stub: actual add-to-collection / add-to-thread wired in Phase 22
|
||||
if (catalogSearchMode === "collection") {
|
||||
openAddToCollection(itemId, itemName);
|
||||
} else {
|
||||
openAddToThread(itemId, itemName);
|
||||
}
|
||||
}
|
||||
|
||||
const contextText = (() => {
|
||||
@@ -555,7 +561,13 @@ export function CatalogSearchOverlay() {
|
||||
<GridCard
|
||||
key={item.id}
|
||||
item={item}
|
||||
onAdd={handleAddStub}
|
||||
onAdd={(e) =>
|
||||
handleAdd(
|
||||
e,
|
||||
item.id,
|
||||
`${item.brand} ${item.model}`,
|
||||
)
|
||||
}
|
||||
onCardClick={() => handleCardClick(item.id)}
|
||||
weight={weight}
|
||||
price={price}
|
||||
@@ -568,7 +580,13 @@ export function CatalogSearchOverlay() {
|
||||
<ListRow
|
||||
key={item.id}
|
||||
item={item}
|
||||
onAdd={handleAddStub}
|
||||
onAdd={(e) =>
|
||||
handleAdd(
|
||||
e,
|
||||
item.id,
|
||||
`${item.brand} ${item.model}`,
|
||||
)
|
||||
}
|
||||
onCardClick={() => handleCardClick(item.id)}
|
||||
weight={weight}
|
||||
price={price}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import { useDeleteItem, useItems } from "../hooks/useItems";
|
||||
import { useUIStore } from "../stores/uiStore";
|
||||
|
||||
@@ -35,7 +35,11 @@ export function ConfirmDialog() {
|
||||
{t("confirm.deleteItem")}
|
||||
</h3>
|
||||
<p className="text-sm text-gray-600 mb-6">
|
||||
{t("confirm.deleteItemMessage", { name: itemName })}
|
||||
<Trans
|
||||
i18nKey="confirm.deleteItemMessage"
|
||||
values={{ name: itemName }}
|
||||
components={{ bold: <strong /> }}
|
||||
/>
|
||||
</p>
|
||||
<div className="flex justify-end gap-3">
|
||||
<button
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
useRouter,
|
||||
} from "@tanstack/react-router";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import { Toaster } from "sonner";
|
||||
import "../app.css";
|
||||
import { AddToCollectionModal } from "../components/AddToCollectionModal";
|
||||
@@ -244,7 +244,11 @@ function CandidateDeleteDialog({
|
||||
{t("confirm.deleteCandidate")}
|
||||
</h3>
|
||||
<p className="text-sm text-gray-600 mb-6">
|
||||
{t("confirm.deleteCandidateMessage", { name: candidateName })}
|
||||
<Trans
|
||||
i18nKey="confirm.deleteCandidateMessage"
|
||||
values={{ name: candidateName }}
|
||||
components={{ bold: <strong /> }}
|
||||
/>
|
||||
</p>
|
||||
<div className="flex justify-end gap-3">
|
||||
<button
|
||||
@@ -308,7 +312,11 @@ function ResolveDialog({
|
||||
{t("confirm.pickWinner")}
|
||||
</h3>
|
||||
<p className="text-sm text-gray-600 mb-6">
|
||||
{t("confirm.pickWinnerMessage", { name: candidateName })}
|
||||
<Trans
|
||||
i18nKey="confirm.pickWinnerMessage"
|
||||
values={{ name: candidateName }}
|
||||
components={{ bold: <strong /> }}
|
||||
/>
|
||||
</p>
|
||||
<div className="flex justify-end gap-3">
|
||||
<button
|
||||
|
||||
@@ -21,21 +21,111 @@ export const DEV_CATEGORIES = [
|
||||
// Seeded with onConflictDoNothing — safe to overlap with SEED_MANUFACTURERS.
|
||||
|
||||
export const DEV_MANUFACTURERS = [
|
||||
{ name: "Rockgeist", slug: "rockgeist", website: "https://rockgeist.com", country: "US", tier: 1 },
|
||||
{ name: "Oveja Negra", slug: "oveja-negra", website: "https://ovejanegrabikewear.com", country: "US", tier: 1 },
|
||||
{ name: "Durston", slug: "durston", website: "https://durstondesigns.com", country: "US", tier: 1 },
|
||||
{ name: "Enlightened Equipment", slug: "enlightened-equipment", website: "https://enlightenedequipment.com", country: "US", tier: 1 },
|
||||
{ name: "BRS", slug: "brs", website: "https://brs-outdoor.com", country: "CN", tier: 1 },
|
||||
{ name: "Soto", slug: "soto", website: "https://sotostoves.com", country: "JP", tier: 1 },
|
||||
{ name: "Snow Peak", slug: "snow-peak", website: "https://snowpeak.com", country: "JP", tier: 1 },
|
||||
{ name: "Lezyne", slug: "lezyne", website: "https://lezyne.com", country: "US", tier: 1 },
|
||||
{ name: "Fenix", slug: "fenix", website: "https://fenixlighting.com", country: "CN", tier: 1 },
|
||||
{ name: "Park Tool", slug: "park-tool", website: "https://parktool.com", country: "US", tier: 1 },
|
||||
{ name: "Gorilla Tape", slug: "gorilla-tape", website: "https://gorillatough.com", country: "US", tier: 1 },
|
||||
{ name: "Patagonia", slug: "patagonia", website: "https://patagonia.com", country: "US", tier: 1 },
|
||||
{ name: "Frogg Toggs", slug: "frogg-toggs", website: "https://froggtoggs.com", country: "US", tier: 1 },
|
||||
{ name: "Buff", slug: "buff", website: "https://buffwear.com", country: "ES", tier: 1 },
|
||||
{ name: "Anker", slug: "anker", website: "https://anker.com", country: "CN", tier: 1 },
|
||||
{
|
||||
name: "Rockgeist",
|
||||
slug: "rockgeist",
|
||||
website: "https://rockgeist.com",
|
||||
country: "US",
|
||||
tier: 1,
|
||||
},
|
||||
{
|
||||
name: "Oveja Negra",
|
||||
slug: "oveja-negra",
|
||||
website: "https://ovejanegrabikewear.com",
|
||||
country: "US",
|
||||
tier: 1,
|
||||
},
|
||||
{
|
||||
name: "Durston",
|
||||
slug: "durston",
|
||||
website: "https://durstondesigns.com",
|
||||
country: "US",
|
||||
tier: 1,
|
||||
},
|
||||
{
|
||||
name: "Enlightened Equipment",
|
||||
slug: "enlightened-equipment",
|
||||
website: "https://enlightenedequipment.com",
|
||||
country: "US",
|
||||
tier: 1,
|
||||
},
|
||||
{
|
||||
name: "BRS",
|
||||
slug: "brs",
|
||||
website: "https://brs-outdoor.com",
|
||||
country: "CN",
|
||||
tier: 1,
|
||||
},
|
||||
{
|
||||
name: "Soto",
|
||||
slug: "soto",
|
||||
website: "https://sotostoves.com",
|
||||
country: "JP",
|
||||
tier: 1,
|
||||
},
|
||||
{
|
||||
name: "Snow Peak",
|
||||
slug: "snow-peak",
|
||||
website: "https://snowpeak.com",
|
||||
country: "JP",
|
||||
tier: 1,
|
||||
},
|
||||
{
|
||||
name: "Lezyne",
|
||||
slug: "lezyne",
|
||||
website: "https://lezyne.com",
|
||||
country: "US",
|
||||
tier: 1,
|
||||
},
|
||||
{
|
||||
name: "Fenix",
|
||||
slug: "fenix",
|
||||
website: "https://fenixlighting.com",
|
||||
country: "CN",
|
||||
tier: 1,
|
||||
},
|
||||
{
|
||||
name: "Park Tool",
|
||||
slug: "park-tool",
|
||||
website: "https://parktool.com",
|
||||
country: "US",
|
||||
tier: 1,
|
||||
},
|
||||
{
|
||||
name: "Gorilla Tape",
|
||||
slug: "gorilla-tape",
|
||||
website: "https://gorillatough.com",
|
||||
country: "US",
|
||||
tier: 1,
|
||||
},
|
||||
{
|
||||
name: "Patagonia",
|
||||
slug: "patagonia",
|
||||
website: "https://patagonia.com",
|
||||
country: "US",
|
||||
tier: 1,
|
||||
},
|
||||
{
|
||||
name: "Frogg Toggs",
|
||||
slug: "frogg-toggs",
|
||||
website: "https://froggtoggs.com",
|
||||
country: "US",
|
||||
tier: 1,
|
||||
},
|
||||
{
|
||||
name: "Buff",
|
||||
slug: "buff",
|
||||
website: "https://buffwear.com",
|
||||
country: "ES",
|
||||
tier: 1,
|
||||
},
|
||||
{
|
||||
name: "Anker",
|
||||
slug: "anker",
|
||||
website: "https://anker.com",
|
||||
country: "CN",
|
||||
tier: 1,
|
||||
},
|
||||
] as const;
|
||||
|
||||
// ── Global Items ───────────────────────────────────────────────────
|
||||
|
||||
@@ -83,7 +83,10 @@ async function seedDevData(database: Db = db) {
|
||||
// ── 1. Seed global items, tags, and dev-specific manufacturers ─
|
||||
await seedGlobalItems(database);
|
||||
for (const m of DEV_MANUFACTURERS) {
|
||||
await database.insert(schema.manufacturers).values(m).onConflictDoNothing();
|
||||
await database
|
||||
.insert(schema.manufacturers)
|
||||
.values(m)
|
||||
.onConflictDoNothing();
|
||||
}
|
||||
console.log(" Global items, tags, and manufacturers seeded.");
|
||||
|
||||
@@ -145,7 +148,9 @@ async function seedDevData(database: Db = db) {
|
||||
for (const item of DEV_GLOBAL_ITEMS) {
|
||||
const mfId = mfBySlug.get(item.manufacturerSlug);
|
||||
if (!mfId) {
|
||||
console.warn(` Skipping "${item.model}" — unknown manufacturer slug: ${item.manufacturerSlug}`);
|
||||
console.warn(
|
||||
` Skipping "${item.model}" — unknown manufacturer slug: ${item.manufacturerSlug}`,
|
||||
);
|
||||
globalItemIds.push(0); // placeholder to keep index alignment
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -5,58 +5,261 @@ import { globalItems, manufacturers, tags } from "./schema.ts";
|
||||
type Db = typeof prodDb;
|
||||
|
||||
export const SEED_MANUFACTURERS = [
|
||||
{ name: "Revelate Designs", slug: "revelate-designs", website: "https://revelatedesigns.com", country: "US", tier: 1 },
|
||||
{ name: "Apidura", slug: "apidura", website: "https://apidura.com", country: "GB", tier: 1 },
|
||||
{ name: "Ortlieb", slug: "ortlieb", website: "https://ortlieb.com", country: "DE", tier: 1 },
|
||||
{ name: "Big Agnes", slug: "big-agnes", website: "https://bigagnes.com", country: "US", tier: 1 },
|
||||
{ name: "Tarptent", slug: "tarptent", website: "https://tarptent.com", country: "US", tier: 1 },
|
||||
{ name: "Zpacks", slug: "zpacks", website: "https://zpacks.com", country: "US", tier: 1 },
|
||||
{ name: "Sea to Summit", slug: "sea-to-summit", website: "https://seatosummit.com", country: "AU", tier: 1 },
|
||||
{ name: "Western Mountaineering", slug: "western-mountaineering", website: "https://westernmountaineering.com", country: "US", tier: 1 },
|
||||
{ name: "MSR", slug: "msr", website: "https://msrgear.com", country: "US", tier: 1 },
|
||||
{ name: "BioLite", slug: "biolite", website: "https://bioliteenergy.com", country: "US", tier: 1 },
|
||||
{ name: "Petzl", slug: "petzl", website: "https://petzl.com", country: "FR", tier: 1 },
|
||||
{ name: "Black Diamond", slug: "black-diamond", website: "https://blackdiamondequipment.com", country: "US", tier: 1 },
|
||||
{ name: "Garmin", slug: "garmin", website: "https://garmin.com", country: "US", tier: 1 },
|
||||
{ name: "Wahoo", slug: "wahoo", website: "https://wahoofitness.com", country: "US", tier: 1 },
|
||||
{ name: "Sawyer", slug: "sawyer", website: "https://sawyerproducts.com", country: "US", tier: 1 },
|
||||
{ name: "Canyon", slug: "canyon", website: "https://canyon.com", country: "DE", tier: 1 },
|
||||
{ name: "Specialized", slug: "specialized", website: "https://specialized.com", country: "US", tier: 1 },
|
||||
{ name: "Trek", slug: "trek", website: "https://trekbikes.com", country: "US", tier: 1 },
|
||||
{ name: "Salsa Cycles", slug: "salsa-cycles", website: "https://salsacycles.com", country: "US", tier: 1 },
|
||||
{ name: "Surly", slug: "surly", website: "https://surlybikes.com", country: "US", tier: 1 },
|
||||
{
|
||||
name: "Revelate Designs",
|
||||
slug: "revelate-designs",
|
||||
website: "https://revelatedesigns.com",
|
||||
country: "US",
|
||||
tier: 1,
|
||||
},
|
||||
{
|
||||
name: "Apidura",
|
||||
slug: "apidura",
|
||||
website: "https://apidura.com",
|
||||
country: "GB",
|
||||
tier: 1,
|
||||
},
|
||||
{
|
||||
name: "Ortlieb",
|
||||
slug: "ortlieb",
|
||||
website: "https://ortlieb.com",
|
||||
country: "DE",
|
||||
tier: 1,
|
||||
},
|
||||
{
|
||||
name: "Big Agnes",
|
||||
slug: "big-agnes",
|
||||
website: "https://bigagnes.com",
|
||||
country: "US",
|
||||
tier: 1,
|
||||
},
|
||||
{
|
||||
name: "Tarptent",
|
||||
slug: "tarptent",
|
||||
website: "https://tarptent.com",
|
||||
country: "US",
|
||||
tier: 1,
|
||||
},
|
||||
{
|
||||
name: "Zpacks",
|
||||
slug: "zpacks",
|
||||
website: "https://zpacks.com",
|
||||
country: "US",
|
||||
tier: 1,
|
||||
},
|
||||
{
|
||||
name: "Sea to Summit",
|
||||
slug: "sea-to-summit",
|
||||
website: "https://seatosummit.com",
|
||||
country: "AU",
|
||||
tier: 1,
|
||||
},
|
||||
{
|
||||
name: "Western Mountaineering",
|
||||
slug: "western-mountaineering",
|
||||
website: "https://westernmountaineering.com",
|
||||
country: "US",
|
||||
tier: 1,
|
||||
},
|
||||
{
|
||||
name: "MSR",
|
||||
slug: "msr",
|
||||
website: "https://msrgear.com",
|
||||
country: "US",
|
||||
tier: 1,
|
||||
},
|
||||
{
|
||||
name: "BioLite",
|
||||
slug: "biolite",
|
||||
website: "https://bioliteenergy.com",
|
||||
country: "US",
|
||||
tier: 1,
|
||||
},
|
||||
{
|
||||
name: "Petzl",
|
||||
slug: "petzl",
|
||||
website: "https://petzl.com",
|
||||
country: "FR",
|
||||
tier: 1,
|
||||
},
|
||||
{
|
||||
name: "Black Diamond",
|
||||
slug: "black-diamond",
|
||||
website: "https://blackdiamondequipment.com",
|
||||
country: "US",
|
||||
tier: 1,
|
||||
},
|
||||
{
|
||||
name: "Garmin",
|
||||
slug: "garmin",
|
||||
website: "https://garmin.com",
|
||||
country: "US",
|
||||
tier: 1,
|
||||
},
|
||||
{
|
||||
name: "Wahoo",
|
||||
slug: "wahoo",
|
||||
website: "https://wahoofitness.com",
|
||||
country: "US",
|
||||
tier: 1,
|
||||
},
|
||||
{
|
||||
name: "Sawyer",
|
||||
slug: "sawyer",
|
||||
website: "https://sawyerproducts.com",
|
||||
country: "US",
|
||||
tier: 1,
|
||||
},
|
||||
{
|
||||
name: "Canyon",
|
||||
slug: "canyon",
|
||||
website: "https://canyon.com",
|
||||
country: "DE",
|
||||
tier: 1,
|
||||
},
|
||||
{
|
||||
name: "Specialized",
|
||||
slug: "specialized",
|
||||
website: "https://specialized.com",
|
||||
country: "US",
|
||||
tier: 1,
|
||||
},
|
||||
{
|
||||
name: "Trek",
|
||||
slug: "trek",
|
||||
website: "https://trekbikes.com",
|
||||
country: "US",
|
||||
tier: 1,
|
||||
},
|
||||
{
|
||||
name: "Salsa Cycles",
|
||||
slug: "salsa-cycles",
|
||||
website: "https://salsacycles.com",
|
||||
country: "US",
|
||||
tier: 1,
|
||||
},
|
||||
{
|
||||
name: "Surly",
|
||||
slug: "surly",
|
||||
website: "https://surlybikes.com",
|
||||
country: "US",
|
||||
tier: 1,
|
||||
},
|
||||
// Additional manufacturers referenced in seed data
|
||||
{ name: "Nemo", slug: "nemo", website: "https://nemoequipment.com", country: "US", tier: 1 },
|
||||
{ name: "Therm-a-Rest", slug: "therm-a-rest", website: "https://thermarest.com", country: "US", tier: 1 },
|
||||
{ name: "Toaks", slug: "toaks", website: "https://toaksoutdoor.com", country: "CN", tier: 1 },
|
||||
{ name: "Katadyn", slug: "katadyn", website: "https://katadyn.com", country: "CH", tier: 1 },
|
||||
{ name: "HydraPak", slug: "hydrapak", website: "https://hydrapak.com", country: "US", tier: 1 },
|
||||
{ name: "Nitecore", slug: "nitecore", website: "https://nitecore.com", country: "CN", tier: 1 },
|
||||
{ name: "Outdoor Research", slug: "outdoor-research", website: "https://outdoorresearch.com", country: "US", tier: 1 },
|
||||
{ name: "Exposure Lights", slug: "exposure-lights", website: "https://exposurelights.com", country: "GB", tier: 1 },
|
||||
{
|
||||
name: "Nemo",
|
||||
slug: "nemo",
|
||||
website: "https://nemoequipment.com",
|
||||
country: "US",
|
||||
tier: 1,
|
||||
},
|
||||
{
|
||||
name: "Therm-a-Rest",
|
||||
slug: "therm-a-rest",
|
||||
website: "https://thermarest.com",
|
||||
country: "US",
|
||||
tier: 1,
|
||||
},
|
||||
{
|
||||
name: "Toaks",
|
||||
slug: "toaks",
|
||||
website: "https://toaksoutdoor.com",
|
||||
country: "CN",
|
||||
tier: 1,
|
||||
},
|
||||
{
|
||||
name: "Katadyn",
|
||||
slug: "katadyn",
|
||||
website: "https://katadyn.com",
|
||||
country: "CH",
|
||||
tier: 1,
|
||||
},
|
||||
{
|
||||
name: "HydraPak",
|
||||
slug: "hydrapak",
|
||||
website: "https://hydrapak.com",
|
||||
country: "US",
|
||||
tier: 1,
|
||||
},
|
||||
{
|
||||
name: "Nitecore",
|
||||
slug: "nitecore",
|
||||
website: "https://nitecore.com",
|
||||
country: "CN",
|
||||
tier: 1,
|
||||
},
|
||||
{
|
||||
name: "Outdoor Research",
|
||||
slug: "outdoor-research",
|
||||
website: "https://outdoorresearch.com",
|
||||
country: "US",
|
||||
tier: 1,
|
||||
},
|
||||
{
|
||||
name: "Exposure Lights",
|
||||
slug: "exposure-lights",
|
||||
website: "https://exposurelights.com",
|
||||
country: "GB",
|
||||
tier: 1,
|
||||
},
|
||||
];
|
||||
|
||||
const SEED_TAGS = [
|
||||
"bikepacking", "cycling", "hiking", "backpacking", "camping", "climbing",
|
||||
"mountaineering", "road-cycling", "gravel", "running", "trail-running",
|
||||
"handlebar-bag", "framebag", "saddlebag", "top-tube-bag", "stem-bag",
|
||||
"fork-bag", "feed-bag", "dry-bag", "stuff-sack", "bike-bag",
|
||||
"tent", "bivy", "tarp", "hammock",
|
||||
"sleeping-bag", "sleeping-pad", "quilt", "pillow",
|
||||
"stove", "cookware", "mug", "utensils",
|
||||
"water-filter", "water-bottle",
|
||||
"headlamp", "bike-light", "lantern",
|
||||
"gps", "bike-computer", "power-bank", "solar-panel",
|
||||
"multi-tool", "pump", "repair-kit", "lock",
|
||||
"rain-jacket", "base-layer", "gloves", "shoe",
|
||||
"bikepacking",
|
||||
"cycling",
|
||||
"hiking",
|
||||
"backpacking",
|
||||
"camping",
|
||||
"climbing",
|
||||
"mountaineering",
|
||||
"road-cycling",
|
||||
"gravel",
|
||||
"running",
|
||||
"trail-running",
|
||||
"handlebar-bag",
|
||||
"framebag",
|
||||
"saddlebag",
|
||||
"top-tube-bag",
|
||||
"stem-bag",
|
||||
"fork-bag",
|
||||
"feed-bag",
|
||||
"dry-bag",
|
||||
"stuff-sack",
|
||||
"bike-bag",
|
||||
"tent",
|
||||
"bivy",
|
||||
"tarp",
|
||||
"hammock",
|
||||
"sleeping-bag",
|
||||
"sleeping-pad",
|
||||
"quilt",
|
||||
"pillow",
|
||||
"stove",
|
||||
"cookware",
|
||||
"mug",
|
||||
"utensils",
|
||||
"water-filter",
|
||||
"water-bottle",
|
||||
"headlamp",
|
||||
"bike-light",
|
||||
"lantern",
|
||||
"gps",
|
||||
"bike-computer",
|
||||
"power-bank",
|
||||
"solar-panel",
|
||||
"multi-tool",
|
||||
"pump",
|
||||
"repair-kit",
|
||||
"lock",
|
||||
"rain-jacket",
|
||||
"base-layer",
|
||||
"gloves",
|
||||
"shoe",
|
||||
];
|
||||
|
||||
export async function seedManufacturers(db: Db = prodDb) {
|
||||
for (const m of SEED_MANUFACTURERS) {
|
||||
await db
|
||||
.insert(manufacturers)
|
||||
.values(m)
|
||||
.onConflictDoNothing();
|
||||
await db.insert(manufacturers).values(m).onConflictDoNothing();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,9 +18,9 @@ import { communityPriceRoutes } from "./routes/community-prices.ts";
|
||||
import { discoveryRoutes } from "./routes/discovery.ts";
|
||||
import { exchangeRateRoutes } from "./routes/exchange-rates.ts";
|
||||
import { globalItemRoutes } from "./routes/global-items.ts";
|
||||
import { manufacturerRoutes } from "./routes/manufacturers.ts";
|
||||
import { imageRoutes } from "./routes/images.ts";
|
||||
import { itemRoutes } from "./routes/items.ts";
|
||||
import { manufacturerRoutes } from "./routes/manufacturers.ts";
|
||||
import { marketPriceRoutes } from "./routes/market-prices.ts";
|
||||
import { oauthRoutes, wellKnownRoute } from "./routes/oauth.ts";
|
||||
import { onboardingRoutes } from "./routes/onboarding.ts";
|
||||
|
||||
@@ -22,10 +22,14 @@ function errorResult(message: string): ToolResult {
|
||||
}
|
||||
|
||||
const catalogItemInputSchema = {
|
||||
manufacturerSlug: z.string().describe("Manufacturer slug (e.g. 'revelate-designs', 'apidura')"),
|
||||
manufacturerSlug: z
|
||||
.string()
|
||||
.describe("Manufacturer slug (e.g. 'revelate-designs', 'apidura')"),
|
||||
model: z
|
||||
.string()
|
||||
.describe("Model name — combined with manufacturerSlug forms the unique identifier"),
|
||||
.describe(
|
||||
"Model name — combined with manufacturerSlug forms the unique identifier",
|
||||
),
|
||||
category: z
|
||||
.string()
|
||||
.optional()
|
||||
|
||||
@@ -31,7 +31,10 @@ app.post("/", zValidator("json", createManufacturerSchema), async (c) => {
|
||||
const manufacturer = await createManufacturer(db, data);
|
||||
return c.json(manufacturer, 201);
|
||||
} catch {
|
||||
return c.json({ error: "Manufacturer with this name or slug already exists" }, 409);
|
||||
return c.json(
|
||||
{ error: "Manufacturer with this name or slug already exists" },
|
||||
409,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { eq, sql } from "drizzle-orm";
|
||||
import type { db as prodDb } from "../../db/index.ts";
|
||||
import { categories, globalItems, items, manufacturers } from "../../db/schema.ts";
|
||||
import {
|
||||
categories,
|
||||
globalItems,
|
||||
items,
|
||||
manufacturers,
|
||||
} from "../../db/schema.ts";
|
||||
import { getOrCreateUncategorized } from "./category.service.ts";
|
||||
|
||||
type Db = typeof prodDb;
|
||||
|
||||
@@ -1,12 +1,21 @@
|
||||
import type { SQL } from "drizzle-orm";
|
||||
import { and, count, eq, ilike, or, sql } from "drizzle-orm";
|
||||
import { db as prodDb } from "../../db/index.ts";
|
||||
import { globalItems, globalItemTags, items, manufacturers, tags } from "../../db/schema.ts";
|
||||
import {
|
||||
globalItems,
|
||||
globalItemTags,
|
||||
items,
|
||||
manufacturers,
|
||||
tags,
|
||||
} from "../../db/schema.ts";
|
||||
|
||||
type Db = typeof prodDb;
|
||||
type TxDb = Parameters<Parameters<Db["transaction"]>[0]>[0];
|
||||
|
||||
async function resolveManufacturerId(db: Db | TxDb, slug: string): Promise<number> {
|
||||
async function resolveManufacturerId(
|
||||
db: Db | TxDb,
|
||||
slug: string,
|
||||
): Promise<number> {
|
||||
const [m] = await (db as Db)
|
||||
.select({ id: manufacturers.id })
|
||||
.from(manufacturers)
|
||||
@@ -26,7 +35,10 @@ export async function searchGlobalItems(
|
||||
const escaped = query.replace(/%/g, "\\%").replace(/_/g, "\\_");
|
||||
const pattern = `%${escaped}%`;
|
||||
conditions.push(
|
||||
or(ilike(manufacturers.name, pattern), ilike(globalItems.model, pattern))!,
|
||||
or(
|
||||
ilike(manufacturers.name, pattern),
|
||||
ilike(globalItems.model, pattern),
|
||||
)!,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -221,7 +233,10 @@ export async function bulkUpsertGlobalItems(
|
||||
const resultItems = [];
|
||||
|
||||
for (const data of itemsData) {
|
||||
const manufacturerId = await resolveManufacturerId(tx as unknown as Db, data.manufacturerSlug);
|
||||
const manufacturerId = await resolveManufacturerId(
|
||||
tx as unknown as Db,
|
||||
data.manufacturerSlug,
|
||||
);
|
||||
|
||||
const [existing] = await tx
|
||||
.select({ id: globalItems.id })
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { and, eq, sql } from "drizzle-orm";
|
||||
import type { db as prodDb } from "../../db/index.ts";
|
||||
import { categories, globalItems, items, manufacturers } from "../../db/schema.ts";
|
||||
import {
|
||||
categories,
|
||||
globalItems,
|
||||
items,
|
||||
manufacturers,
|
||||
} from "../../db/schema.ts";
|
||||
import type { CreateItem } from "../../shared/types.ts";
|
||||
|
||||
type Db = typeof prodDb;
|
||||
@@ -122,7 +127,10 @@ export async function createItem(
|
||||
const [gi] = await db
|
||||
.select({ name: manufacturers.name, model: globalItems.model })
|
||||
.from(globalItems)
|
||||
.innerJoin(manufacturers, eq(globalItems.manufacturerId, manufacturers.id))
|
||||
.innerJoin(
|
||||
manufacturers,
|
||||
eq(globalItems.manufacturerId, manufacturers.id),
|
||||
)
|
||||
.where(eq(globalItems.id, data.globalItemId));
|
||||
if (gi) {
|
||||
name = `${gi.name} ${gi.model}`;
|
||||
|
||||
@@ -371,7 +371,10 @@ export async function resolveThread(
|
||||
const [gi] = await tx
|
||||
.select({ name: manufacturers.name, model: globalItems.model })
|
||||
.from(globalItems)
|
||||
.innerJoin(manufacturers, eq(globalItems.manufacturerId, manufacturers.id))
|
||||
.innerJoin(
|
||||
manufacturers,
|
||||
eq(globalItems.manufacturerId, manufacturers.id),
|
||||
)
|
||||
.where(eq(globalItems.id, candidate.globalItemId));
|
||||
const fallbackName = gi ? `${gi.name} ${gi.model}` : candidate.name;
|
||||
insertValues = {
|
||||
|
||||
Reference in New Issue
Block a user