refactor: Cập nhật các thành phần UI để sử dụng các lớp Tailwind CSS ngữ nghĩa cho việc tạo chủ đề tốt hơn và xóa tài liệu thiết kế.
This commit is contained in:
@@ -129,7 +129,7 @@ export default function ForgotPasswordPage() {
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="text-white"
|
||||
className="text-text-primary"
|
||||
>
|
||||
<path
|
||||
d="M12 2L14.4 9.6H22L15.8 14.2L18.2 21.8L12 17.2L5.8 21.8L8.2 14.2L2 9.6H9.6L12 2Z"
|
||||
@@ -138,7 +138,7 @@ export default function ForgotPasswordPage() {
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<h1 className="text-4xl font-bold tracking-tight text-white mb-2">
|
||||
<h1 className="text-4xl font-bold tracking-tight text-text-primary mb-2">
|
||||
{t('auth.forgotPassword.title')}
|
||||
</h1>
|
||||
<p className="text-text-secondary">
|
||||
@@ -148,7 +148,7 @@ export default function ForgotPasswordPage() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="glass-card p-8 shadow-glass-xl border-glass-hover/20">
|
||||
<div className="glass-card p-8 shadow-glass-xl border-border-primary">
|
||||
{isSuccess ? (
|
||||
// EN: Success state - show confirmation message
|
||||
// VI: Trạng thái thành công - hiển thị thông báo xác nhận
|
||||
@@ -200,7 +200,7 @@ export default function ForgotPasswordPage() {
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className="w-full text-sm text-text-tertiary hover:text-white transition-colors"
|
||||
className="w-full text-sm text-text-tertiary hover:text-text-primary transition-colors"
|
||||
onClick={() => {
|
||||
setIsSuccess(false);
|
||||
setSubmittedEmail('');
|
||||
|
||||
@@ -129,7 +129,7 @@ export default function LoginPage() {
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="text-white"
|
||||
className="text-text-primary"
|
||||
>
|
||||
<path
|
||||
d="M12 2L14.4 9.6H22L15.8 14.2L18.2 21.8L12 17.2L5.8 21.8L8.2 14.2L2 9.6H9.6L12 2Z"
|
||||
@@ -138,13 +138,13 @@ export default function LoginPage() {
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<h1 className="text-4xl font-bold tracking-tight text-white mb-2">
|
||||
<h1 className="text-4xl font-bold tracking-tight text-text-primary mb-2">
|
||||
{t('auth.login.title')}
|
||||
</h1>
|
||||
<p className="text-text-secondary">{t('auth.login.description')}</p>
|
||||
</div>
|
||||
|
||||
<div className="glass-card p-8 shadow-glass-xl border-glass-hover/20">
|
||||
<div className="glass-card p-8 shadow-glass-xl border-border-primary">
|
||||
<form className="space-y-6" onSubmit={handleSubmit(onSubmit)}>
|
||||
{/* EN: API Error message display / VI: Hiển thị thông báo lỗi API */}
|
||||
{apiError && (
|
||||
|
||||
@@ -246,7 +246,7 @@ export default function RegisterPage() {
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="text-white"
|
||||
className="text-text-primary"
|
||||
>
|
||||
<path
|
||||
d="M12 2L14.4 9.6H22L15.8 14.2L18.2 21.8L12 17.2L5.8 21.8L8.2 14.2L2 9.6H9.6L12 2Z"
|
||||
@@ -255,7 +255,7 @@ export default function RegisterPage() {
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<h1 className="text-4xl font-bold tracking-tight text-white mb-2">
|
||||
<h1 className="text-4xl font-bold tracking-tight text-text-primary mb-2">
|
||||
{t('auth.register.createAccount')}
|
||||
</h1>
|
||||
<p className="text-text-secondary">
|
||||
@@ -263,7 +263,7 @@ export default function RegisterPage() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="glass-card p-8 shadow-glass-xl border-glass-hover/20">
|
||||
<div className="glass-card p-8 shadow-glass-xl border-border-primary">
|
||||
<form className="space-y-6" onSubmit={handleSubmit(onSubmit)}>
|
||||
{/* EN: API Error message display / VI: Hiển thị thông báo lỗi API */}
|
||||
{apiError && (
|
||||
|
||||
@@ -72,7 +72,7 @@ export default function Home() {
|
||||
/>
|
||||
|
||||
{/* Hero Section */}
|
||||
<main className="min-h-screen bg-black text-white">
|
||||
<main className="min-h-screen bg-bg-primary text-text-primary">
|
||||
<div className="container mx-auto px-6">
|
||||
{/* Hero Content */}
|
||||
<div className="flex flex-col items-center justify-center min-h-screen text-center space-y-12">
|
||||
@@ -84,17 +84,17 @@ export default function Home() {
|
||||
{/* Search Input */}
|
||||
<div className="max-w-2xl mx-auto">
|
||||
<div className="relative">
|
||||
<div className="absolute inset-0 bg-white/10 backdrop-blur-sm rounded-full border border-white/20" />
|
||||
<div className="absolute inset-0 bg-glass backdrop-blur-sm rounded-full border border-glass" />
|
||||
<div className="relative flex items-center">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="What do you want to know?"
|
||||
className="w-full px-6 py-4 bg-transparent text-white placeholder-white/60 rounded-full focus:outline-none text-lg"
|
||||
className="w-full px-6 py-4 bg-transparent text-text-primary placeholder:text-text-tertiary rounded-full focus:outline-none text-lg"
|
||||
/>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="absolute right-2 p-2 hover:bg-white/20 rounded-full"
|
||||
className="absolute right-2 p-2 hover:bg-glass-hover rounded-full"
|
||||
>
|
||||
<Search className="h-5 w-5" />
|
||||
</Button>
|
||||
@@ -108,31 +108,31 @@ export default function Home() {
|
||||
<section className="py-24">
|
||||
<div className="text-center mb-16">
|
||||
<div className="inline-flex items-center space-x-2 mb-4">
|
||||
<span className="text-white/60 text-sm font-medium">[</span>
|
||||
<span className="text-white text-sm font-medium">Products</span>
|
||||
<span className="text-white/60 text-sm font-medium">]</span>
|
||||
<span className="text-text-tertiary text-sm font-medium">[</span>
|
||||
<span className="text-text-secondary text-sm font-medium">Products</span>
|
||||
<span className="text-text-tertiary text-sm font-medium">]</span>
|
||||
</div>
|
||||
<h2 className="text-4xl md:text-5xl font-bold">AI for all humanity</h2>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||
{/* Grok Card */}
|
||||
<div className="bg-white/5 backdrop-blur-sm rounded-2xl border border-white/10 p-8 hover:bg-white/10 transition-all duration-300 group">
|
||||
<div className="bg-bg-secondary backdrop-blur-sm rounded-2xl border border-border-primary p-8 hover:bg-glass-hover transition-all duration-300 group">
|
||||
<div className="flex flex-col space-y-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-2xl font-bold">Grok</h3>
|
||||
<div className="w-12 h-12 bg-white/10 rounded-full flex items-center justify-center group-hover:bg-white/20 transition-colors">
|
||||
<div className="w-12 h-12 bg-glass-subtle rounded-full flex items-center justify-center group-hover:bg-glass-hover transition-colors">
|
||||
<ArrowRight className="h-5 w-5" />
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-white/80 leading-relaxed">
|
||||
<p className="text-text-secondary leading-relaxed">
|
||||
Grok is your cosmic guide, now accessible on grok.com, iOS, and Android. Explore the universe with AI.
|
||||
</p>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="text-white hover:bg-white/20"
|
||||
className="text-text-secondary hover:bg-glass-hover"
|
||||
onPress={() => window.location.href = '/chat'}
|
||||
>
|
||||
Use now
|
||||
@@ -142,22 +142,22 @@ export default function Home() {
|
||||
</div>
|
||||
|
||||
{/* API Card */}
|
||||
<div className="bg-white/5 backdrop-blur-sm rounded-2xl border border-white/10 p-8 hover:bg-white/10 transition-all duration-300 group">
|
||||
<div className="bg-bg-secondary backdrop-blur-sm rounded-2xl border border-border-primary p-8 hover:bg-glass-hover transition-all duration-300 group">
|
||||
<div className="flex flex-col space-y-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-2xl font-bold">API</h3>
|
||||
<div className="w-12 h-12 bg-white/10 rounded-full flex items-center justify-center group-hover:bg-white/20 transition-colors">
|
||||
<div className="w-12 h-12 bg-glass-subtle rounded-full flex items-center justify-center group-hover:bg-glass-hover transition-colors">
|
||||
<Code className="h-5 w-5" />
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-white/80 leading-relaxed">
|
||||
<p className="text-text-secondary leading-relaxed">
|
||||
Supercharge your applications with Grok's enhanced speed, precision, and multilingual capabilities.
|
||||
</p>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="text-white hover:bg-white/20"
|
||||
className="text-text-secondary hover:bg-glass-hover"
|
||||
onPress={() => window.location.href = '/api'}
|
||||
>
|
||||
Build now
|
||||
@@ -167,22 +167,22 @@ export default function Home() {
|
||||
</div>
|
||||
|
||||
{/* Developer Docs Card */}
|
||||
<div className="bg-white/5 backdrop-blur-sm rounded-2xl border border-white/10 p-8 hover:bg-white/10 transition-all duration-300 group">
|
||||
<div className="bg-bg-secondary backdrop-blur-sm rounded-2xl border border-border-primary p-8 hover:bg-glass-hover transition-all duration-300 group">
|
||||
<div className="flex flex-col space-y-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-2xl font-bold">Developer Docs</h3>
|
||||
<div className="w-12 h-12 bg-white/10 rounded-full flex items-center justify-center group-hover:bg-white/20 transition-colors">
|
||||
<div className="w-12 h-12 bg-glass-subtle rounded-full flex items-center justify-center group-hover:bg-glass-hover transition-colors">
|
||||
<Globe className="h-5 w-5" />
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-white/80 leading-relaxed">
|
||||
<p className="text-text-secondary leading-relaxed">
|
||||
Learn how to quickly install Grok at the heart of your applications and explore guides covering common use cases.
|
||||
</p>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="text-white hover:bg-white/20"
|
||||
className="text-text-secondary hover:bg-glass-hover"
|
||||
onPress={() => window.location.href = '/docs'}
|
||||
>
|
||||
Learn more
|
||||
@@ -197,16 +197,16 @@ export default function Home() {
|
||||
{isAuthenticated && user && (
|
||||
<section className="py-24">
|
||||
<div className="max-w-md mx-auto">
|
||||
<div className="bg-white/5 backdrop-blur-sm rounded-2xl border border-white/10 p-8 text-center">
|
||||
<div className="w-20 h-20 bg-white/10 rounded-2xl flex items-center justify-center mx-auto mb-6 text-3xl font-bold">
|
||||
<div className="bg-bg-secondary backdrop-blur-sm rounded-2xl border border-border-primary p-8 text-center">
|
||||
<div className="w-20 h-20 bg-glass-subtle rounded-2xl flex items-center justify-center mx-auto mb-6 text-3xl font-bold">
|
||||
{user.email?.[0].toUpperCase()}
|
||||
</div>
|
||||
<h3 className="text-xl font-bold mb-2">Welcome back</h3>
|
||||
<p className="text-white/60 mb-6">{user.email}</p>
|
||||
<p className="text-text-tertiary mb-6">{user.email}</p>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="text-white border border-white/20 hover:bg-white/10 w-full"
|
||||
className="text-text-secondary border border-border-primary hover:bg-glass-hover w-full"
|
||||
onPress={() => window.location.href = '/chat'}
|
||||
>
|
||||
Continue to Chat
|
||||
|
||||
@@ -82,7 +82,7 @@ export interface ChatInputProps {
|
||||
* ```
|
||||
*/
|
||||
export function ChatInput({
|
||||
value = '',
|
||||
value: externalValue,
|
||||
onChange,
|
||||
onSend,
|
||||
onAttachFile,
|
||||
@@ -99,9 +99,21 @@ export function ChatInput({
|
||||
// EN: Reference to textarea element for auto-resize / VI: Reference đến element textarea cho auto-resize
|
||||
const textareaRef = React.useRef<HTMLTextAreaElement>(null);
|
||||
|
||||
// EN: Internal state for uncontrolled mode / VI: State nội bộ cho chế độ uncontrolled
|
||||
const [internalValue, setInternalValue] = React.useState('');
|
||||
|
||||
// EN: Use external value if provided, otherwise use internal state / VI: Sử dụng external value nếu có, nếu không dùng internal state
|
||||
const value = externalValue !== undefined ? externalValue : internalValue;
|
||||
|
||||
// EN: Handle textarea input change / VI: Xử lý thay đổi input textarea
|
||||
const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
const newValue = e.target.value;
|
||||
|
||||
// EN: Update internal state if uncontrolled / VI: Cập nhật internal state nếu uncontrolled
|
||||
if (externalValue === undefined) {
|
||||
setInternalValue(newValue);
|
||||
}
|
||||
|
||||
onChange?.(newValue);
|
||||
|
||||
// EN: Auto-resize textarea / VI: Tự động thay đổi kích thước textarea
|
||||
@@ -131,6 +143,10 @@ export function ChatInput({
|
||||
if (textareaRef.current) {
|
||||
textareaRef.current.style.height = `${minHeight}px`;
|
||||
}
|
||||
// EN: Clear internal state if uncontrolled / VI: Xóa internal state nếu uncontrolled
|
||||
if (externalValue === undefined) {
|
||||
setInternalValue('');
|
||||
}
|
||||
// EN: Clear input value (expecting parent to handle this via onChange) / VI: Xóa giá trị input (kỳ vọng parent xử lý qua onChange)
|
||||
onChange?.('');
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ export function AnnouncementBanner({
|
||||
className,
|
||||
}: AnnouncementBannerProps) {
|
||||
return (
|
||||
<div className={`bg-black/40 backdrop-blur-sm border-b border-white/5 ${className}`}>
|
||||
<div className={`bg-bg-secondary/40 backdrop-blur-glass border-b border-border-primary ${className}`}>
|
||||
<div className="container mx-auto px-6">
|
||||
<div className="flex items-center justify-between py-3">
|
||||
<div className="flex items-center space-x-4">
|
||||
@@ -58,9 +58,9 @@ export function AnnouncementBanner({
|
||||
News
|
||||
</span>
|
||||
</div>
|
||||
<div className="hidden sm:block w-px h-4 bg-white/20" />
|
||||
<div className="hidden sm:block w-px h-4 bg-border-primary" />
|
||||
<div className="flex items-center space-x-3">
|
||||
<h3 className="text-sm font-medium text-white">
|
||||
<h3 className="text-sm font-medium text-text-primary">
|
||||
{title}
|
||||
</h3>
|
||||
<span className="text-xs text-text-secondary">
|
||||
@@ -72,7 +72,7 @@ export function AnnouncementBanner({
|
||||
<div className="flex items-center space-x-4">
|
||||
<a
|
||||
href={linkUrl}
|
||||
className="text-xs text-text-secondary hover:text-white transition-colors underline"
|
||||
className="text-xs text-text-secondary hover:text-text-primary transition-colors underline"
|
||||
>
|
||||
{linkText}
|
||||
</a>
|
||||
@@ -81,7 +81,7 @@ export function AnnouncementBanner({
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onPress={onClose}
|
||||
className="p-1 h-auto hover:bg-white/10"
|
||||
className="p-1 h-auto hover:bg-glass-hover"
|
||||
>
|
||||
<X className="h-3 w-3" />
|
||||
</Button>
|
||||
|
||||
@@ -52,8 +52,8 @@ export function NavigationHeader({ className }: { className?: string }) {
|
||||
return (
|
||||
<header
|
||||
className={cn(
|
||||
'sticky top-0 z-50 w-full border-b border-white/5',
|
||||
'bg-black/20 backdrop-blur-sm',
|
||||
'sticky top-0 z-50 w-full border-b border-border-primary',
|
||||
'bg-bg-primary/80 backdrop-blur-glass',
|
||||
'transition-all duration-normal',
|
||||
className
|
||||
)}
|
||||
@@ -70,7 +70,7 @@ export function NavigationHeader({ className }: { className?: string }) {
|
||||
<a
|
||||
key={item.name}
|
||||
href={item.href}
|
||||
className="text-text-secondary hover:text-white transition-colors text-sm font-medium"
|
||||
className="text-text-secondary hover:text-text-primary transition-colors text-sm font-medium"
|
||||
>
|
||||
{item.name}
|
||||
</a>
|
||||
@@ -85,7 +85,7 @@ export function NavigationHeader({ className }: { className?: string }) {
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onPress={() => window.location.href = '/chat'}
|
||||
className="text-white border border-white/20 hover:bg-white/10 transition-all"
|
||||
className="text-text-primary border border-border-primary hover:bg-glass-hover transition-all"
|
||||
>
|
||||
Try Grok
|
||||
</Button>
|
||||
@@ -93,15 +93,15 @@ export function NavigationHeader({ className }: { className?: string }) {
|
||||
|
||||
{/* EN: Mobile Menu Button / VI: Button menu mobile */}
|
||||
<button
|
||||
className="md:hidden p-2 hover:bg-white/5 rounded-md transition-colors"
|
||||
className="md:hidden p-2 hover:bg-glass-hover rounded-md transition-colors"
|
||||
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
|
||||
aria-label="Toggle mobile menu"
|
||||
aria-expanded={mobileMenuOpen}
|
||||
>
|
||||
{mobileMenuOpen ? (
|
||||
<X className="h-5 w-5 text-white" />
|
||||
<X className="h-5 w-5 text-text-primary" />
|
||||
) : (
|
||||
<Menu className="h-5 w-5 text-white" />
|
||||
<Menu className="h-5 w-5 text-text-primary" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
@@ -109,7 +109,7 @@ export function NavigationHeader({ className }: { className?: string }) {
|
||||
{/* EN: Mobile Menu / VI: Menu mobile */}
|
||||
{mobileMenuOpen && (
|
||||
<div
|
||||
className="md:hidden py-4 space-y-4 border-t border-white/5 animate-fadeIn"
|
||||
className="md:hidden py-4 space-y-4 border-t border-border-primary animate-fadeIn"
|
||||
role="navigation"
|
||||
aria-label="Mobile navigation"
|
||||
>
|
||||
@@ -118,7 +118,7 @@ export function NavigationHeader({ className }: { className?: string }) {
|
||||
<a
|
||||
key={item.name}
|
||||
href={item.href}
|
||||
className="text-text-secondary hover:text-white transition-colors text-sm font-medium py-2"
|
||||
className="text-text-secondary hover:text-text-primary transition-colors text-sm font-medium py-2"
|
||||
onClick={() => setMobileMenuOpen(false)}
|
||||
>
|
||||
{item.name}
|
||||
@@ -132,13 +132,13 @@ export function NavigationHeader({ className }: { className?: string }) {
|
||||
window.location.href = '/chat';
|
||||
setMobileMenuOpen(false);
|
||||
}}
|
||||
className="text-white border border-white/20 hover:bg-white/10 transition-all w-full mt-4"
|
||||
className="text-text-primary border border-border-primary hover:bg-glass-hover transition-all w-full mt-4"
|
||||
>
|
||||
Try Grok
|
||||
</Button>
|
||||
|
||||
{/* EN: Theme and Language Controls / VI: Controls theme và ngôn ngữ */}
|
||||
<div className="flex items-center gap-3 pt-4 border-t border-white/5">
|
||||
<div className="flex items-center gap-3 pt-4 border-t border-border-primary">
|
||||
<ThemeToggle />
|
||||
<LanguageSwitcher />
|
||||
</div>
|
||||
|
||||
@@ -147,7 +147,7 @@ export const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-8 w-8 p-0 hover:bg-white/10 text-text-tertiary"
|
||||
className="h-8 w-8 p-0 hover:bg-glass-hover text-text-tertiary"
|
||||
onPress={() => setShowPassword(!showPassword)}
|
||||
aria-label={showPassword ? 'Hide password' : 'Show password'}
|
||||
>
|
||||
|
||||
@@ -55,7 +55,7 @@ export function LanguageSwitcher() {
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="text-white border border-white/20 hover:bg-white/10 transition-all gap-2"
|
||||
className="text-text-primary border border-border-primary hover:bg-glass-hover transition-all gap-2"
|
||||
aria-label="Change language"
|
||||
>
|
||||
<span className="font-medium">{currentLanguage.code.toUpperCase()}</span>
|
||||
|
||||
@@ -57,7 +57,7 @@ export function ThemeToggle() {
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="text-white border border-white/20 hover:bg-white/10 transition-all"
|
||||
className="text-text-primary border border-border-primary hover:bg-glass-hover transition-all"
|
||||
aria-label="Toggle theme"
|
||||
>
|
||||
<CurrentIcon className="h-5 w-5" />
|
||||
|
||||
Reference in New Issue
Block a user