Rename ssr to website, website to spa, add spa nginx config

This commit is contained in:
Your Name committed 2026-06-03 09:38:15 +07:00
1 parent fc6bf0218e
commit 8514342c2f
103 files changed
+801 -768

No files matched your search

+138
View File
@@ -0,0 +1,138 @@
import { useState, useEffect, useCallback } from 'react'
import { useSearchParams, Link } from 'react-router-dom'
import Navbar from '../components/Navbar'
import Footer from '../components/Footer'
import ProductCard from '../components/ProductCard'
import { API_BASE, type Category, type Product } from '../types'
export default function SearchPage() {
const [searchParams, setSearchParams] = useSearchParams()
const query = searchParams.get('q') ?? ''
const [input, setInput] = useState(query)
const [categories, setCategories] = useState<Category[]>([])
const [products, setProducts] = useState<Product[]>([])
const [loading, setLoading] = useState(false)
const [searched, setSearched] = useState(false)
useEffect(() => {
fetch(`${API_BASE}/categories`).then(r => r.json()).then(setCategories).catch(console.error)
}, [])
const doSearch = useCallback(async (q: string) => {
if (!q.trim()) { setProducts([]); setSearched(false); return }
setLoading(true)
setSearched(true)
try {
const res = await fetch(`${API_BASE}/products?search=${encodeURIComponent(q)}`)
const paginated = await res.json()
setProducts(paginated.data || [])
} catch (err) {
console.error(err)
} finally {
setLoading(false)
}
}, [])
// Search when URL query changes
useEffect(() => {
setInput(query)
doSearch(query)
}, [query, doSearch])
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
setSearchParams(input.trim() ? { q: input.trim() } : {})
}
return (
<div className="min-h-screen">
<Navbar categories={categories} />
{/* Search Header */}
<div className="pt-32 pb-16 px-6 bg-night-800/20 border-b border-white/5">
<div className="max-w-3xl mx-auto space-y-6 text-center">
<p className="text-cyan-glow uppercase tracking-[0.4em] text-xs font-semibold opacity-70">
Discover
</p>
<h1 className="text-5xl font-bold">Search Products</h1>
<form onSubmit={handleSubmit} className="relative">
<div className="flex glass border border-white/10 rounded-2xl overflow-hidden focus-within:border-cyan-glow/40 transition-colors">
<svg className="w-5 h-5 text-slate-400 m-4 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Search by scent name, category..."
className="bg-transparent border-none outline-none text-base p-4 pl-0 w-full text-white placeholder-slate-500"
autoFocus
/>
<button
type="submit"
className="bg-white text-night-900 px-8 font-bold text-xs uppercase tracking-widest hover:bg-cyan-glow transition-colors shrink-0"
>
Search
</button>
</div>
</form>
{/* Quick category links */}
<div className="flex flex-wrap justify-center gap-2 pt-2">
{categories.map((cat) => (
<Link
key={cat.id}
to={`/search?q=${encodeURIComponent(cat.name)}`}
className="glass px-4 py-1.5 rounded-full text-xs font-medium hover:border-cyan-glow/30 hover:text-cyan-glow transition-all duration-300"
>
{cat.name}
</Link>
))}
</div>
</div>
</div>
{/* Results */}
<div className="max-w-7xl mx-auto px-6 py-16 space-y-8">
{!searched && !query && (
<div className="text-center py-20 space-y-3 text-slate-500">
<p className="text-5xl">🔍</p>
<p className="text-lg text-slate-400">Type something to discover our collection</p>
</div>
)}
{searched && (
<p className="text-slate-400 text-sm">
{loading ? 'Searching...' : `${products.length} result${products.length !== 1 ? 's' : ''} for `}
{!loading && <span className="text-white font-semibold">"{query}"</span>}
</p>
)}
{loading ? (
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
{Array.from({ length: 6 }).map((_, i) => (
<div key={i} className="rounded-2xl bg-white/5 animate-pulse h-96" />
))}
</div>
) : searched && products.length === 0 ? (
<div className="text-center py-20 space-y-4">
<p className="text-5xl">🌿</p>
<p className="text-xl text-slate-400">No results found</p>
<p className="text-slate-500 text-sm">Try a different keyword or browse our categories</p>
<Link to="/shop" className="inline-block mt-4 px-8 py-3 bg-white/10 hover:bg-white hover:text-night-900 text-white font-bold rounded-lg uppercase tracking-widest text-xs transition-all duration-300">
Browse All Products
</Link>
</div>
) : (
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
{products.map((p) => <ProductCard key={p.id} product={p} />)}
</div>
)}
</div>
<Footer categories={categories} />
</div>
)
}