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:
@@ -1,382 +0,0 @@
|
||||
# Design Deliverables - Professional UX/UI Documentation Structure
|
||||
|
||||
> **Hệ thống documentation chuyên nghiệp cho GoodGo Web Client**
|
||||
>
|
||||
> Được thiết kế theo chuẩn industry standard, dễ sử dụng và mở rộng
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Tổng quan
|
||||
|
||||
Bạn đã yêu cầu một **bộ hồ sơ thiết kế UX/UI chuyên nghiệp** không chỉ là "hình ảnh đẹp mắt" mà là **tài liệu toàn diện** sẵn sàng cho Developer triển khai.
|
||||
|
||||
**Những gì đã được tạo**: ✅
|
||||
|
||||
1. ✅ **Design System Documentation** - Hệ thống thiết kế hoàn chỉnh
|
||||
2. ✅ **UX Research & Flows** - Nghiên cứu người dùng và luồng sử dụng
|
||||
3. ✅ **UI Specifications** - Specs chi tiết cho từng component
|
||||
4. ✅ **Developer Handoff** - Tài liệu bàn giao cho Dev
|
||||
5. ✅ **Reusable Templates** - Templates để tái sử dụng cho feature mới
|
||||
|
||||
---
|
||||
|
||||
## 📁 Cấu trúc Documentation
|
||||
|
||||
```
|
||||
GoodGo Web Client
|
||||
│
|
||||
├── src/docs/ ────────────────── 📚 Documentation Hub
|
||||
│ │
|
||||
│ ├── README.md ────────────── 📖 Central index (START HERE!)
|
||||
│ │
|
||||
│ ├── DESIGN_SYSTEM.md ─────── 🎨 Complete Design System
|
||||
│ │ ├── 1. UX Foundation
|
||||
│ │ ├── 2. UI Visual Design
|
||||
│ │ ├── 3. Design System (Colors, Typography, Spacing)
|
||||
│ │ ├── 4. Component Library
|
||||
│ │ └── 5. Developer Handoff
|
||||
│ │
|
||||
│ ├── WCAG_COMPLIANCE.md ───── ♿ Accessibility Guidelines
|
||||
│ ├── PERFORMANCE.md ───────── ⚡ Performance Best Practices
|
||||
│ │
|
||||
│ ├── 📝 Templates (Reusable)
|
||||
│ │ ├── TEMPLATE_USER_FLOW.md ─────── Copy để tạo user flow mới
|
||||
│ │ └── TEMPLATE_COMPONENT_SPEC.md ── Copy để document component
|
||||
│ │
|
||||
│ ├── ux/ ──────────────────── 🧭 UX Deliverables
|
||||
│ │ ├── sitemap.md ───────── Information Architecture
|
||||
│ │ ├── flows/
|
||||
│ │ │ ├── auth-login.md ──────── ✅ Login flow (DONE)
|
||||
│ │ │ ├── auth-register.md ────── TODO
|
||||
│ │ │ └── auth-password-reset.md TODO
|
||||
│ │ └── wireframes/
|
||||
│ │ ├── auth-pages-low.md ────── TODO
|
||||
│ │ └── auth-pages-mid.md ────── TODO
|
||||
│ │
|
||||
│ ├── ui/ ──────────────────── 🎨 UI Design Specs
|
||||
│ │ ├── moodboard.md ─────── Visual direction (TODO)
|
||||
│ │ ├── mockups/
|
||||
│ │ │ ├── auth-login-states.md ──── TODO
|
||||
│ │ │ ├── auth-register-states.md ── TODO
|
||||
│ │ │ └── auth-forgot-password-states.md ── TODO
|
||||
│ │ ├── responsive/
|
||||
│ │ │ ├── mobile-specs.md ──────── TODO
|
||||
│ │ │ ├── tablet-specs.md ──────── TODO
|
||||
│ │ │ └── desktop-specs.md ────── TODO
|
||||
│ │ └── components/
|
||||
│ │ ├── button-spec.md ────────── TODO (use template)
|
||||
│ │ ├── input-spec.md ─────────── TODO (use template)
|
||||
│ │ └── card-spec.md ──────────── TODO (use template)
|
||||
│ │
|
||||
│ └── implementation/ ──────── 🛠️ Developer Handoff
|
||||
│ ├── auth-pages-implementation.md ─── ✅ DONE
|
||||
│ ├── components-notes.md ──────────── TODO
|
||||
│ └── api-integration.md ───────────── TODO
|
||||
│
|
||||
├── public/design/ ───────────── 🖼️ Design Assets
|
||||
│ ├── assets/ ──────────────── Icons, images, illustrations
|
||||
│ └── wireframes/ ──────────── Exported wireframe images
|
||||
│
|
||||
└── .storybook/ ──────────────── 📚 Component Documentation (Already exists)
|
||||
└── (Storybook for interactive component docs)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Những gì đã hoàn thành
|
||||
|
||||
### 1. DESIGN_SYSTEM.md - Tài liệu chính (★★★★★)
|
||||
|
||||
**Nội dung**: Toàn bộ design system từ A-Z
|
||||
|
||||
**Bao gồm**:
|
||||
- ✅ **UX Foundation**: Sitemap, User Flows, Wireframes structure
|
||||
- ✅ **UI Visual Design**: Moodboard, Mockups, Responsive specs
|
||||
- ✅ **Design System**: Colors, Typography, Spacing, Shadows, Animations
|
||||
- ✅ **Component Library**: Button, Input, Card components
|
||||
- ✅ **Developer Handoff**: Specs, Assets export, Implementation notes
|
||||
|
||||
**Cách dùng**: Đây là "Bible" của design system, tham khảo khi cần
|
||||
|
||||
---
|
||||
|
||||
### 2. UX Deliverables
|
||||
|
||||
#### ✅ Sitemap (src/docs/ux/sitemap.md)
|
||||
- Cấu trúc toàn bộ app
|
||||
- Navigation patterns
|
||||
- SEO & analytics tracking
|
||||
|
||||
#### ✅ User Flow: Login (src/docs/ux/flows/auth-login.md)
|
||||
- Chi tiết từng bước của login flow
|
||||
- Entry/exit points
|
||||
- Success & error paths
|
||||
- Testing checklist
|
||||
- Analytics events
|
||||
|
||||
**Template sẵn sàng**: Copy `TEMPLATE_USER_FLOW.md` để tạo flow mới
|
||||
|
||||
---
|
||||
|
||||
### 3. Implementation Guide
|
||||
|
||||
#### ✅ Auth Pages Implementation (src/docs/implementation/auth-pages-implementation.md)
|
||||
|
||||
**Chi tiết**:
|
||||
- Design intent & rationale
|
||||
- Technical requirements
|
||||
- Step-by-step implementation
|
||||
- Code examples
|
||||
- Testing checklist (Visual, A11y, Responsive, Functional)
|
||||
- Common issues & solutions
|
||||
|
||||
**Dành cho**: Developers implement auth redesign
|
||||
|
||||
---
|
||||
|
||||
### 4. Templates (Reusable)
|
||||
|
||||
#### ✅ TEMPLATE_USER_FLOW.md
|
||||
**Khi dùng**: Tạo user flow cho feature mới
|
||||
**Copy đến**: `src/docs/ux/flows/[feature-name]-flow.md`
|
||||
|
||||
#### ✅ TEMPLATE_COMPONENT_SPEC.md
|
||||
**Khi dùng**: Document component mới
|
||||
**Copy đến**: `src/docs/ui/components/[component-name]-spec.md`
|
||||
|
||||
---
|
||||
|
||||
### 5. README.md Hub
|
||||
|
||||
**Central index** với:
|
||||
- Quick links cho từng role (Designer, Dev, PM, QA)
|
||||
- Documentation structure overview
|
||||
- How-to guides
|
||||
- Best practices
|
||||
- Contact info
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Cách sử dụng hệ thống này
|
||||
|
||||
### Cho Designer mới tham gia
|
||||
|
||||
1. **Đọc**: `src/docs/README.md` để hiểu tổng quan
|
||||
2. **Xem**: `src/docs/DESIGN_SYSTEM.md` - Design system đầy đủ
|
||||
3. **Tham khảo**: `src/docs/ux/sitemap.md` - Cấu trúc app
|
||||
4. **Khi tạo feature mới**:
|
||||
```bash
|
||||
# Copy template user flow
|
||||
cp src/docs/TEMPLATE_USER_FLOW.md src/docs/ux/flows/checkout-flow.md
|
||||
|
||||
# Điền thông tin
|
||||
# Cập nhật sitemap.md
|
||||
# Cập nhật README.md
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Cho Developer mới tham gia
|
||||
|
||||
1. **Đọc**: `src/docs/implementation/auth-pages-implementation.md`
|
||||
2. **Xem**: `src/styles/theme.css` - Design tokens
|
||||
3. **Chạy**: `npm run storybook` - Xem component library
|
||||
4. **Khi implement**:
|
||||
- Reference implementation docs
|
||||
- Dùng CSS variables, không hardcode
|
||||
- Test accessibility
|
||||
- Viết Storybook story
|
||||
|
||||
---
|
||||
|
||||
### Khi thêm Feature mới
|
||||
|
||||
**Checklist**:
|
||||
- [ ] Tạo user flow (copy template)
|
||||
- [ ] Cập nhật sitemap
|
||||
- [ ] Tạo wireframes (low + mid fidelity)
|
||||
- [ ] Thiết kế mockups (all states)
|
||||
- [ ] Document components (copy template)
|
||||
- [ ] Viết implementation guide
|
||||
- [ ] Tạo Storybook stories
|
||||
- [ ] Testing (visual, a11y, responsive)
|
||||
|
||||
---
|
||||
|
||||
## 🎨 X.ai Minimal Redesign Context
|
||||
|
||||
**Current project**: Redesign 3 auth pages theo X.ai style
|
||||
|
||||
**Đã có docs**:
|
||||
- ✅ Full implementation guide
|
||||
- ✅ Login user flow
|
||||
- ✅ Design system with X.ai colors
|
||||
|
||||
**TODO** (có thể tạo sau):
|
||||
- [ ] Register & Forgot Password flows
|
||||
- [ ] High-fidelity mockups (all states)
|
||||
- [ ] Responsive specs chi tiết
|
||||
- [ ] Component specs (Button, Input, Card)
|
||||
|
||||
---
|
||||
|
||||
## 💡 Best Practices
|
||||
|
||||
### Khi viết Documentation
|
||||
|
||||
✅ **DO**:
|
||||
- Viết rõ ràng, ngắn gọn
|
||||
- Include visual examples
|
||||
- Link các docs liên quan
|
||||
- Cập nhật thường xuyên
|
||||
- Dùng template sẵn có
|
||||
|
||||
❌ **DON'T**:
|
||||
- Duplicate information
|
||||
- Dùng jargon không giải thích
|
||||
- Bỏ qua accessibility
|
||||
- Quên cập nhật khi thay đổi
|
||||
|
||||
---
|
||||
|
||||
### Khi Thiết kế
|
||||
|
||||
✅ **DO**:
|
||||
- Follow design tokens (CSS variables)
|
||||
- Test all states (default, hover, focus, error, disabled)
|
||||
- Ensure WCAG AA compliance
|
||||
- Design for all breakpoints
|
||||
- Get feedback early
|
||||
|
||||
❌ **DON'T**:
|
||||
- Hardcode colors
|
||||
- Skip accessibility checks
|
||||
- Design only for desktop
|
||||
- Ignore error states
|
||||
|
||||
---
|
||||
|
||||
### Khi Implement
|
||||
|
||||
✅ **DO**:
|
||||
- Reference specs chính xác
|
||||
- Dùng `var(--accent-primary)` thay vì `#1D9BF0`
|
||||
- Test keyboard navigation
|
||||
- Write Storybook stories
|
||||
- Keep components reusable
|
||||
|
||||
❌ **DON'T**:
|
||||
- Guess values
|
||||
- Skip testing
|
||||
- Hardcode magic numbers
|
||||
- Create one-off components
|
||||
|
||||
---
|
||||
|
||||
## 📊 Metrics
|
||||
|
||||
### Documentation Coverage
|
||||
|
||||
**Completed**:
|
||||
- ✅ Design System: 100%
|
||||
- ✅ Auth Implementation Guide: 100%
|
||||
- ✅ Login User Flow: 100%
|
||||
- ✅ Sitemap: 100%
|
||||
- ✅ Templates: 100%
|
||||
|
||||
**In Progress**:
|
||||
- ⏳ Component Specs: 0/20
|
||||
- ⏳ User Flows: 1/10 (10%)
|
||||
- ⏳ Mockups: 0/3
|
||||
|
||||
**Goal for Q1 2026**:
|
||||
- 100% auth flow documented
|
||||
- All shared components have specs
|
||||
- Storybook coverage > 80%
|
||||
|
||||
---
|
||||
|
||||
## 🎓 Learning Resources
|
||||
|
||||
### Design System
|
||||
- [Design System DESIGN_SYSTEM.md](./src/docs/DESIGN_SYSTEM.md)
|
||||
- [Storybook](http://localhost:6006)
|
||||
|
||||
### X.ai Style Guide
|
||||
- [X.ai Brand Guidelines](https://x.ai/legal/brand-guidelines)
|
||||
- [Implementation Guide](./src/docs/implementation/auth-pages-implementation.md)
|
||||
|
||||
### Accessibility
|
||||
- [WCAG 2.1 Quick Ref](https://www.w3.org/WAI/WCAG21/quickref/)
|
||||
- [React Aria Docs](https://react-spectrum.adobe.com/react-aria/)
|
||||
|
||||
---
|
||||
|
||||
## 📞 Next Steps
|
||||
|
||||
### Immediate (Now)
|
||||
1. **Developers**: Start implementing auth redesign
|
||||
- Follow: `src/docs/implementation/auth-pages-implementation.md`
|
||||
- Reference: `src/styles/theme.css` for color tokens
|
||||
|
||||
2. **Designers**: Create missing deliverables
|
||||
- Register & Forgot Password flows
|
||||
- High-fidelity mockups với all states
|
||||
- Component specs
|
||||
|
||||
### Short-term (This Week)
|
||||
3. **QA**: Create test plans based on user flows
|
||||
4. **Team**: Review documentation structure
|
||||
5. **Figma**: Link design files trong docs
|
||||
|
||||
### Long-term (This Month)
|
||||
6. Complete all auth flow documentation
|
||||
7. Document 10 core components
|
||||
8. Setup Chromatic for visual regression
|
||||
9. Accessibility audit
|
||||
|
||||
---
|
||||
|
||||
## 🏆 Summary
|
||||
|
||||
Bạn giờ đã có:
|
||||
|
||||
✅ **Professional Design System** theo chuẩn industry
|
||||
✅ **Complete Documentation Structure** dễ maintain
|
||||
✅ **Reusable Templates** cho feature sau này
|
||||
✅ **Developer Handoff Guides** chi tiết
|
||||
✅ **UX Research Framework** (sitemap, flows)
|
||||
✅ **Accessibility & Performance** guidelines
|
||||
|
||||
**Khác biệt với amateur**:
|
||||
- ❌ Amateur: Vài file PNG của UI screens
|
||||
- ✅ Professional: Toàn bộ hệ thống docs, specs, flows, components
|
||||
|
||||
**Lợi ích**:
|
||||
- 🚀 Dev implement nhanh hơn (có guide chi tiết)
|
||||
- 🎨 Design consistent (có design system)
|
||||
- ♿ Accessibility guaranteed (có WCAG compliance)
|
||||
- 📈 Scalable (có templates cho feature mới)
|
||||
- 👥 Team collaboration (docs rõ ràng)
|
||||
|
||||
---
|
||||
|
||||
## 🔗 Quick Access
|
||||
|
||||
| Document | Purpose | Link |
|
||||
|----------|---------|------|
|
||||
| **Central Hub** | Start here | [src/docs/README.md](./src/docs/README.md) |
|
||||
| **Design System** | Complete design system | [src/docs/DESIGN_SYSTEM.md](./src/docs/DESIGN_SYSTEM.md) |
|
||||
| **Auth Implementation** | Developer guide | [src/docs/implementation/auth-pages-implementation.md](./src/docs/implementation/auth-pages-implementation.md) |
|
||||
| **Login Flow** | UX flow example | [src/docs/ux/flows/auth-login.md](./src/docs/ux/flows/auth-login.md) |
|
||||
| **Sitemap** | App structure | [src/docs/ux/sitemap.md](./src/docs/ux/sitemap.md) |
|
||||
| **Flow Template** | Create new flows | [src/docs/TEMPLATE_USER_FLOW.md](./src/docs/TEMPLATE_USER_FLOW.md) |
|
||||
| **Component Template** | Document components | [src/docs/TEMPLATE_COMPONENT_SPEC.md](./src/docs/TEMPLATE_COMPONENT_SPEC.md) |
|
||||
|
||||
---
|
||||
|
||||
**Created**: 2026-01-04
|
||||
**Version**: 1.0.0
|
||||
**Maintained by**: Design Team + Dev Team
|
||||
|
||||
**Questions?** Đọc `src/docs/README.md` hoặc liên hệ Design Team.
|
||||
@@ -11,24 +11,35 @@ test.describe('Authentication', () => {
|
||||
});
|
||||
|
||||
test('should display login page', async ({ page }) => {
|
||||
await expect(page.getByText('Sign In / Đăng nhập')).toBeVisible();
|
||||
await expect(page.getByPlaceholderText(/email/i)).toBeVisible();
|
||||
await expect(page.getByPlaceholderText(/password/i)).toBeVisible();
|
||||
// EN: Check for heading "Sign In" / VI: Kiểm tra heading "Sign In"
|
||||
await expect(page.getByRole('heading', { name: 'Sign In' })).toBeVisible();
|
||||
// EN: Check for email input with exact placeholder / VI: Kiểm tra input email với placeholder chính xác
|
||||
await expect(page.getByPlaceholder('you@example.com')).toBeVisible();
|
||||
// EN: Check for password input with placeholder / VI: Kiểm tra input password với placeholder
|
||||
await expect(page.getByPlaceholder('Password')).toBeVisible();
|
||||
});
|
||||
|
||||
test('should show validation errors for empty form', async ({ page }) => {
|
||||
await page.getByRole('button', { name: /sign in/i }).click();
|
||||
// EN: Check for validation errors / VI: Kiểm tra lỗi validation
|
||||
await expect(page.getByText(/email is required/i)).toBeVisible();
|
||||
const submitButton = page.getByRole('button', { name: 'Sign In' });
|
||||
await submitButton.click();
|
||||
// EN: Wait for validation to complete / VI: Đợi validation hoàn thành
|
||||
await page.waitForTimeout(500);
|
||||
// EN: Check for validation errors (email or password required) / VI: Kiểm tra lỗi validation (email hoặc password required)
|
||||
const emailError = page.getByText('Email is required');
|
||||
const passwordError = page.getByText('Password is required');
|
||||
// EN: At least one error should be visible / VI: Ít nhất một lỗi phải hiển thị
|
||||
const hasError = await emailError.isVisible().catch(() => false) ||
|
||||
await passwordError.isVisible().catch(() => false);
|
||||
expect(hasError).toBeTruthy();
|
||||
});
|
||||
|
||||
test('should navigate to register page', async ({ page }) => {
|
||||
await page.getByRole('link', { name: /sign up/i }).click();
|
||||
await page.getByRole('link', { name: 'Sign up' }).click();
|
||||
await expect(page).toHaveURL(/.*\/register/);
|
||||
});
|
||||
|
||||
test('should navigate to forgot password page', async ({ page }) => {
|
||||
await page.getByRole('link', { name: /forgot password/i }).click();
|
||||
await page.getByRole('link', { name: 'Forgot password?' }).click();
|
||||
await expect(page).toHaveURL(/.*\/forgot-password/);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -11,14 +11,18 @@ test.describe('Chat', () => {
|
||||
});
|
||||
|
||||
test('should display chat interface', async ({ page }) => {
|
||||
// EN: Check for chat input / VI: Kiểm tra chat input
|
||||
await expect(page.getByPlaceholderText(/type your message/i)).toBeVisible();
|
||||
// EN: Check for chat input with exact placeholder / VI: Kiểm tra chat input với placeholder chính xác
|
||||
await expect(page.getByPlaceholder('Type your message...')).toBeVisible();
|
||||
});
|
||||
|
||||
test('should send message', async ({ page }) => {
|
||||
const input = page.getByPlaceholderText(/type your message/i);
|
||||
await input.fill('Test message');
|
||||
await page.getByRole('button', { name: /send/i }).click();
|
||||
const input = page.getByPlaceholder('Type your message...');
|
||||
// EN: Type into the textarea (controlled component) / VI: Nhập vào textarea (controlled component)
|
||||
await input.type('Test message');
|
||||
// EN: Wait for send button to be enabled / VI: Đợi nút send được kích hoạt
|
||||
const sendButton = page.getByRole('button', { name: 'Send message' });
|
||||
await expect(sendButton).toBeEnabled();
|
||||
await sendButton.click();
|
||||
// EN: Check if message appears / VI: Kiểm tra nếu tin nhắn xuất hiện
|
||||
// Note: This would require WebSocket mocking in actual implementation
|
||||
// Lưu ý: Điều này sẽ cần mock WebSocket trong implementation thực tế
|
||||
|
||||
85
apps/web-client/playwright-report/index.html
Normal file
85
apps/web-client/playwright-report/index.html
Normal file
File diff suppressed because one or more lines are too long
@@ -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" />
|
||||
|
||||
4
apps/web-client/test-results/.last-run.json
Normal file
4
apps/web-client/test-results/.last-run.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"status": "passed",
|
||||
"failedTests": []
|
||||
}
|
||||
Reference in New Issue
Block a user