feat: add React frontend - homepage, auth, products, craftsmen pages

- React 18 + Vite + TypeScript + TailwindCSS + shadcn/ui components
- Auth pages (login/register) with JWT token management via Zustand store
- Homepage with 9 craft category tiles (ceramics first, lacquerware second)
- METI 伝統的工芸品 badge and featured section on homepage
- Products page with category filters + METI認定 filter
- Craftsmen list page with METI badge display
- Navbar with auth-aware navigation
- Japanese warm color theme (amber/terracotta)
- API proxy config pointing to Express backend

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-21 18:19:30 +00:00
parent 6707c44d31
commit 2fa526075e
28 changed files with 1141 additions and 0 deletions

69
client/src/store/auth.ts Normal file
View File

@@ -0,0 +1,69 @@
import { create } from 'zustand'
import { api } from '@/lib/api'
interface User {
id: string
email: string
role: string
first_name?: string
last_name?: string
display_name?: string
avatar_url?: string
}
interface AuthState {
user: User | null
token: string | null
isLoading: boolean
login: (email: string, password: string) => Promise<void>
register: (data: { email: string; password: string; role?: string; display_name?: string }) => Promise<void>
logout: () => void
checkAuth: () => Promise<void>
}
export const useAuthStore = create<AuthState>((set) => ({
user: null,
token: localStorage.getItem('token'),
isLoading: false,
login: async (email, password) => {
set({ isLoading: true })
try {
const { user, token } = await api.post<{ user: User; token: string }>('/auth/login', { email, password })
localStorage.setItem('token', token)
set({ user, token, isLoading: false })
} catch (err) {
set({ isLoading: false })
throw err
}
},
register: async (data) => {
set({ isLoading: true })
try {
const { user, token } = await api.post<{ user: User; token: string }>('/auth/register', data)
localStorage.setItem('token', token)
set({ user, token, isLoading: false })
} catch (err) {
set({ isLoading: false })
throw err
}
},
logout: () => {
localStorage.removeItem('token')
set({ user: null, token: null })
},
checkAuth: async () => {
const token = localStorage.getItem('token')
if (!token) return
try {
const user = await api.get<User>('/auth/me')
set({ user })
} catch {
localStorage.removeItem('token')
set({ user: null, token: null })
}
},
}))