import React, { useState, useMemo, useEffect, useRef } from 'react'; import { createRoot } from 'react-dom/client'; // --- MOCK DATA (Think of this as our server's database) --- const mockDatabaseAlbums = [ { id: 1, name: { en: 'Stellar Drift', 'zh-CN': '星际漂移', 'zh-TW': '星際漂移' }, artist: 'Cosmic Rays', genre: 'electronic', region: 'western', price: 19.99, image: 'https://picsum.photos/seed/album1/400', description: { en: 'An interstellar journey through sound.', 'zh-CN': '一场穿越声音的星际之旅。', 'zh-TW': '一場穿越聲音的星際之旅。'} }, { id: 2, name: { en: 'Midnight City', 'zh-CN': '午夜都市', 'zh-TW': '午夜都市' }, artist: 'Urban Glow', genre: 'pop', region: 'western', price: 15.99, image: 'https://picsum.photos/seed/album2/400', description: { en: 'The pulse of the city that never sleeps.', 'zh-CN': '不夜城的脉搏。', 'zh-TW': '不夜城的脈搏。'} }, { id: 3, name: { en: 'Ocean Echoes', 'zh-CN': '海洋回响', 'zh-TW': '海洋回響' }, artist: 'Deep Blue', genre: 'ambient', region: 'classical', price: 22.50, image: 'https://picsum.photos/seed/album3/400', description: { en: 'Calm waves and deep-sea melodies.', 'zh-CN': '平静的海浪与深海的旋律。', 'zh-TW': '平靜的海浪與深海的旋律。'} }, { id: 4, name: { en: 'Concrete Soul', 'zh-CN': '混凝土灵魂', 'zh-TW': '混凝土靈魂' }, artist: 'The Pavements', genre: 'rock', region: 'western', price: 18.00, image: 'https://picsum.photos/seed/album4/400', description: { en: 'Raw energy from the heart of the streets.', 'zh-CN': '来自街头中心的原始能量。', 'zh-TW': '來自街頭中心的原始能量。'} }, { id: 5, name: { en: 'Ancient Rhymes', 'zh-CN': '古韵', 'zh-TW': '古韻' }, artist: 'Li Qing', genre: 'folk', region: 'chinese', price: 14.99, image: 'https://picsum.photos/seed/album5/400', description: { en: 'Acoustic tales from the Middle Kingdom.', 'zh-CN': '来自神州的木吉他故事。', 'zh-TW': '來自神州的木吉他故事。'} }, { id: 6, name: { en: 'Retrograde', 'zh-CN': '逆行', 'zh-TW': '逆行' }, artist: 'Galaxy Runners', genre: 'electronic', region: 'western', price: 21.00, image: 'https://picsum.photos/seed/album6/400', description: { en: 'Synthwave beats for a future past.', 'zh-CN': '为过去的未来而设的合成波节拍。', 'zh-TW': '為過去的未來而設的合成波節拍。'} }, { id: 7, name: { en: 'Shanghai Jazz', 'zh-CN': '上海爵士', 'zh-TW': '上海爵士' }, artist: 'The Peace Trio', genre: 'jazz', region: 'chinese', price: 16.50, image: 'https://picsum.photos/seed/album7/400', description: { en: 'Swinging tunes from the Pearl of the Orient.', 'zh-CN': '来自东方之珠的摇摆曲调。', 'zh-TW': '來自東方之珠的搖擺曲調。'} }, { id: 8, name: { en: 'Symphony No. 5', 'zh-CN': '第五交响曲', 'zh-TW': '第五交響曲' }, artist: 'Vienna Orchestra', genre: 'classical', region: 'classical', price: 25.00, image: 'https://picsum.photos/seed/album8/400', description: { en: 'A timeless masterpiece of classical music.', 'zh-CN': '永恒的古典音乐杰作。', 'zh-TW': '永恆的古典音樂傑作。'} }, ]; const genres = ['electronic', 'pop', 'ambient', 'rock', 'folk', 'jazz', 'classical']; const regions = ['chinese', 'western', 'classical']; // --- i18n LOCALIZATION --- const translations = { 'en': { companyName: 'Cherovin Entertainment', newArrivals: 'New Arrivals', discountZone: 'Discounts', allGenres: 'All Genres', allRegions: 'All Regions', searchPlaceholder: 'Search albums (EN/CN)...', login: 'Login', logout: 'Logout', addToCart: 'Add to Cart', wishlist: 'Make a Wish', liveChat: 'Live Chat', footerText: 'Cherovin Entertainment Technology Limited | 60 Hung To Road, Kwun Tong, Kowloon, Hong Kong', genres: { electronic: 'Electronic', pop: 'Pop', ambient: 'Ambient', rock: 'Rock', folk: 'Folk', jazz: 'Jazz', classical: 'Classical' }, regions: { chinese: 'Chinese', western: 'Western', classical: 'Classical' }, loading: 'Loading albums...', error: 'Could not load albums. Please try refreshing the page.', // Login Modal loginTitle: 'Login to Your Account', registerTitle: 'Create New Account', loginEmailPhone: 'Email', loginPasswordCode: 'Password', confirmPassword: 'Confirm Password', loginWithGoogle: 'Login with Google', toggleToRegister: "Don't have an account? Register", toggleToLogin: 'Already have an account? Login', registrationSuccess: 'Registration successful! Please log in.', loginFailed: 'Login failed. Check credentials.', passwordsDoNotMatch: 'Passwords do not match.', welcomeUser: 'Welcome, {username}', // Cart Sidebar shoppingCart: 'Shopping Cart', subtotal: 'Subtotal', shipping: 'Shipping', total: 'Total', proceedToCheckout: 'Proceed to Checkout', cartEmpty: 'Your cart is empty.', // Wishlist Modal wishlistTitle: 'Make a Wish', wishlistAlbumName: 'Album Name', wishlistArtist: 'Artist', wishlistYear: 'Release Year', submit: 'Submit', wishlistSuccess: 'Wish submitted! We will consider it.', submitting: 'Submitting...', // Chat Window chatTitle: 'Live Support', chatWelcome: 'Hello! How can we help you today?', chatPlaceholder: 'Type your message...', send: 'Send', // Checkout Modal checkoutTitle: 'Checkout', orderSummary: 'Order Summary', paymentDetails: 'Payment Details', payNow: 'Pay Now', paymentSuccess: 'Payment successful! Thank you for your order.', }, 'zh-CN': { companyName: '夕鳥娛樂', newArrivals: '专辑上新', discountZone: '折扣专区', allGenres: '所有流派', allRegions: '所有地区', searchPlaceholder: '搜索专辑(中/英)...', login: '登录', logout: '登出', addToCart: '加入购物车', wishlist: '专辑许愿', liveChat: '在线客服', footerText: '夕鳥娛樂科技股份有限公司 | 香港九龍觀塘鴻圖道60號', genres: { electronic: '电子', pop: '流行', ambient: '氛围', rock: '摇滚', folk: '民谣', jazz: '爵士', classical: '古典' }, regions: { chinese: '华语', western: '欧美', classical: '古典' }, loading: '专辑加载中...', error: '无法加载专辑,请刷新页面重试。', loginTitle: '登录您的账户', registerTitle: '创建新账户', loginEmailPhone: '邮箱', loginPasswordCode: '密码', confirmPassword: '确认密码', loginWithGoogle: '使用谷歌登录', toggleToRegister: '没有账户?点击注册', toggleToLogin: '已有账户?点击登录', registrationSuccess: '注册成功!请登录。', loginFailed: '登录失败,请检查账户信息。', passwordsDoNotMatch: '两次输入的密码不一致。', welcomeUser: '欢迎您, {username}', shoppingCart: '购物车', subtotal: '商品小计', shipping: '运费', total: '总计', proceedToCheckout: '前往结账', cartEmpty: '您的购物车是空的。', wishlistTitle: '专辑许愿', wishlistAlbumName: '专辑名称', wishlistArtist: '歌手', wishlistYear: '发行年份', submit: '提交', wishlistSuccess: '许愿成功!我们会考虑您的请求。', submitting: '提交中...', chatTitle: '在线客服', chatWelcome: '您好!请问有什么可以帮助您?', chatPlaceholder: '输入您的问题...', send: '发送', checkoutTitle: '结账', orderSummary: '订单摘要', paymentDetails: '支付详情', payNow: '立即支付', paymentSuccess: '支付成功!感谢您的订单。', }, 'zh-TW': { companyName: '夕鳥娛樂', newArrivals: '專輯上新', discountZone: '折扣專區', allGenres: '所有流派', allRegions: '所有地區', searchPlaceholder: '搜索專輯(中/英)...', login: '登錄', logout: '登出', addToCart: '加入購物車', wishlist: '專輯許願', liveChat: '在線客服', footerText: '夕鳥娛樂科技股份有限公司 | 香港九龍觀塘鴻圖道60號', genres: { electronic: '電子', pop: '流行', ambient: '氛圍', rock: '搖滾', folk: '民謠', jazz: '爵士', classical: '古典' }, regions: { chinese: '華語', western: '歐美', classical: '古典' }, loading: '專輯加載中...', error: '無法加載專輯,請刷新頁面重試。', loginTitle: '登錄您的賬戶', registerTitle: '創建新賬戶', loginEmailPhone: '郵箱', loginPasswordCode: '密碼', confirmPassword: '確認密碼', loginWithGoogle: '使用谷歌登錄', toggleToRegister: '沒有賬戶?點擊註冊', toggleToLogin: '已有賬戶?點擊登錄', registrationSuccess: '註冊成功!請登錄。', loginFailed: '登錄失敗,請檢查賬戶信息。', passwordsDoNotMatch: '兩次輸入的密碼不一致。', welcomeUser: '歡迎您, {username}', shoppingCart: '購物車', subtotal: '商品小計', shipping: '運費', total: '總計', proceedToCheckout: '前往結賬', cartEmpty: '您的購物車是空的。', wishlistTitle: '專輯許願', wishlistAlbumName: '專輯名稱', wishlistArtist: '歌手', wishlistYear: '發行年份', submit: '提交', wishlistSuccess: '許願成功!我們會考慮您的請求。', submitting: '提交中...', chatTitle: '在線客服', chatWelcome: '您好!請問有什麼可以幫助您?', chatPlaceholder: '輸入您的問題...', send: '發送', checkoutTitle: '結賬', orderSummary: '訂單摘要', paymentDetails: '支付詳情', payNow: '立即支付', paymentSuccess: '支付成功!感謝您的訂單。', } }; type Language = keyof typeof translations; type Album = typeof mockDatabaseAlbums[0]; type User = { id: string; email: string; name: string, token?: string }; // --- REAL API CLIENT --- // Use environment variable for the API base URL. // Fallback to localhost for local development. const API_BASE_URL = process.env.REACT_APP_API_URL || 'http://localhost:3001/api'; const apiClient = { fetchAlbums: async (): Promise => { // For now, we'll return mock data if the API fails, to keep the app running. // In a real app, you'd handle the error more gracefully. try { const response = await fetch(`${API_BASE_URL}/albums`); if (!response.ok) throw new Error('Network response was not ok'); return await response.json(); } catch (error) { console.warn("API fetch failed, falling back to mock data. Is your backend server running?"); return mockDatabaseAlbums; } }, register: async (email: string, pass: string): Promise<{ success: boolean }> => { const response = await fetch(`${API_BASE_URL}/auth/register`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, password: pass }), }); return response.json(); }, login: async (email: string, pass: string): Promise<{ success: boolean; user?: User }> => { const response = await fetch(`${API_BASE_URL}/auth/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, password: pass }), }); const data = await response.json(); if(data.success && data.user) { sessionStorage.setItem('auth_session', JSON.stringify(data.user)); } return data; }, logout: (): Promise => { sessionStorage.removeItem('auth_session'); return Promise.resolve(); }, checkSession: (): Promise => { const session = sessionStorage.getItem('auth_session'); return Promise.resolve(session ? JSON.parse(session) : null); }, submitWish: async (wish: { name: string; artist: string; year: string }): Promise<{ success: boolean }> => { const response = await fetch(`${API_BASE_URL}/wishlist`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(wish), }); return response.json(); }, submitPayment: async (cart: Album[]): Promise<{ success: boolean }> => { // In a real scenario, you'd send item IDs and quantities, and the backend would calculate the price. // The user's auth token would also be sent in the headers. const response = await fetch(`${API_BASE_URL}/checkout`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ cart }), }); return response.json(); }, }; // --- UTILITY --- const getTranslated = (obj: { [key in Language | string]: string }, lang: Language) => obj[lang]; // --- COMPONENTS --- const LoginModal = ({ isOpen, onClose, lang, onLoginSuccess, onRegisterSuccess }) => { const t = translations[lang]; const [mode, setMode] = useState<'login' | 'register'>('login'); const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [confirmPassword, setConfirmPassword] = useState(''); const [error, setError] = useState(''); const handleSubmit = async (e) => { e.preventDefault(); setError(''); if (mode === 'register') { if (password !== confirmPassword) { setError(t.passwordsDoNotMatch); return; } const res = await apiClient.register(email, password); if(res.success) { onRegisterSuccess(); setMode('login'); } } else { const res = await apiClient.login(email, password); if (res.success && res.user) { onLoginSuccess(res.user); } else { setError(t.loginFailed); } } }; return (
e.stopPropagation()}>

{mode === 'login' ? t.loginTitle : t.registerTitle}

setEmail(e.target.value)} required /> setPassword(e.target.value)} required /> {mode === 'register' && ( setConfirmPassword(e.target.value)} required /> )} {error &&

{error}

}
); }; const CartSidebar = ({ isOpen, onClose, cartItems, lang, onRemoveItem, onCheckout }) => { const t = translations[lang]; const subtotal = cartItems.reduce((sum, item) => sum + item.price, 0); const shipping = subtotal > 0 ? 5.00 : 0; const total = subtotal + shipping; return ( <>

{t.shoppingCart}

{cartItems.length === 0 ? (

{t.cartEmpty}

) : ( cartItems.map(item => (
{getTranslated(item.name,

{getTranslated(item.name, lang)}

{item.artist}

${item.price.toFixed(2)}
)) )}
{cartItems.length > 0 && (

{t.subtotal} ${subtotal.toFixed(2)}

{t.shipping} ${shipping.toFixed(2)}

{t.total} ${total.toFixed(2)}

)}
); }; const WishlistModal = ({ isOpen, onClose, lang }) => { const t = translations[lang]; const [name, setName] = useState(''); const [artist, setArtist] = useState(''); const [year, setYear] = useState(''); const [isSubmitting, setIsSubmitting] = useState(false); const [submitStatus, setSubmitStatus] = useState<'idle' | 'success'>('idle'); const handleSubmit = async (e) => { e.preventDefault(); setIsSubmitting(true); setSubmitStatus('idle'); await apiClient.submitWish({ name, artist, year }); setIsSubmitting(false); setSubmitStatus('success'); setName(''); setArtist(''); setYear(''); setTimeout(() => { onClose(); setSubmitStatus('idle'); }, 1500); }; return (
e.stopPropagation()}>

{t.wishlistTitle}

setName(e.target.value)} placeholder={t.wishlistAlbumName} required /> setArtist(e.target.value)} placeholder={t.wishlistArtist} required /> setYear(e.target.value)} placeholder={t.wishlistYear} /> {submitStatus === 'success' ? (

{t.wishlistSuccess}

) : ( )}
); }; const ChatWindow = ({ isOpen, onClose, lang }) => { const t = translations[lang]; const [messages, setMessages] = useState([{ sender: 'bot', text: t.chatWelcome }]); const [input, setInput] = useState(''); const messagesEndRef = useRef(null); useEffect(() => { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); }, [messages]); const handleSend = () => { if (input.trim() === '') return; const newMessages = [...messages, { sender: 'user', text: input }]; setMessages(newMessages); setInput(''); setTimeout(() => { setMessages(prev => [...prev, { sender: 'bot', text: 'Thanks for your message. An agent will be with you shortly.' }]); }, 1000); }; if (!isOpen) return null; return (

{t.chatTitle}

{messages.map((msg, index) => (

{msg.text}

))}
setInput(e.target.value)} onKeyPress={e => e.key === 'Enter' && handleSend()} />
); }; const CheckoutModal = ({ isOpen, onClose, cartItems, lang, onPaymentSuccess }) => { const t = translations[lang]; const subtotal = cartItems.reduce((sum, item) => sum + item.price, 0); const shipping = subtotal > 0 ? 5.00 : 0; const total = subtotal + shipping; const handlePayment = async () => { await apiClient.submitPayment(cartItems); alert(t.paymentSuccess); onPaymentSuccess(); } return (
e.stopPropagation()}>

{t.checkoutTitle}

{t.orderSummary}

{cartItems.map(item => (
{getTranslated(item.name, lang)} ${item.price.toFixed(2)}
))}
{t.subtotal}${subtotal.toFixed(2)}
{t.shipping}${shipping.toFixed(2)}
{t.total}${total.toFixed(2)}

{t.paymentDetails}

Stripe Payment Element would be here.
); } const Header = ({ lang, setLang, cartItemCount, onSearch, onGenreChange, onRegionChange, onLoginClick, onCartClick, currentUser, onLogout }) => { const t = translations[lang]; const languages: Language[] = ['zh-TW', 'en', 'zh-CN']; const handleLanguageChange = () => { const currentIndex = languages.indexOf(lang); const nextIndex = (currentIndex + 1) % languages.length; setLang(languages[nextIndex]); }; return (
{t.companyName}
{cartItemCount > 0 && {cartItemCount}}
{currentUser ? (
{t.welcomeUser.replace('{username}', currentUser.name)}
) : ( )}
); }; const ProductCard = ({ album, lang, onAddToCart }) => { const t = translations[lang]; return (
{getTranslated(album.name,

{getTranslated(album.name, lang)}

{album.artist}

{getTranslated(album.description, lang)}

${album.price.toFixed(2)}
); }; const FloatingButtons = ({ lang, onWishlistClick, onChatClick }) => { const t = translations[lang]; return (
); } const Footer = ({ lang }) => { const t = translations[lang]; return

{t.footerText}

; } const App = () => { const [lang, setLang] = useState('zh-TW'); const [currentUser, setCurrentUser] = useState(null); const [cart, setCart] = useState([]); const [searchQuery, setSearchQuery] = useState(''); const [selectedGenre, setSelectedGenre] = useState(''); const [selectedRegion, setSelectedRegion] = useState(''); const [albums, setAlbums] = useState([]); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); const [isLoginOpen, setIsLoginOpen] = useState(false); const [isCartOpen, setIsCartOpen] = useState(false); const [isWishlistOpen, setIsWishlistOpen] = useState(false); const [isChatOpen, setIsChatOpen] = useState(false); const [isCheckoutOpen, setIsCheckoutOpen] = useState(false); const t = translations[lang]; useEffect(() => { apiClient.fetchAlbums().then(setAlbums).catch(err => setError(t.error)).finally(() => setIsLoading(false)); apiClient.checkSession().then(user => { if(user) setCurrentUser(user); }); }, [t.error]); useEffect(() => { const isModalOpen = isLoginOpen || isCartOpen || isWishlistOpen || isCheckoutOpen; if (isModalOpen) { document.body.style.overflow = 'hidden'; } else { document.body.style.overflow = 'auto'; } return () => { document.body.style.overflow = 'auto'; }; }, [isLoginOpen, isCartOpen, isWishlistOpen, isCheckoutOpen]); const handleAddToCart = (album: Album) => { if (!cart.find(item => item.id === album.id)) { setCart(prevCart => [...prevCart, album]); setIsCartOpen(true); } }; const handleRemoveFromCart = (albumId: number) => { setCart(prevCart => prevCart.filter(item => item.id !== albumId)); } const handleLoginSuccess = (user: User) => { setCurrentUser(user); setIsLoginOpen(false); }; const handleRegisterSuccess = () => { alert(t.registrationSuccess); }; const handleLogout = async () => { await apiClient.logout(); setCurrentUser(null); } const handleProceedToCheckout = () => { setIsCartOpen(false); setIsCheckoutOpen(true); } const handlePaymentSuccess = () => { setCart([]); setIsCheckoutOpen(false); } const filteredAlbums = useMemo(() => { return albums.filter(album => { const query = searchQuery.toLowerCase().trim(); if (!query) { // If search is empty, just filter by genre/region const matchesGenre = selectedGenre ? album.genre === selectedGenre : true; const matchesRegion = selectedRegion ? album.region === selectedRegion : true; return matchesGenre && matchesRegion; } const artist = album.artist.toLowerCase(); const nameInEN = album.name.en.toLowerCase(); const nameInCN = album.name['zh-CN'].toLowerCase(); const nameInTW = album.name['zh-TW'].toLowerCase(); const matchesSearch = artist.includes(query) || nameInEN.includes(query) || nameInCN.includes(query) || nameInTW.includes(query); const matchesGenre = selectedGenre ? album.genre === selectedGenre : true; const matchesRegion = selectedRegion ? album.region === selectedRegion : true; return matchesSearch && matchesGenre && matchesRegion; }); }, [albums, searchQuery, selectedGenre, selectedRegion]); if (isLoading) return
{t.loading}
; if (error) return
{error}
; return ( <>
setIsLoginOpen(true)} onCartClick={() => setIsCartOpen(true)} currentUser={currentUser} onLogout={handleLogout} />
{filteredAlbums.map(album => ( ))}
setIsWishlistOpen(true)} onChatClick={() => setIsChatOpen(!isChatOpen)} />