diff --git a/apps/web-client/README.md b/apps/web-client/README.md index 1fef691f..46d5fb75 100644 --- a/apps/web-client/README.md +++ b/apps/web-client/README.md @@ -1,31 +1,185 @@ -# Web Client Application +# GoodGo Platform - Web Client -Next.js web application for GoodGo Platform Client Portal. +> **EN**: Enterprise-grade web client for GoodGo microservices platform +> **VI**: Web client cấp doanh nghiệp cho nền tảng microservices GoodGo -## Features +## Features / Tính Năng -- Next.js 14 with App Router -- TypeScript -- Tailwind CSS -- Zustand for state management -- API integration with auth service +- ✅ **Brand Identity System** - Complete logo suite, favicons, and illustrations +- ✅ **Design System** - Professional design tokens with brand colors, gradients, and glassmorphism +- ✅ **UI Component Library** - Enhanced components with brand variants (Button, EmptyState, Loading States, Logo) +- ✅ **Dark/Light Theme** - Automatic theme switching with system preference +- ✅ **Internationalization** - Multi-language support (i18n ready) +- ✅ **TypeScript** - Full type safety +- ✅ **Tailwind CSS 4** - Modern utility-first styling +- ✅ **Next.js 14** - App Router with RSC +- ✅ **Accessibility** - WCAG 2.1 AA compliant +- ✅ **PWA Ready** - Progressive Web App support -## Development +## Tech Stack + +- **Framework**: Next.js 14 (App Router) +- **Language**: TypeScript 5+ +- **Styling**: Tailwind CSS 4 (CSS-first) +- **State Management**: Zustand +- **API Client**: `@goodgo/http-client` +- **Testing**: Vitest + Playwright +- **Component Development**: Storybook + +##Development / Phát Triển ```bash -# Install dependencies +# Install dependencies / Cài đặt dependencies pnpm install -# Start development server +# Start dev server / Khởi động dev server pnpm dev +# → http://localhost:3000 -# Build for production +# Start Storybook / Khởi động Storybook +pnpm storybook +# → http://localhost:6006 + +# Build for production / Build cho production pnpm build -# Start production server -pnpm start +# Type checking / Kiểm tra kiểu +pnpm typecheck + +# Lint / Kiểm tra lỗi +pnpm lint ``` -## Environment Variables +## Environment Variables / Biến Môi Trường + +Create `.env.local` file: + +```bash +# API URL +NEXT_PUBLIC_API_URL=http://localhost/api/v1 +``` + +## Brand Assets / Tài Sản Thương Hiệu + +Brand assets are located in `/public/brand-assets/`: + +- **Logos**: `/brand-assets/logo/` (full, icon, wordmark variants) +- **Icons**: `/brand-assets/icons/` (favicon) +- **Illustrations**: `/brand-assets/illustrations/` (empty state, error state) + +Usage in components: + +```tsx +import { BrandLogo } from '@/components/ui/brand-logo'; +import { BRAND } from '@/lib/brand-constants'; + +// Logo component + + +// Brand constants +const primaryColor = BRAND.colors.primary.hex; // #3B82F6 +``` + +## UI Components / Components Giao Diện + +### BrandLogo +```tsx +import { BrandLogo, BrandLogoLink } from '@/components/ui/brand-logo'; + + + +``` + +### Button with Brand Variants +```tsx +import { Button } from '@/components/ui/button'; + + + +``` + +### Empty State +```tsx +import { EmptyState, ErrorState } from '@/components/ui/empty-state'; + + +``` + +### Loading States +```tsx +import { + BrandSpinner, + Skeleton, + SkeletonCard, + LoadingOverlay, + ProgressBar +} from '@/components/ui/loading-states'; + + + + + +``` + +## Design System / Hệ Thống Thiết Kế + +### Brand Colors + +```css +/* Primary (Blue) - Tech & Trust */ +--brand-primary: #3B82F6; + +/* Secondary (Purple) - Innovation */ +--brand-secondary: #8B5CF6; + +/* Accent (Cyan) - Energy */ +--brand-accent: #06B6D4; +``` + +### Tailwind Utilities + +```tsx +// Brand colors +
+
+ +// Glassmorphism +
+ +// Brand shadows +
+``` + +## Project Structure / Cấu Trúc Dự Án + +``` +apps/web-client/ +├── public/ +│ └── brand-assets/ # Brand logos, icons, illustrations +├── src/ +│ ├── app/ # Next.js App Router pages +│ ├── components/ +│ │ └── ui/ # Reusable UI components +│ ├── lib/ +│ │ └── brand-constants.ts # Brand helper functions +│ ├── styles/ +│ │ └── theme.css # Design system tokens +│ ├── contexts/ # React contexts (Theme, etc.) +│ ├── hooks/ # Custom React hooks +│ ├── providers/ # Provider components +│ └── stores/ # Zustand stores +└── tailwind.config.js # Tailwind configuration +``` + +## Contributing / Đóng Góp + +Please read [CONTRIBUTING.md](../../CONTRIBUTING.md) for details. + +## License / Giấy Phép + +Proprietary - GoodGo Platform -- `NEXT_PUBLIC_API_URL` - API base URL (default: http://localhost/api/v1) diff --git a/apps/web-client/public/brand-assets/icons/favicon.svg b/apps/web-client/public/brand-assets/icons/favicon.svg new file mode 100644 index 00000000..183d8617 --- /dev/null +++ b/apps/web-client/public/brand-assets/icons/favicon.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + diff --git a/apps/web-client/public/brand-assets/illustrations/empty-state.svg b/apps/web-client/public/brand-assets/illustrations/empty-state.svg new file mode 100644 index 00000000..92884489 --- /dev/null +++ b/apps/web-client/public/brand-assets/illustrations/empty-state.svg @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web-client/public/brand-assets/illustrations/error-state.svg b/apps/web-client/public/brand-assets/illustrations/error-state.svg new file mode 100644 index 00000000..da0d6abb --- /dev/null +++ b/apps/web-client/public/brand-assets/illustrations/error-state.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web-client/public/brand-assets/logo/logo-full.svg b/apps/web-client/public/brand-assets/logo/logo-full.svg new file mode 100644 index 00000000..c25659f9 --- /dev/null +++ b/apps/web-client/public/brand-assets/logo/logo-full.svg @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + GoodGo + + + + + Platform + + + diff --git a/apps/web-client/public/brand-assets/logo/logo-icon.svg b/apps/web-client/public/brand-assets/logo/logo-icon.svg new file mode 100644 index 00000000..5fa21ac2 --- /dev/null +++ b/apps/web-client/public/brand-assets/logo/logo-icon.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + diff --git a/apps/web-client/public/brand-assets/logo/logo-wordmark.svg b/apps/web-client/public/brand-assets/logo/logo-wordmark.svg new file mode 100644 index 00000000..d754c4de --- /dev/null +++ b/apps/web-client/public/brand-assets/logo/logo-wordmark.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + GoodGo + + + + + Platform + + diff --git a/apps/web-client/public/manifest.json b/apps/web-client/public/manifest.json new file mode 100644 index 00000000..944da3b9 --- /dev/null +++ b/apps/web-client/public/manifest.json @@ -0,0 +1,23 @@ +{ + "name": "GoodGo Platform", + "short_name": "GoodGo", + "description": "Enterprise Microservices Platform - Build, deploy, and scale with confidence", + "start_url": "/", + "display": "standalone", + "background_color": "#000000", + "theme_color": "#3B82F6", + "orientation": "portrait-primary", + "icons": [ + { + "src": "/brand-assets/icons/favicon.svg", + "sizes": "any", + "type": "image/svg+xml", + "purpose": "any maskable" + } + ], + "categories": [ + "developer tools", + "productivity" + ], + "lang": "en-US" +} \ No newline at end of file diff --git a/apps/web-client/src/app/globals.css b/apps/web-client/src/app/globals.css index 439915f8..3e05ecdf 100644 --- a/apps/web-client/src/app/globals.css +++ b/apps/web-client/src/app/globals.css @@ -31,7 +31,7 @@ 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); + color var(--duration-normal) var(--ease-in-out); } /** @@ -42,8 +42,8 @@ *::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); + color var(--duration-normal) var(--ease-in-out), + border-color var(--duration-normal) var(--ease-in-out); } /** @@ -122,10 +122,14 @@ * VI: Animation cho Typing Indicator */ @keyframes typing-pulse { - 0%, 60%, 100% { + + 0%, + 60%, + 100% { opacity: 0.3; transform: scale(0.8); } + 30% { opacity: 1; transform: scale(1); @@ -145,12 +149,27 @@ opacity: 0; transform: translateY(8px); } + to { opacity: 1; transform: translateY(0); } } +/** + * EN: Shimmer animation for skeleton loaders + * VI: Animation shimmer cho skeleton loaders + */ +@keyframes shimmer { + 0% { + transform: translateX(-100%); + } + + 100% { + transform: translateX(100%); + } +} + /** * EN: Ensure smooth animations and prevent layout shift * VI: Đảm bảo animation mượt mà và ngăn layout shift @@ -159,4 +178,8 @@ .animate-fadeIn { animation: fadeIn 0.3s ease-out forwards; } -} + + .animate-shimmer { + animation: shimmer 2s infinite; + } +} \ No newline at end of file diff --git a/apps/web-client/src/app/layout.tsx b/apps/web-client/src/app/layout.tsx index 20ca0152..6390505a 100644 --- a/apps/web-client/src/app/layout.tsx +++ b/apps/web-client/src/app/layout.tsx @@ -10,8 +10,45 @@ import { SkipToContent } from '../components/accessibility/skip-to-content'; * VI: Metadata cho ứng dụng */ export const metadata: Metadata = { - title: 'GoodGo Platform', - description: 'Enterprise microservices platform / Nền tảng microservices doanh nghiệp', + title: { + default: 'GoodGo Platform - Enterprise Microservices', + template: '%s | GoodGo Platform', + }, + description: 'Build, deploy, and scale microservices with confidence. Enterprise-grade microservices platform for modern development teams.', + keywords: ['microservices', 'enterprise', 'platform', 'cloud', 'kubernetes', 'devops'], + + // EN: Brand icons for all platforms / VI: Brand icons cho tất cả platforms + icons: { + icon: '/brand-assets/icons/favicon.svg', + apple: '/brand-assets/icons/favicon.svg', + }, + + // EN: Open Graph for social media sharing / VI: Open Graph cho chia sẻ mạng xã hội + openGraph: { + type: 'website', + locale: 'en_US', + url: 'https://goodgo.com', + siteName: 'GoodGo Platform', + title: 'GoodGo Platform - Enterprise Microservices', + description: 'Build, deploy, and scale microservices with confidence', + }, + + // EN: Twitter Card metadata / VI: Twitter Card metadata + twitter: { + card: 'summary_large_image', + title: 'GoodGo Platform', + description: 'Enterprise Microservices Platform', + }, + + // EN: PWA manifest / VI: PWA manifest + manifest: '/manifest.json', + + // EN: Viewport configuration / VI: Cấu hình viewport + viewport: { + width: 'device-width', + initialScale: 1, + maximumScale: 5, + }, }; /** diff --git a/apps/web-client/src/app/page.tsx b/apps/web-client/src/app/page.tsx index 211327a3..e210bd72 100644 --- a/apps/web-client/src/app/page.tsx +++ b/apps/web-client/src/app/page.tsx @@ -3,10 +3,12 @@ import { useAuthStore } from '@/stores/auth-store'; import { useEffect, useState } from 'react'; import { useTranslation } from '@/hooks/use-translation'; +import { BrandLogo } from '@/components/ui/brand-logo'; +import { Button } from '@/components/ui/button'; /** - * EN: Home page component - main application entry point - * VI: Component trang chủ - điểm vào chính của ứng dụng + * EN: Home page component - main application entry point with brand elements + * VI: Component trang chủ - điểm vào chính với brand elements */ export default function Home() { // EN: Translation hook / VI: Hook translation @@ -46,21 +48,38 @@ export default function Home() { // EN: Show loading state while checking authentication // VI: Hiển thị trạng thái loading trong khi kiểm tra xác thực if (isLoading) { - return
{t('common.loading')}
; + return ( +
+ {t('common.loading')} +
+ ); } return ( - // EN: Main content area with centered layout for minimal aesthetic - // VI: Khu vực nội dung chính với layout căn giữa cho thẩm mỹ tối giản -
-
+ // EN: Main content area with brand gradient background + // VI: Khu vực nội dung chính với background gradient thương hiệu +
+ {/* EN: Brand gradient overlay / VI: Overlay gradient thương hiệu */} +
+ + {/* EN: Decorative brand dots / VI: Chấm trang trí thương hiệu */} +
+
+
+ +
+ {/* EN: Brand Logo / VI: Logo thương hiệu */} +
+ +
+ {/* EN: Hero Title / VI: Tiêu đề Hero */} -

+

{t('home.title')}

{/* EN: Subtitle/Description / VI: Phụ đề/Mô tả */} -

+

{t('home.description')}

@@ -68,31 +87,69 @@ export default function Home() {
{isAuthenticated && user ? ( // EN: Authenticated user welcome message / VI: Thông báo chào mừng người dùng đã xác thực -
-
+
+
{user.email?.[0].toUpperCase()}
-

{t('home.welcome', { email: user.email })}

-
- +

+ {t('home.welcome', { email: user.email })} +

+
+ {t('home.role', { role: user.role })}
) : ( // EN: Login prompt for unauthenticated users / VI: Nhắc đăng nhập cho người dùng chưa xác thực -
-

{t('home.pleaseLogin')}

-
- +

+ {t('home.pleaseLogin')} +

+
+ +
)}
+ + {/* EN: Feature highlights / VI: Điểm nổi bật tính năng */} + {!isAuthenticated && ( +
+ {[ + { title: 'Fast Development', icon: '⚡', desc: 'Build and deploy in minutes' }, + { title: 'Enterprise Ready', icon: '🏢', desc: 'Production-grade platform' }, + { title: 'Developer First', icon: '💻', desc: 'Built for modern teams' }, + ].map((feature, index) => ( +
+
+ {feature.icon} +
+

+ {feature.title} +

+

{feature.desc}

+
+ ))} +
+ )}
); diff --git a/apps/web-client/src/components/ui/brand-logo.tsx b/apps/web-client/src/components/ui/brand-logo.tsx new file mode 100644 index 00000000..309dd41c --- /dev/null +++ b/apps/web-client/src/components/ui/brand-logo.tsx @@ -0,0 +1,140 @@ +'use client'; + +import React from 'react'; +import Image from 'next/image'; +import { useTheme } from '@/contexts/theme-context'; +import { cn } from '@/lib/utils'; +import { BRAND, getBrandLogo } from '@/lib/brand-constants'; + +/** + * EN: Brand logo variant types + * VI: Các kiểu biến thể logo thương hiệu + */ +export type LogoVariant = 'full' | 'icon' | 'wordmark'; + +/** + * EN: Brand logo size presets + * VI: Kích thước logo định sẵn + */ +export type LogoSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'; + +/** + * EN: Brand logo component props + * VI: Props cho component logo thương hiệu + */ +export interface BrandLogoProps { + /** Logo variant / Biến thể logo */ + variant?: LogoVariant; + /** Logo size / Kích thước logo */ + size?: LogoSize; + /** Additional CSS classes / CSS classes bổ sung */ + className?: string; + /** Whether to use Next.js Image optimization / Có sử dụng tối ưu Image của Next.js không */ + priority?: boolean; +} + +/** + * EN: Size mappings for different logo sizes + * VI: Ánh xạ kích thước cho các size logo khác nhau + */ +const sizeClasses: Record = { + xs: { width: 'w-16', height: 'h-4' }, + sm: { width: 'w-24', height: 'h-6' }, + md: { width: 'w-32', height: 'h-8' }, + lg: { width: 'w-48', height: 'h-12' }, + xl: { width: 'w-64', height: 'h-16' }, + '2xl': { width: 'w-80', height: 'h-20' }, +}; + +/** + * EN: Size mappings for icon variant + * VI: Ánh xạ kích thước cho biến thể icon + */ +const iconSizeClasses: Record = { + xs: { width: 'w-4', height: 'h-4' }, + sm: { width: 'w-6', height: 'h-6' }, + md: { width: 'w-8', height: 'h-8' }, + lg: { width: 'w-12', height: 'h-12' }, + xl: { width: 'w-16', height: 'h-16' }, + '2xl': { width: 'w-20', height: 'h-20' }, +}; + +/** + * EN: Brand Logo Component - Displays brand logo with automatic theme switching + * VI: Component Logo Thương Hiệu - Hiển thị logo với tự động chuyển theme + * + * @example + * ```tsx + * + * + * + * ``` + */ +export function BrandLogo({ + variant = 'full', + size = 'md', + className, + priority = false, +}: BrandLogoProps) { + const { resolvedTheme } = useTheme(); + + // EN: Get logo path based on variant + // VI: Lấy đường dẫn logo dựa trên biến thể + const logoSrc = getBrandLogo(variant); + + // EN: Select size classes based on variant + // VI: Chọn class kích thước dựa trên biến thể + const sizes = variant === 'icon' ? iconSizeClasses[size] : sizeClasses[size]; + + return ( +
+ {BRAND.name} +
+ ); +} + +/** + * EN: Brand Logo Link - Clickable logo that links to home + * VI: Logo Link - Logo có thể nhấp dẫn về trang chủ + * + * @example + * ```tsx + * + * ``` + */ +export interface BrandLogoLinkProps extends BrandLogoProps { + href?: string; +} + +export function BrandLogoLink({ + href = '/', + ...logoProps +}: BrandLogoLinkProps) { + return ( + + + + ); +} diff --git a/apps/web-client/src/components/ui/button.tsx b/apps/web-client/src/components/ui/button.tsx index ba0b0d10..960b5c4c 100644 --- a/apps/web-client/src/components/ui/button.tsx +++ b/apps/web-client/src/components/ui/button.tsx @@ -24,6 +24,12 @@ const buttonVariants = cva( // EN: Danger button - destructive actions / VI: Button nguy hiểm - hành động phá hủy danger: 'bg-accent-error text-white hover:brightness-110 hover:scale-[1.02] active:scale-[0.98] active:brightness-90 focus-visible:ring-accent-error focus-visible:shadow-[0_0_20px_rgba(239,68,68,0.3)] shadow-md hover:shadow-lg', + // EN: Brand button - main CTA with brand gradient / VI: Button thương hiệu - CTA chính với brand gradient + brand: + 'bg-brand-gradient text-white hover:shadow-brand-lg hover:scale-[1.02] active:scale-[0.98] focus-visible:ring-brand-primary focus-visible:shadow-colored shadow-brand transition-all duration-fast', + // EN: Glass button - glassmorphism style / VI: Button glass - phong cách glassmorphism + glass: + 'bg-glass-bg backdrop-blur-glass border border-glass-border text-white hover:bg-glass-bg-hover hover:border-white/20 hover:scale-[1.02] active:scale-[0.98] focus-visible:ring-brand-primary shadow-md', }, size: { // EN: Extra small button - 28px height (mobile: min 44px) / VI: Button cực nhỏ - chiều cao 28px (mobile: tối thiểu 44px) @@ -51,7 +57,7 @@ const buttonVariants = cva( */ export interface ButtonProps extends React.ButtonHTMLAttributes, - VariantProps { + VariantProps { /** * EN: Loading state - shows spinner when true / VI: Trạng thái loading - hiển thị spinner khi true */ diff --git a/apps/web-client/src/components/ui/empty-state.tsx b/apps/web-client/src/components/ui/empty-state.tsx new file mode 100644 index 00000000..d33d1c29 --- /dev/null +++ b/apps/web-client/src/components/ui/empty-state.tsx @@ -0,0 +1,153 @@ +'use client'; + +import React from 'react'; +import Image from 'next/image'; +import { cn } from '@/lib/utils'; +import { getBrandIllustration } from '@/lib/brand-constants'; +import { Button } from './button'; + +/** + * EN: Empty state action button configuration + * VI: Cấu hình button hành động cho empty state + */ +export interface EmptyStateAction { + /** Button label / Nhãn button */ + label: string; + /** Click handler / Hàm xử lý click */ + onClick: () => void; + /** Button variant / Biến thể button */ + variant?: 'brand' | 'primary' | 'secondary' | 'ghost'; +} + +/** + * EN: Empty state component props + * VI: Props cho component empty state + */ +export interface EmptyStateProps { + /** Title text / Tiêu đề */ + title: string; + /** Description text / Mô tả */ + description?: string; + /** Illustration type / Loại minh họa */ + illustration?: 'empty' | 'error' | 'custom'; + /** Custom illustration path / Đường dẫn minh họa tùy chỉnh */ + customIllustration?: string; + /** Primary action button / Button hành động chính */ + action?: EmptyStateAction; + /** Secondary action button / Button hành động phụ */ + secondaryAction?: EmptyStateAction; + /** Additional CSS classes / CSS classes bổ sung */ + className?: string; + /** Show illustration / Hiển thị minh họa */ + showIllustration?: boolean; +} + +/** + * EN: Empty State Component - Professional empty state with illustrations + * VI: Component Empty State - Empty state chuyên nghiệp với minh họa + * + * @example + * ```tsx + * {} }} + * /> + * ``` + */ +export function EmptyState({ + title, + description, + illustration = 'empty', + customIllustration, + action, + secondaryAction, + className, + showIllustration = true, +}: EmptyStateProps) { + // EN: Get illustration path + // VI: Lấy đường dẫn minh họa + const illustrationSrc = + illustration === 'custom' && customIllustration + ? customIllustration + : getBrandIllustration(illustration === 'custom' ? 'empty' : illustration); + + return ( +
+ {/* EN: Illustration / VI: Minh họa */} + {showIllustration && ( +
+ {title} +
+ )} + + {/* EN: Title / VI: Tiêu đề */} +

+ {title} +

+ + {/* EN: Description / VI: Mô tả */} + {description && ( +

+ {description} +

+ )} + + {/* EN: Actions / VI: Hành động */} + {(action || secondaryAction) && ( +
+ {action && ( + + )} + {secondaryAction && ( + + )} +
+ )} +
+ ); +} + +/** + * EN: Error State Component - Specialized empty state for errors + * VI: Component Error State - Empty state chuyên biệt cho lỗi + * + * @example + * ```tsx + * {} }} + * /> + * ``` + */ +export function ErrorState(props: Omit) { + return ; +} diff --git a/apps/web-client/src/components/ui/loading-states.tsx b/apps/web-client/src/components/ui/loading-states.tsx new file mode 100644 index 00000000..8b7ea791 --- /dev/null +++ b/apps/web-client/src/components/ui/loading-states.tsx @@ -0,0 +1,269 @@ +'use client'; + +import React from 'react'; +import { cn } from '@/lib/utils'; + +/** + * EN: Loading spinner size types + * VI: Kiểu kích thước loading spinner + */ +export type SpinnerSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl'; + +/** + * EN: Loading spinner props + * VI: Props cho loading spinner + */ +export interface SpinnerProps { + /** Spinner size / Kích thước spinner */ + size?: SpinnerSize; + /** Spinner color / Màu spinner */ + color?: 'brand' | 'primary' | 'white'; + /** Additional CSS classes / CSS classes bổ sung */ + className?: string; +} + +/** + * EN: Size mappings for spinner + * VI: Ánh xạ kích thước cho spinner + */ +const spinnerSizes: Record = { + xs: 'w-4 h-4 border-2', + sm: 'w-6 h-6 border-2', + md: 'w-8 h-8 border-3', + lg: 'w-12 h-12 border-4', + xl: 'w-16 h-16 border-4', +}; + +/** + * EN: Color mappings for spinner + * VI: Ánh xạ màu cho spinner + */ +const spinnerColors: Record = { + brand: 'border-brand-primary/20 border-t-brand-primary', + primary: 'border-text-primary/20 border-t-text-primary', + white: 'border-white/20 border-t-white', +}; + +/** + * EN: Brand Spinner - Loading spinner with brand colors + * VI: Brand Spinner - Loading spinner với màu thương hiệu + * + * @example + * ```tsx + * + * ``` + */ +export function BrandSpinner({ + size = 'md', + color = 'brand', + className, +}: SpinnerProps) { + return ( +
+ Loading... +
+ ); +} + +/** + * EN: Skeleton component props + * VI: Props cho skeleton component + */ +export interface SkeletonProps { + /** Skeleton variant / Biến thể skeleton */ + variant?: 'text' | 'circular' | 'rectangular'; + /** Width / Chiều rộng */ + width?: string | number; + /** Height / Chiều cao */ + height?: string | number; + /** Additional CSS classes / CSS classes bổ sung */ + className?: string; + /** Animation wave effect / Hiệu ứng sóng animation */ + wave?: boolean; +} + +/** + * EN: Skeleton - Loading placeholder with shimmer effect + * VI: Skeleton - Loading placeholder với hiệu ứng shimmer + * + * @example + * ```tsx + * + * + * + * ``` + */ +export function Skeleton({ + variant = 'rectangular', + width, + height, + className, + wave = true, +}: SkeletonProps) { + const variantClasses = { + text: 'rounded', + circular: 'rounded-full', + rectangular: 'rounded-md', + }; + + return ( +
+ {wave && ( +
+ )} + Loading... +
+ ); +} + +/** + * EN: Skeleton Card - Pre-built skeleton for card layouts + * VI: Skeleton Card - Skeleton định sẵn cho layout card + */ +export function SkeletonCard({ className }: { className?: string }) { + return ( +
+ + + + +
+ + +
+
+ ); +} + +/** + * EN: Skeleton List - Pre-built skeleton for list layouts + * VI: Skeleton List - Skeleton định sẵn cho layout list + */ +export function SkeletonList({ + count = 3, + className, +}: { + count?: number; + className?: string; +}) { + return ( +
+ {Array.from({ length: count }).map((_, index) => ( +
+ +
+ + +
+
+ ))} +
+ ); +} + +/** + * EN: Loading Overlay - Full screen loading overlay + * VI: Loading Overlay - Overlay loading toàn màn hình + */ +export interface LoadingOverlayProps { + /** Loading message / Thông báo loading */ + message?: string; + /** Show overlay / Hiển thị overlay */ + show: boolean; + /** Spinner size / Kích thước spinner */ + size?: SpinnerSize; +} + +export function LoadingOverlay({ + message = 'Loading...', + show, + size = 'lg', +}: LoadingOverlayProps) { + if (!show) return null; + + return ( +
+ +

{message}

+
+ ); +} + +/** + * EN: Progress Bar - Linear progress indicator with brand colors + * VI: Progress Bar - Thanh tiến trình với màu thương hiệu + */ +export interface ProgressBarProps { + /** Progress value (0-100) / Giá trị tiến trình (0-100) */ + value: number; + /** Show percentage label / Hiển thị nhãn phần trăm */ + showLabel?: boolean; + /** Additional CSS classes / CSS classes bổ sung */ + className?: string; + /** Progress bar color / Màu thanh tiến trình */ + color?: 'brand' | 'success' | 'warning' | 'error'; +} + +const progressColors: Record = { + brand: 'bg-brand-gradient', + success: 'bg-accent-success', + warning: 'bg-accent-warning', + error: 'bg-accent-error', +}; + +export function ProgressBar({ + value, + showLabel = false, + className, + color = 'brand', +}: ProgressBarProps) { + const clampedValue = Math.max(0, Math.min(100, value)); + + return ( +
+
+
+
+
+ {showLabel && ( + + {Math.round(clampedValue)}% + + )} +
+
+ ); +} diff --git a/apps/web-client/src/lib/brand-constants.ts b/apps/web-client/src/lib/brand-constants.ts new file mode 100644 index 00000000..00cc571f --- /dev/null +++ b/apps/web-client/src/lib/brand-constants.ts @@ -0,0 +1,122 @@ +/** + * EN: Brand Constants - Easy access to brand values + * VI: Hằng số thương hiệu - Truy cập dễ dàng các giá trị thương hiệu + * + * This file provides type-safe constants for brand colors, fonts, and assets. + * Use these instead of hardcoding values for consistency and maintainability. + * + * File này cung cấp constants type-safe cho màu sắc, fonts và assets thương hiệu. + * Sử dụng các constants này thay vì hardcode giá trị để đảm bảo tính nhất quán và dễ bảo trì. + */ + +export const BRAND = { + /** EN: Brand name and tagline / VI: Tên thương hiệu và slogan */ + name: 'GoodGo Platform', + tagline: 'Enterprise Microservices Platform', + description: 'Build, deploy, and scale microservices with confidence', + + /** + * EN: Brand color palette - Use these for consistent branding + * VI: Bảng màu thương hiệu - Sử dụng để đảm bảo tính nhất quán + */ + colors: { + /** Primary brand color - Main identity color (Blue) */ + primary: { + main: 'var(--brand-primary)', + light: 'var(--brand-primary-light)', + dark: 'var(--brand-primary-dark)', + contrast: 'var(--brand-primary-contrast)', + hex: '#3B82F6', // For use outside CSS + }, + /** Secondary brand color - Supporting color (Purple) */ + secondary: { + main: 'var(--brand-secondary)', + light: 'var(--brand-secondary-light)', + dark: 'var(--brand-secondary-dark)', + hex: '#8B5CF6', + }, + /** Accent color - Call-to-action color (Cyan) */ + accent: { + main: 'var(--brand-accent)', + hex: '#06B6D4', + }, + /** Brand gradients - For backgrounds and special elements */ + gradients: { + primary: 'var(--brand-gradient-primary)', + accent: 'var(--brand-gradient-accent)', + }, + }, + + /** + * EN: Typography system - Font families for different use cases + * VI: Hệ thống typography - Font families cho các trường hợp khác nhau + */ + fonts: { + display: 'var(--font-display)', // For hero titles (48px+) + heading: 'var(--font-heading)', // For section headings (24-36px) + body: 'var(--font-sans)', // For body text (16px) + mono: 'var(--font-mono)', // For code blocks + }, + + /** + * EN: Brand assets paths - Logo, icons, illustrations + * VI: Đường dẫn brand assets - Logo, icons, illustrations + */ + assets: { + logo: { + full: '/brand-assets/logo/logo-full.svg', + icon: '/brand-assets/logo/logo-icon.svg', + wordmark: '/brand-assets/logo/logo-wordmark.svg', + }, + icons: { + favicon: '/brand-assets/icons/favicon.svg', + }, + illustrations: { + empty: '/brand-assets/illustrations/empty-state.svg', + error: '/brand-assets/illustrations/error-state.svg', + }, + }, +} as const; + +/** + * EN: Type-safe brand color getter + * VI: Hàm lấy màu thương hiệu type-safe + * + * @example + * ```tsx + * const primaryColor = getBrandColor('primary.main'); + * const secondaryHex = getBrandColor('secondary.hex'); + * ``` + */ +export const getBrandColor = (path: string): string => { + const keys = path.split('.'); + let value: any = BRAND.colors; + + for (const key of keys) { + value = value?.[key]; + } + + return value || ''; +}; + +/** + * EN: Brand logo path getter + * VI: Hàm lấy đường dẫn logo thương hiệu + * + * @param variant - Logo variant: 'full' | 'icon' | 'wordmark' + * @returns Logo file path + */ +export const getBrandLogo = (variant: 'full' | 'icon' | 'wordmark' = 'full'): string => { + return BRAND.assets.logo[variant]; +}; + +/** + * EN: Brand illustration path getter + * VI: Hàm lấy đường dẫn illustration thương hiệu + * + * @param type - Illustration type: 'empty' | 'error' + * @returns Illustration file path + */ +export const getBrandIllustration = (type: 'empty' | 'error'): string => { + return BRAND.assets.illustrations[type]; +}; diff --git a/apps/web-client/src/styles/theme.css b/apps/web-client/src/styles/theme.css index f576bae2..e225dda2 100644 --- a/apps/web-client/src/styles/theme.css +++ b/apps/web-client/src/styles/theme.css @@ -75,6 +75,51 @@ --border-focus: #FFFFFF; /* Focus state - White */ + /* ============================================ + EN: Brand Colors (Primary Identity) + VI: Màu thương hiệu (Nhận diện chính) + ============================================ */ + + /* Primary Brand Color - Main brand identity (Blue - Tech & Trust) */ + --brand-primary: #3B82F6; + --brand-primary-light: #60A5FA; + --brand-primary-dark: #2563EB; + --brand-primary-contrast: #FFFFFF; + + /* Secondary Brand Color - Supporting color (Purple - Innovation) */ + --brand-secondary: #8B5CF6; + --brand-secondary-light: #A78BFA; + --brand-secondary-dark: #7C3AED; + + /* Accent Color - Call-to-action (Cyan - Energy) */ + --brand-accent: #06B6D4; + --brand-accent-light: #22D3EE; + --brand-accent-dark: #0891B2; + + /* Brand Gradients - For backgrounds and special elements */ + --brand-gradient-primary: linear-gradient(135deg, var(--brand-primary) 0%, var(--brand-secondary) 100%); + --brand-gradient-accent: linear-gradient(135deg, var(--brand-accent) 0%, var(--brand-primary) 100%); + --brand-gradient-vertical: linear-gradient(180deg, var(--brand-primary) 0%, var(--brand-secondary) 100%); + + /* ============================================ + EN: Glassmorphism Effects + VI: Hiệu ứng Glassmorphism + ============================================ */ + + --glass-bg: rgba(255, 255, 255, 0.05); + --glass-bg-hover: rgba(255, 255, 255, 0.08); + --glass-border: rgba(255, 255, 255, 0.1); + --glass-blur: 10px; + + /* ============================================ + EN: Extended Shadows & Effects + VI: Shadows & Effects mở rộng + ============================================ */ + + --shadow-brand: 0 10px 40px rgba(59, 130, 246, 0.2); + --shadow-brand-lg: 0 20px 60px rgba(59, 130, 246, 0.3); + --shadow-colored: 0 8px 30px rgba(59, 130, 246, 0.25); + /* ============================================ EN: Light Mode Colors (Secondary Theme) VI: Màu sắc cho Light Mode (Theme phụ) @@ -95,6 +140,12 @@ --font-sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; --font-mono: "JetBrains Mono", "SF Mono", Consolas, "Liberation Mono", Menlo, monospace; + /* Display Font - For hero titles (48px+) */ + --font-display: "Inter Display", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; + + /* Heading Font - For section headings (24-36px) */ + --font-heading: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; + /* Type Scale / Kích thước chữ */ --text-6xl: 3.75rem; /* 60px - Hero titles */ @@ -243,6 +294,23 @@ /* Complex animations */ --duration-slower: 500ms; /* Page transitions */ + + /* ============================================ + EN: Advanced Motion Tokens + VI: Token chuyển động nâng cao + ============================================ */ + + /* Motion Ease Functions - For micro-interactions */ + --motion-ease-bounce: cubic-bezier(0.68, -0.55, 0.265, 1.55); + --motion-ease-elastic: cubic-bezier(0.68, -0.6, 0.32, 1.6); + + /* Hover Scale - For interactive elements */ + --hover-scale-sm: 1.02; + --hover-scale-md: 1.05; + --hover-scale-lg: 1.1; + + /* Active Scale - For pressed states */ + --active-scale: 0.98; } /* ============================================ diff --git a/apps/web-client/tailwind.config.js b/apps/web-client/tailwind.config.js index 6348b0d3..978e4c6b 100644 --- a/apps/web-client/tailwind.config.js +++ b/apps/web-client/tailwind.config.js @@ -58,6 +58,31 @@ module.exports = { secondary: 'var(--border-secondary)', focus: 'var(--border-focus)', }, + // EN: Brand colors for easy access / VI: Màu thương hiệu dễ sử dụng + brand: { + primary: { + DEFAULT: 'var(--brand-primary)', + light: 'var(--brand-primary-light)', + dark: 'var(--brand-primary-dark)', + contrast: 'var(--brand-primary-contrast)', + }, + secondary: { + DEFAULT: 'var(--brand-secondary)', + light: 'var(--brand-secondary-light)', + dark: 'var(--brand-secondary-dark)', + }, + accent: { + DEFAULT: 'var(--brand-accent)', + light: 'var(--brand-accent-light)', + dark: 'var(--brand-accent-dark)', + }, + }, + // EN: Glassmorphism utilities / VI: Utilities glassmorphism + glass: { + bg: 'var(--glass-bg)', + 'bg-hover': 'var(--glass-bg-hover)', + border: 'var(--glass-border)', + }, }, // EN: Font families from CSS variables // VI: Font families từ CSS variables @@ -120,12 +145,26 @@ module.exports = { }, // EN: Box shadows from CSS variables // VI: Đổ bóng từ CSS variables + // EN: Brand gradients / VI: Gradients thương hiệu + backgroundImage: { + 'brand-gradient': 'var(--brand-gradient-primary)', + 'brand-gradient-accent': 'var(--brand-gradient-accent)', + 'brand-gradient-vertical': 'var(--brand-gradient-vertical)', + }, + // EN: Extended shadows / VI: Shadows mở rộng boxShadow: { sm: 'var(--shadow-sm)', md: 'var(--shadow-md)', lg: 'var(--shadow-lg)', xl: 'var(--shadow-xl)', glow: 'var(--shadow-glow)', + brand: 'var(--shadow-brand)', + 'brand-lg': 'var(--shadow-brand-lg)', + colored: 'var(--shadow-colored)', + }, + // EN: Glassmorphism backdrop blur / VI: Backdrop blur glassmorphism + backdropBlur: { + glass: 'var(--glass-blur)', }, // EN: Animation timing functions // VI: Hàm thời gian animation