feat: add product catalog with filters, product detail page with AR support, enhanced homepage
- Backend: add GET /api/products/search (full-text) and GET /api/products/featured endpoints; update GET /:id to include related_products (same category, limit 4) - Products.tsx: full rewrite with sidebar filters (METI toggle prominent first, category buttons, price bands), sort dropdown, 12-per-page pagination, search bar calling /search endpoint, empty state, responsive 1/2/3 col grid - ProductDetail.tsx: new page with image, name/name_ja, price, METI badge, craftsman link, description, AR button (amber, only when ar_model_url + ar_eligible=true), ar_ineligible_reason badge, add-to-cart (login-gated), related products grid (max 4), reviews section (max 5, star rating) - App.tsx: add /products/:id route for ProductDetail - Home.tsx: category tiles already link to /products?category=<key>; add New Arrivals section fetching /api/products/featured showing 4-col product card grid Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -45,7 +45,50 @@ router.get('/', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// Get product by ID
|
||||
// Full-text search
|
||||
router.get('/search', async (req, res) => {
|
||||
try {
|
||||
const pool = getPool();
|
||||
const { q } = req.query;
|
||||
if (!q || q.trim() === '') {
|
||||
return res.json([]);
|
||||
}
|
||||
const searchTerm = `%${q.trim()}%`;
|
||||
const { rows } = await pool.query(
|
||||
`SELECT p.*, c.shop_name, c.meti_certified as craftsman_meti, c.prefecture
|
||||
FROM products p JOIN craftsmen c ON p.craftsman_id = c.id
|
||||
WHERE p.is_active = true
|
||||
AND (p.name ILIKE $1 OR COALESCE(p.name_ja,'') ILIKE $1 OR COALESCE(p.description,'') ILIKE $1)
|
||||
ORDER BY p.is_featured DESC, p.created_at DESC
|
||||
LIMIT 20`,
|
||||
[searchTerm]
|
||||
);
|
||||
res.json(rows);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ error: 'Failed to search products' });
|
||||
}
|
||||
});
|
||||
|
||||
// Featured products
|
||||
router.get('/featured', async (req, res) => {
|
||||
try {
|
||||
const pool = getPool();
|
||||
const { rows } = await pool.query(
|
||||
`SELECT p.*, c.shop_name, c.meti_certified as craftsman_meti, c.prefecture
|
||||
FROM products p JOIN craftsmen c ON p.craftsman_id = c.id
|
||||
WHERE p.is_active = true
|
||||
ORDER BY p.is_featured DESC, p.created_at DESC
|
||||
LIMIT 8`
|
||||
);
|
||||
res.json(rows);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ error: 'Failed to fetch featured products' });
|
||||
}
|
||||
});
|
||||
|
||||
// Get product by ID (with related products)
|
||||
router.get('/:id', async (req, res) => {
|
||||
try {
|
||||
const pool = getPool();
|
||||
@@ -58,6 +101,8 @@ router.get('/:id', async (req, res) => {
|
||||
|
||||
if (rows.length === 0) return res.status(404).json({ error: 'Product not found' });
|
||||
|
||||
const product = rows[0];
|
||||
|
||||
// Get reviews
|
||||
const reviews = await pool.query(
|
||||
`SELECT r.*, u.display_name FROM reviews r
|
||||
@@ -65,9 +110,22 @@ router.get('/:id', async (req, res) => {
|
||||
WHERE r.product_id = $1 ORDER BY r.created_at DESC LIMIT 10`,
|
||||
[req.params.id]
|
||||
);
|
||||
|
||||
// Get related products (same craft_category, different product, limit 4)
|
||||
const related = await pool.query(
|
||||
`SELECT p.*, c.shop_name, c.meti_certified as craftsman_meti, c.prefecture
|
||||
FROM products p JOIN craftsmen c ON p.craftsman_id = c.id
|
||||
WHERE p.is_active = true
|
||||
AND p.craft_category = $1
|
||||
AND p.id != $2
|
||||
ORDER BY p.is_featured DESC, p.created_at DESC
|
||||
LIMIT 4`,
|
||||
[product.craft_category, req.params.id]
|
||||
);
|
||||
|
||||
res.json({ ...rows[0], reviews: reviews.rows });
|
||||
res.json({ ...product, reviews: reviews.rows, related_products: related.rows });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ error: 'Failed to fetch product' });
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user