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:
2026-02-21 18:50:43 +00:00
parent 2fa526075e
commit ecc0d9ee83
4 changed files with 411 additions and 10 deletions

View File

@@ -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' });
}
});