Update dependencies and enhance Tailwind CSS configuration for web applications

- Added new dependencies including clsx, lucide-react, recharts, and various Radix UI components to improve UI functionality.
- Upgraded Tailwind CSS to version 4.0.0 and updated configuration to utilize CSS variables for theming and responsive design.
- Introduced global styles and improved accessibility features in the layout and components.
- Removed outdated login page and refactored authentication store for better state management.
- Enhanced API service with additional authentication methods and improved error handling.

These changes aim to modernize the web applications and improve user experience through better design and functionality.
This commit is contained in:
Ho Ngoc Hai
2026-01-02 09:41:40 +07:00
parent af303eaf7b
commit c088de53c3
130 changed files with 20618 additions and 415 deletions

View File

@@ -0,0 +1,149 @@
'use client';
import * as React from 'react';
import { AnalyticsCard } from '@/components/admin/analytics-card';
import { RecentActivityTable, type RecentActivity } from '@/components/admin/recent-activity-table';
// EN: Lazy load heavy chart components / VI: Lazy load các component chart nặng
const UserGrowthChart = React.lazy(() => import('@/components/admin/charts/user-growth-chart').then(m => ({ default: m.UserGrowthChart })));
const RevenueChart = React.lazy(() => import('@/components/admin/charts/revenue-chart').then(m => ({ default: m.RevenueChart })));
import { Users, MessageSquare, TrendingUp, DollarSign } from 'lucide-react';
/**
* EN: Dashboard overview page component
* VI: Component trang tổng quan Dashboard
*
* Features:
* - Metrics row with key statistics
* - Charts row (User Growth, Revenue)
* - Recent activity table
*
* Tính năng:
* - Hàng metrics với các thống kê chính
* - Hàng charts (Tăng trưởng người dùng, Doanh thu)
* - Bảng hoạt động gần đây
*/
export default function DashboardPage() {
// EN: Mock data - replace with actual API calls / VI: Dữ liệu mock - thay thế bằng API calls thực tế
const [activities] = React.useState<RecentActivity[]>([
{
id: '1',
user: {
id: 'user1',
name: 'John Doe',
email: 'john@example.com',
},
action: 'user_created',
description: 'Created new user account',
status: 'success',
timestamp: new Date(Date.now() - 1000 * 60 * 5), // 5 minutes ago
},
{
id: '2',
user: {
id: 'user2',
name: 'Jane Smith',
email: 'jane@example.com',
},
action: 'message_sent',
description: 'Sent message in conversation',
status: 'info',
timestamp: new Date(Date.now() - 1000 * 60 * 15), // 15 minutes ago
},
]);
return (
<div className="space-y-6">
{/* EN: Page header / VI: Header trang */}
<div>
<h1 className="text-3xl font-bold text-text-primary">
Dashboard / Bảng điều khiển
</h1>
<p className="mt-1 text-sm text-text-tertiary">
Overview of your platform metrics and activities / Tổng quan về các metric hoạt đng của nền tảng
</p>
</div>
{/* EN: Metrics row / VI: Hàng metrics */}
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
<AnalyticsCard
title="Total Users / Tổng người dùng"
value={12543}
change="+12.5%"
trend="up"
icon={Users}
variant="metric"
/>
<AnalyticsCard
title="Messages / Tin nhắn"
value={89234}
change="+8.2%"
trend="up"
icon={MessageSquare}
variant="metric"
/>
<AnalyticsCard
title="Active Users / Người dùng hoạt động"
value={3421}
change="+5.1%"
trend="up"
icon={TrendingUp}
variant="metric"
/>
<AnalyticsCard
title="Revenue / Doanh thu"
value="$45,231"
change="-2.3%"
trend="down"
icon={DollarSign}
variant="metric"
/>
</div>
{/* EN: Charts row / VI: Hàng charts */}
{/* EN: Tablet: Side-by-side charts / VI: Tablet: Charts cạnh nhau */}
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 sm:gap-6 lg:grid-cols-2">
{/* EN: User Growth Chart / VI: User Growth Chart */}
<React.Suspense fallback={<div className="h-[300px] bg-bg-secondary rounded-lg border border-border-primary flex items-center justify-center"><div className="text-text-tertiary">Loading chart... / Đang tải chart...</div></div>}>
<UserGrowthChart
data={[
{ date: 'Jan', users: 1000, newUsers: 150 },
{ date: 'Feb', users: 1200, newUsers: 200 },
{ date: 'Mar', users: 1450, newUsers: 250 },
{ date: 'Apr', users: 1700, newUsers: 250 },
{ date: 'May', users: 1950, newUsers: 250 },
{ date: 'Jun', users: 2200, newUsers: 250 },
]}
title="User Growth / Tăng trưởng người dùng"
description="Monthly user growth trend / Xu hướng tăng trưởng người dùng hàng tháng"
/>
</React.Suspense>
{/* EN: Revenue Chart / VI: Revenue Chart */}
<React.Suspense fallback={<div className="h-[300px] bg-bg-secondary rounded-lg border border-border-primary flex items-center justify-center"><div className="text-text-tertiary">Loading chart... / Đang tải chart...</div></div>}>
<RevenueChart
data={[
{ date: 'Jan', revenue: 45000, previousRevenue: 40000 },
{ date: 'Feb', revenue: 52000, previousRevenue: 45000 },
{ date: 'Mar', revenue: 48000, previousRevenue: 52000 },
{ date: 'Apr', revenue: 55000, previousRevenue: 48000 },
{ date: 'May', revenue: 60000, previousRevenue: 55000 },
{ date: 'Jun', revenue: 65000, previousRevenue: 60000 },
]}
title="Revenue / Doanh thu"
description="Monthly revenue trend / Xu hướng doanh thu hàng tháng"
currency="$"
/>
</React.Suspense>
</div>
{/* EN: Recent Activity Table / VI: Bảng hoạt động gần đây */}
<RecentActivityTable
activities={activities}
currentPage={1}
itemsPerPage={10}
onPageChange={(page) => console.log('Page changed:', page)}
onQuickAction={(id, action) => console.log('Action:', id, action)}
/>
</div>
);
}

View File

@@ -0,0 +1,217 @@
'use client';
import * as React from 'react';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import { cn } from '@/lib/utils';
import {
LayoutDashboard,
Users,
BarChart3,
MessageSquare,
Settings,
LogOut,
Menu,
X,
} from 'lucide-react';
import { useAuthStore } from '@/stores/auth.store';
/**
* EN: Admin navigation items configuration
* VI: Cấu hình các mục điều hướng Admin
*/
const adminNavItems = [
{
id: 'dashboard',
label: 'Dashboard / Bảng điều khiển',
href: '/dashboard',
icon: LayoutDashboard,
},
{
id: 'users',
label: 'Users / Người dùng',
href: '/dashboard/users',
icon: Users,
},
{
id: 'analytics',
label: 'Analytics / Phân tích',
href: '/dashboard/analytics',
icon: BarChart3,
},
{
id: 'messages',
label: 'Messages / Tin nhắn',
href: '/dashboard/messages',
icon: MessageSquare,
},
{
id: 'settings',
label: 'Settings / Cài đặt',
href: '/dashboard/settings',
icon: Settings,
},
];
/**
* EN: Admin Dashboard layout component with sidebar navigation
* VI: Component layout Admin Dashboard với điều hướng sidebar
*
* Features:
* - Sidebar navigation with icons
* - Responsive design (collapsible on mobile)
* - User profile section
* - Logout functionality
* - Active route highlighting
*
* Tính năng:
* - Điều hướng sidebar với icons
* - Responsive design (có thể thu gọn trên mobile)
* - Phần profile người dùng
* - Chức năng logout
* - Highlight route đang active
*/
export default function AdminDashboardLayout({
children,
}: {
children: React.ReactNode;
}) {
const pathname = usePathname();
const { user, logout } = useAuthStore();
const [sidebarOpen, setSidebarOpen] = React.useState(false);
/**
* EN: Handle logout
* VI: Xử lý logout
*/
const handleLogout = async () => {
await logout();
window.location.href = '/login';
};
return (
<div className="flex h-screen w-full overflow-hidden bg-bg-primary">
{/* EN: Mobile sidebar overlay / VI: Overlay sidebar mobile */}
{sidebarOpen && (
<div
className="fixed inset-0 bg-black/50 z-40 md:hidden"
onClick={() => setSidebarOpen(false)}
aria-hidden="true"
/>
)}
{/* EN: Sidebar / VI: Sidebar */}
<aside
className={cn(
// EN: Base sidebar styles / VI: Style sidebar cơ bản
'flex flex-col bg-bg-secondary border-r border-border-primary transition-all duration-[250ms] ease-out',
// EN: Desktop: Fixed width / VI: Desktop: Chiều rộng cố định
'w-64 flex-shrink-0',
// EN: Mobile: Fixed position, slide in/out / VI: Mobile: Vị trí cố định, trượt vào/ra
'max-md:fixed max-md:inset-y-0 max-md:left-0 max-md:z-50',
'max-md:transform max-md:transition-transform',
sidebarOpen ? 'max-md:translate-x-0' : 'max-md:-translate-x-full'
)}
aria-label="Admin sidebar / Sidebar Admin"
>
{/* EN: Sidebar header with mobile menu button / VI: Header sidebar với nút menu mobile */}
<div className="flex items-center justify-between p-4 border-b border-border-primary">
<h1 className="text-xl font-semibold text-text-primary">
GoodGo Admin / Quản trị GoodGo
</h1>
<button
onClick={() => setSidebarOpen(false)}
className="md:hidden p-2 rounded-md hover:bg-bg-tertiary transition-colors"
aria-label="Close sidebar / Đóng sidebar"
>
<X className="h-5 w-5 text-text-secondary" />
</button>
</div>
{/* EN: Navigation menu / VI: Menu điều hướng */}
<nav className="flex-1 overflow-y-auto p-4 space-y-1">
{adminNavItems.map((item) => {
const Icon = item.icon;
const isActive = pathname === item.href || pathname.startsWith(item.href + '/');
return (
<Link
key={item.id}
href={item.href}
onClick={() => setSidebarOpen(false)}
className={cn(
// EN: Base nav item styles / VI: Style nav item cơ bản
'flex items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium transition-all duration-[150ms]',
// EN: Active state / VI: Trạng thái active
isActive
? 'bg-accent-primary text-white shadow-md'
: 'text-text-secondary hover:bg-bg-tertiary hover:text-text-primary',
)}
aria-current={isActive ? 'page' : undefined}
>
<Icon
className={cn(
'h-5 w-5 flex-shrink-0',
isActive ? 'text-white' : 'text-text-tertiary',
)}
aria-hidden="true"
/>
<span>{item.label}</span>
</Link>
);
})}
</nav>
{/* EN: User profile section / VI: Phần profile người dùng */}
{user && (
<div className="p-4 border-t border-border-primary">
<div className="flex items-center gap-3 mb-3">
<div className="h-10 w-10 rounded-full bg-chat-ai-bubble flex items-center justify-center">
<span className="text-sm font-medium text-text-primary">
{user.email?.charAt(0).toUpperCase() || 'A'}
</span>
</div>
<div className="flex-1 min-w-0">
<p className="text-sm font-medium text-text-primary truncate">
{user.email}
</p>
<p className="text-xs text-text-tertiary truncate">
{user.role || 'Admin / Quản trị viên'}
</p>
</div>
</div>
<button
onClick={handleLogout}
className="w-full flex items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium text-text-secondary hover:bg-bg-tertiary hover:text-text-primary transition-all duration-[150ms]"
aria-label="Logout / Đăng xuất"
>
<LogOut className="h-5 w-5 flex-shrink-0" aria-hidden="true" />
<span>Logout / Đăng xuất</span>
</button>
</div>
)}
</aside>
{/* EN: Main content area / VI: Khu vực nội dung chính */}
<main className="flex-1 overflow-y-auto">
{/* EN: Mobile header with menu button / VI: Header mobile với nút menu */}
<header className="sticky top-0 z-30 flex items-center justify-between p-4 bg-bg-secondary border-b border-border-primary md:hidden">
<button
onClick={() => setSidebarOpen(true)}
className="p-2 rounded-md hover:bg-bg-tertiary transition-colors"
aria-label="Open sidebar / Mở sidebar"
>
<Menu className="h-5 w-5 text-text-secondary" />
</button>
<h1 className="text-lg font-semibold text-text-primary">
GoodGo Admin / Quản trị GoodGo
</h1>
<div className="w-9" /> {/* EN: Spacer for centering / VI: Khoảng trống để căn giữa */}
</header>
{/* EN: Page content / VI: Nội dung trang */}
<div className="p-6">{children}</div>
</main>
</div>
);
}

View File

@@ -0,0 +1,103 @@
'use client';
import * as React from 'react';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
// EN: Lazy load settings forms / VI: Lazy load các form cài đặt
const GeneralSettings = React.lazy(() => import('@/components/admin/settings/general-settings').then(m => ({ default: m.GeneralSettings })));
const EmailSettings = React.lazy(() => import('@/components/admin/settings/email-settings').then(m => ({ default: m.EmailSettings })));
const SecuritySettings = React.lazy(() => import('@/components/admin/settings/security-settings').then(m => ({ default: m.SecuritySettings })));
/**
* EN: System Settings page component
* VI: Component trang cài đặt hệ thống
*
* Features:
* - Tab navigation (General, Email, Security, API, Advanced)
* - Settings forms for each category
*
* Tính năng:
* - Điều hướng tab (General, Email, Security, API, Advanced)
* - Forms cài đặt cho mỗi danh mục
*/
export default function SystemSettingsPage() {
return (
<div className="space-y-6">
{/* EN: Page header / VI: Header trang */}
<div>
<h1 className="text-3xl font-bold text-text-primary">
System Settings / Cài đt hệ thống
</h1>
<p className="mt-1 text-sm text-text-tertiary">
Configure system-wide settings and preferences / Cấu hình cài đt tùy chọn toàn hệ thống
</p>
</div>
{/* EN: Settings tabs / VI: Tabs cài đặt */}
<Tabs defaultValue="general" className="w-full">
<TabsList className="grid w-full grid-cols-5">
<TabsTrigger value="general">General / Chung</TabsTrigger>
<TabsTrigger value="email">Email / Email</TabsTrigger>
<TabsTrigger value="security">Security / Bảo mật</TabsTrigger>
<TabsTrigger value="api">API / API</TabsTrigger>
<TabsTrigger value="advanced">Advanced / Nâng cao</TabsTrigger>
</TabsList>
{/* EN: General settings tab / VI: Tab cài đặt chung */}
<TabsContent value="general" className="space-y-6">
<React.Suspense fallback={<div className="p-8 text-center"><div className="inline-block h-8 w-8 animate-spin rounded-full border-4 border-solid border-accent-primary border-r-transparent" /><p className="mt-4 text-sm text-text-tertiary">Loading... / Đang tải...</p></div>}>
<GeneralSettings />
</React.Suspense>
</TabsContent>
{/* EN: Email settings tab / VI: Tab cài đặt email */}
<TabsContent value="email" className="space-y-6">
<React.Suspense fallback={<div className="p-8 text-center"><div className="inline-block h-8 w-8 animate-spin rounded-full border-4 border-solid border-accent-primary border-r-transparent" /><p className="mt-4 text-sm text-text-tertiary">Loading... / Đang tải...</p></div>}>
<EmailSettings />
</React.Suspense>
</TabsContent>
{/* EN: Security settings tab / VI: Tab cài đặt bảo mật */}
<TabsContent value="security" className="space-y-6">
<React.Suspense fallback={<div className="p-8 text-center"><div className="inline-block h-8 w-8 animate-spin rounded-full border-4 border-solid border-accent-primary border-r-transparent" /><p className="mt-4 text-sm text-text-tertiary">Loading... / Đang tải...</p></div>}>
<SecuritySettings />
</React.Suspense>
</TabsContent>
{/* EN: API settings tab / VI: Tab cài đặt API */}
<TabsContent value="api" className="space-y-6">
<Card>
<CardHeader>
<CardTitle>API Settings / Cài đt API</CardTitle>
<CardDescription>
Configure API settings and webhooks / Cấu hình cài đt API webhooks
</CardDescription>
</CardHeader>
<CardContent>
<p className="text-sm text-text-tertiary">
API settings form will be implemented here / Form cài đt API sẽ đưc implement đây
</p>
</CardContent>
</Card>
</TabsContent>
{/* EN: Advanced settings tab / VI: Tab cài đặt nâng cao */}
<TabsContent value="advanced" className="space-y-6">
<Card>
<CardHeader>
<CardTitle>Advanced Settings / Cài đt nâng cao</CardTitle>
<CardDescription>
Advanced system configuration / Cấu hình hệ thống nâng cao
</CardDescription>
</CardHeader>
<CardContent>
<p className="text-sm text-text-tertiary">
Advanced settings form will be implemented here / Form cài đt nâng cao sẽ đưc implement đây
</p>
</CardContent>
</Card>
</TabsContent>
</Tabs>
</div>
);
}

View File

@@ -0,0 +1,139 @@
'use client';
import * as React from 'react';
import { Input } from '@/components/ui/input';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
import { DataTable, type DataTableColumn } from '@/components/admin/data-table';
// EN: Lazy load modal component / VI: Lazy load component modal
const UserDetailsModal = React.lazy(() => import('@/components/admin/user-details-modal').then(m => ({ default: m.UserDetailsModal })));
import type { User } from '@/components/admin/user-details-modal';
import { Search, Filter, Download, MoreVertical } from 'lucide-react';
/**
* EN: User Management page component
* VI: Component trang quản lý người dùng
*
* Features:
* - Search bar
* - Filter dropdowns
* - User list table (will use DataTable component)
* - Export functionality
*
* Tính năng:
* - Thanh tìm kiếm
* - Dropdowns lọc
* - Bảng danh sách người dùng (sẽ sử dụng component DataTable)
* - Chức năng export
*/
export default function UserManagementPage() {
const [searchQuery, setSearchQuery] = React.useState('');
const [selectedRole, setSelectedRole] = React.useState<string>('all');
const [selectedStatus, setSelectedStatus] = React.useState<string>('all');
return (
<div className="space-y-6">
{/* EN: Page header / VI: Header trang */}
<div>
<h1 className="text-3xl font-bold text-text-primary">
User Management / Quản người dùng
</h1>
<p className="mt-1 text-sm text-text-tertiary">
Manage users, roles, and permissions / Quản người dùng, vai trò quyền
</p>
</div>
{/* EN: Search and filters card / VI: Card tìm kiếm và lọc */}
<Card>
<CardHeader>
<CardTitle>Search & Filters / Tìm kiếm & Lọc</CardTitle>
<CardDescription>
Find and filter users by name, email, role, or status / Tìm lọc người dùng theo tên, email, vai trò hoặc trạng thái
</CardDescription>
</CardHeader>
<CardContent>
<div className="grid grid-cols-1 gap-4 md:grid-cols-3">
{/* EN: Search input / VI: Input tìm kiếm */}
<div className="md:col-span-2">
<Input
type="search"
placeholder="Search by name or email... / Tìm kiếm theo tên hoặc email..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="w-full"
/>
</div>
{/* EN: Filter buttons / VI: Nút lọc */}
<div className="flex gap-2">
<Button variant="secondary" size="md" className="flex-1">
<Filter className="h-4 w-4 mr-2" />
Filters / Lọc
</Button>
<Button variant="secondary" size="md">
<Download className="h-4 w-4" />
</Button>
</div>
</div>
</CardContent>
</Card>
{/* EN: User list table / VI: Bảng danh sách người dùng */}
<DataTable
data={users}
columns={columns}
currentPage={currentPage}
itemsPerPage={10}
onPageChange={setCurrentPage}
selectable
selectedRows={selectedRows}
onSelectionChange={setSelectedRows}
getRowId={(row) => row.id}
bulkActions={[
{
label: 'Activate / Kích hoạt',
action: handleBulkActivate,
variant: 'primary',
},
{
label: 'Deactivate / Vô hiệu hóa',
action: handleBulkDeactivate,
variant: 'secondary',
},
{
label: 'Delete / Xóa',
action: handleBulkDelete,
variant: 'danger',
},
]}
exportable
onExport={() => console.log('Export users')}
/>
{/* EN: User details modal / VI: Modal chi tiết người dùng */}
{selectedUser && (
<React.Suspense fallback={null}>
<UserDetailsModal
user={selectedUser}
open={!!selectedUser}
onOpenChange={(open) => !open && setSelectedUser(null)}
onEdit={(user) => {
console.log('Edit user:', user);
// EN: TODO: Implement edit / VI: TODO: Implement edit
}}
onDelete={(userId) => {
console.log('Delete user:', userId);
setSelectedUser(null);
// EN: TODO: Implement delete / VI: TODO: Implement delete
}}
onDeactivate={(userId) => {
console.log('Deactivate user:', userId);
setSelectedUser(null);
// EN: TODO: Implement deactivate / VI: TODO: Implement deactivate
}}
/>
</React.Suspense>
)}
</div>
);
}

View File

@@ -1,27 +1,199 @@
/**
* EN: Global Styles with Tailwind CSS 4
* VI: Styles toàn cục với Tailwind CSS 4
*
* Import theme variables first, then Tailwind directives
* Import các biến theme trước, sau đó là các directives của Tailwind
*/
@import "../styles/theme.css";
@tailwind base;
@tailwind components;
@tailwind utilities;
/**
* EN: Theme CSS Variables - Dark Mode (Default)
* VI: CSS Variables cho Theme - Dark Mode (Mặc định)
*/
:root {
--foreground-rgb: 0, 0, 0;
--background-start-rgb: 214, 219, 220;
--background-end-rgb: 255, 255, 255;
/* Background Colors */
--bg-primary: #0A0A0A;
--bg-secondary: #121212;
--bg-tertiary: #1A1A1A;
--bg-elevated: #242424;
/* Text Colors */
--text-primary: #FAFAFA;
--text-secondary: #E0E0E0;
--text-tertiary: #A0A0A0;
--text-inverse: #1A1A1A;
/* Brand/Accent Colors */
--accent-primary: #3B82F6;
--accent-secondary: #8B5CF6;
--accent-success: #10B981;
--accent-warning: #F59E0B;
--accent-error: #EF4444;
--accent-info: #06B6D4;
/* Chat Specific Colors */
--chat-user-bubble: #2563EB;
--chat-ai-bubble: #374151;
--chat-user-text: #FFFFFF;
--chat-ai-text: #F3F4F6;
--chat-timestamp: #9CA3AF;
--chat-divider: #1F2937;
/* Border Colors */
--border-primary: #2A2A2A;
--border-secondary: #3A3A3A;
--border-focus: #3B82F6;
/* Font Stack */
--font-sans: -apple-system, BlinkMacSystemFont, "SF Pro Display", "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
--font-mono: "SF Mono", Consolas, "Liberation Mono", Menlo, monospace;
/* Type Scale */
--text-6xl: 3.75rem;
--text-5xl: 3rem;
--text-4xl: 2.25rem;
--text-3xl: 1.875rem;
--text-2xl: 1.5rem;
--text-xl: 1.25rem;
--text-lg: 1.125rem;
--text-base: 1rem;
--text-sm: 0.875rem;
--text-xs: 0.75rem;
/* Font Weights */
--font-light: 300;
--font-normal: 400;
--font-medium: 500;
--font-semibold: 600;
--font-bold: 700;
/* Spacing Scale (Base: 4px) */
--space-0: 0;
--space-1: 0.25rem;
--space-2: 0.5rem;
--space-3: 0.75rem;
--space-4: 1rem;
--space-5: 1.25rem;
--space-6: 1.5rem;
--space-8: 2rem;
--space-10: 2.5rem;
--space-12: 3rem;
--space-16: 4rem;
--space-20: 5rem;
/* Container Widths */
--container-sm: 640px;
--container-md: 768px;
--container-lg: 1024px;
--container-xl: 1280px;
--container-2xl: 1536px;
--chat-max-width: 768px;
--sidebar-width: 280px;
/* Border Radius */
--radius-sm: 0.25rem;
--radius-md: 0.5rem;
--radius-lg: 0.75rem;
--radius-xl: 1rem;
--radius-2xl: 1.5rem;
--radius-full: 9999px;
/* Shadows */
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.5);
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.5);
--shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.6);
--shadow-xl: 0 20px 25px rgba(0, 0, 0, 0.7);
--shadow-glow: 0 0 20px rgba(59, 130, 246, 0.3);
/* Animation Timing */
--ease-in: cubic-bezier(0.4, 0, 1, 1);
--ease-out: cubic-bezier(0, 0, 0.2, 1);
--ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
--ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
/* Duration */
--duration-fast: 150ms;
--duration-normal: 250ms;
--duration-slow: 350ms;
--duration-slower: 500ms;
}
@media (prefers-color-scheme: dark) {
:root {
--foreground-rgb: 255, 255, 255;
--background-start-rgb: 0, 0, 0;
--background-end-rgb: 0, 0, 0;
}
/**
* EN: Light Mode Theme Variables
* VI: CSS Variables cho Light Mode
*/
:root.light {
/* Background Colors */
--bg-primary: #FFFFFF;
--bg-secondary: #F9FAFB;
--bg-tertiary: #F3F4F6;
--bg-elevated: #FFFFFF;
/* Text Colors */
--text-primary: #111827;
--text-secondary: #4B5563;
--text-tertiary: #9CA3AF;
--text-inverse: #FAFAFA;
/* Chat Specific Colors */
--chat-user-bubble: #3B82F6;
--chat-ai-bubble: #F3F4F6;
--chat-user-text: #FFFFFF;
--chat-ai-text: #111827;
--chat-timestamp: #6B7280;
--chat-divider: #E5E7EB;
/* Border Colors */
--border-primary: #E5E7EB;
--border-secondary: #D1D5DB;
--border-focus: #3B82F6;
/* Shadows (lighter for light mode) */
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1);
--shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1);
--shadow-xl: 0 20px 25px rgba(0, 0, 0, 0.1);
--shadow-glow: 0 0 20px rgba(59, 130, 246, 0.2);
}
/**
* EN: Base styles
* VI: Styles cơ bản
*/
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html {
font-family: var(--font-sans);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
body {
color: rgb(var(--foreground-rgb));
background: linear-gradient(
to bottom,
transparent,
rgb(var(--background-end-rgb))
)
rgb(var(--background-start-rgb));
background-color: var(--bg-primary);
color: var(--text-primary);
font-size: var(--text-base);
line-height: 1.5;
transition: background-color var(--duration-normal) var(--ease-in-out),
color var(--duration-normal) var(--ease-in-out);
}
/**
* EN: Smooth transitions for theme switching
* VI: Chuyển đổi mượt mà cho việc chuyển theme
*/
*,
*::before,
*::after {
transition: background-color var(--duration-normal) var(--ease-in-out),
color var(--duration-normal) var(--ease-in-out),
border-color var(--duration-normal) var(--ease-in-out);
}

View File

@@ -1,5 +1,6 @@
import type { Metadata } from 'next';
import './globals.css';
import { ThemeProvider } from '../contexts/theme-context';
export const metadata: Metadata = {
title: 'GoodGo Platform',
@@ -12,8 +13,10 @@ export default function RootLayout({
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>{children}</body>
<html lang="en" suppressHydrationWarning>
<body>
<ThemeProvider>{children}</ThemeProvider>
</body>
</html>
);
}