feat: add craftsman profile page with featured endpoint and full craftsmen list linking
- Add GET /api/craftsmen/featured endpoint returning top 6 by product count - Expand GET /api/craftsmen/:id to return all profile fields and last 12 products - New CraftsmanProfile.tsx page: header, bio/story, stats, extended details, products grid - Update CraftsmenList.tsx: category translations, years_of_practice, card links to /:id - Add /craftsmen/:id route to App.tsx Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -34,12 +34,41 @@ router.get('/', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// Featured craftsmen — top 6 by product count (must be BEFORE /:id to avoid conflict)
|
||||
router.get('/featured', async (req, res) => {
|
||||
try {
|
||||
const pool = getPool();
|
||||
const { rows } = await pool.query(
|
||||
`SELECT c.*, u.email, u.display_name as user_display_name,
|
||||
COUNT(p.id) AS product_count
|
||||
FROM craftsmen c
|
||||
JOIN users u ON c.user_id = u.id
|
||||
LEFT JOIN products p ON p.craftsman_id = c.id AND p.is_active = true
|
||||
WHERE c.is_active = true
|
||||
GROUP BY c.id, u.email, u.display_name
|
||||
ORDER BY product_count DESC
|
||||
LIMIT 6`
|
||||
);
|
||||
res.json(rows);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ error: 'Failed to fetch featured craftsmen' });
|
||||
}
|
||||
});
|
||||
|
||||
// Get craftsman by ID
|
||||
router.get('/:id', async (req, res) => {
|
||||
try {
|
||||
const pool = getPool();
|
||||
const { rows } = await pool.query(
|
||||
`SELECT c.*, u.email, u.display_name as user_display_name
|
||||
`SELECT c.id, c.shop_name, c.bio, c.story, c.location, c.prefecture,
|
||||
c.craft_region, c.regional_designation, c.guild_association,
|
||||
c.meti_certified, c.meti_certification_number, c.meti_craft_category,
|
||||
c.years_of_practice, c.apprenticeship_lineage, c.primary_materials,
|
||||
c.workshop_size, c.languages_spoken, c.production_method,
|
||||
c.craft_category, c.social_links, c.rating, c.review_count,
|
||||
c.is_active, c.profile_image_url,
|
||||
u.email, u.display_name as user_display_name
|
||||
FROM craftsmen c JOIN users u ON c.user_id = u.id
|
||||
WHERE c.id = $1 AND c.is_active = true`,
|
||||
[req.params.id]
|
||||
@@ -47,14 +76,15 @@ router.get('/:id', async (req, res) => {
|
||||
|
||||
if (rows.length === 0) return res.status(404).json({ error: 'Craftsman not found' });
|
||||
|
||||
// Get their products
|
||||
// Get their last 12 products
|
||||
const products = await pool.query(
|
||||
'SELECT * FROM products WHERE craftsman_id = $1 AND is_active = true ORDER BY created_at DESC LIMIT 10',
|
||||
'SELECT * FROM products WHERE craftsman_id = $1 AND is_active = true ORDER BY created_at DESC LIMIT 12',
|
||||
[req.params.id]
|
||||
);
|
||||
|
||||
res.json({ ...rows[0], products: products.rows });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ error: 'Failed to fetch craftsman' });
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user