@npm-tuan17tuii/chat-support
v0.1.0
Published
Embeddable chat support ticket widget for multiple apps
Readme
@mojo/chat-support
Embeddable chat support ticket widget cho nhiều app — build 1 lần, nhúng ở mọi nơi.
📋 Tổng quan
@mojo/chat-support là một npm package cung cấp widget hỗ trợ khách hàng dạng ticket, nhúng được vào bất kỳ ứng dụng React nào (web, Tauri, Electron...). Tất cả app đều gọi chung 1 Backend, giúp team support xử lý tập trung.
Tính năng chính
- 🎫 Ticket-based chat — tạo yêu cầu, chat nhiều lượt, đóng/mở lại tự động
- 🌐 Multi-app — mỗi app truyền
appIdriêng, dữ liệu phân biệt rõ ràng - 🎨 Theming — đổi màu theme qua CSS variable, mỗi app 1 màu riêng
- 🌍 i18n — hỗ trợ 9 ngôn ngữ: 🇻🇳 vi, 🇬🇧 en, 🇯🇵 ja, 🇪🇸 es, 🇩🇪 de, 🇫🇷 fr, 🇰🇷 ko, 🇧🇷 pt, 🇮🇹 it
- 📱 Responsive — full-screen trên mobile (≤480px), safe-area cho Tauri iOS
- 💉 CSS tự inject — không cần import stylesheet riêng
- 📦 Dual format — build ESM + CJS +
.d.ts
🚀 Quick Start
1. Cài đặt
npm install @mojo/chat-supportYêu cầu:
react >= 18vàreact-dom >= 18phải có sẵn trong project (peer dependencies).
2. Sử dụng
import { initChatSupport } from '@mojo/chat-support';
// Khởi tạo widget — chỉ cần 1 dòng
const cleanup = initChatSupport({
appId: 'your-app-id', // Bắt buộc — ID ứng dụng đã đăng ký trên BE
apiBaseUrl: 'https://api.example.com/support', // Bắt buộc — URL gốc API
appName: 'Your App', // Tên hiển thị trên Welcome screen
locale: 'vi', // Ngôn ngữ UI ('vi' | 'en' | 'ja' | ...)
themeColor: '#4F46E5', // Màu theme (default: indigo)
});
// Gọi cleanup() khi cần unmount widget
// cleanup();Xong! Widget sẽ tự render bubble button ở góc dưới phải. Không cần import CSS.
⚙️ Config API
| Property | Type | Bắt buộc | Default | Mô tả |
|----------|------|----------|---------|-------|
| appId | string | ✅ | — | ID ứng dụng đã đăng ký trên Backend |
| apiBaseUrl | string | ✅ | — | URL gốc API BE (không có trailing slash) |
| appName | string | | appId | Tên app hiển thị trên Welcome screen |
| locale | LocaleCode | | 'vi' | Ngôn ngữ UI widget |
| themeColor | string | | '#4F46E5' | Màu theme (hex) — áp dụng cho header, button, bubble |
| mode | 'floating' \| 'inline' | | 'floating' | Chế độ hiển thị |
| containerId | string | | — | ID phần tử DOM chứa widget (bắt buộc khi mode='inline') |
| position | 'bottom-right' \| 'bottom-left' | | 'bottom-right' | Vị trí bubble (chỉ floating mode) |
| clientId | string | | auto-generated | Override client ID (lưu localStorage) |
Locale codes hỗ trợ
| Code | Ngôn ngữ |
|------|----------|
| vi | 🇻🇳 Tiếng Việt (default) |
| en | 🇬🇧 English |
| ja | 🇯🇵 日本語 |
| es | 🇪🇸 Español |
| de | 🇩🇪 Deutsch |
| fr | 🇫🇷 Français |
| ko | 🇰🇷 한국어 |
| pt | 🇧🇷 Português |
| it | 🇮🇹 Italiano |
🖥 Chế độ hiển thị
Floating Mode (mặc định)
Widget hiển thị như một bubble nổi ở góc màn hình. Click bubble để mở panel chat.
initChatSupport({
appId: 'my-app',
apiBaseUrl: 'https://api.example.com/support',
mode: 'floating', // hoặc bỏ qua — floating là default
position: 'bottom-right', // vị trí bubble
});Inline Mode
Widget nhúng trực tiếp vào một container DOM có sẵn. Không có bubble, panel luôn mở.
<!-- Trong HTML -->
<div id="support-widget"></div>initChatSupport({
appId: 'my-app',
apiBaseUrl: 'https://api.example.com/support',
mode: 'inline',
containerId: 'support-widget',
});🎨 Theming
Widget dùng CSS variable --chat-theme-color xuyên suốt. Bạn chỉ cần truyền themeColor khi init:
// App A — Indigo
initChatSupport({
appId: 'app-a',
apiBaseUrl: '...',
themeColor: '#4F46E5',
});
// App B — Teal
initChatSupport({
appId: 'app-b',
apiBaseUrl: '...',
themeColor: '#0D9488',
});Widget sẽ tự set CSS variable vào container, không ảnh hưởng đến style của app host.
🔧 TypeScript
Package export đầy đủ type definitions (.d.ts):
import { initChatSupport } from '@mojo/chat-support';
import type {
ChatSupportConfig,
LocaleCode,
FeTicket,
FeMessage,
TicketStatus,
SenderType,
} from '@mojo/chat-support';📂 Cấu trúc dự án
src/
├── api/
│ ├── apiMap.ts # Endpoints, field mapping, adapter BE ↔ FE
│ └── client.ts # HTTP client, gọi API qua adapter
├── config/
│ └── init.ts # initChatSupport() — entry point công khai
├── i18n/
│ └── locales.ts # Hệ thống i18n, 9 ngôn ngữ
├── state/
│ ├── ChatSupportContext.tsx # React Context + Provider
│ ├── reducer.ts # useReducer — state transitions
│ ├── actions.ts # Async action creators (thunk-style)
│ └── types.ts # State types, Action types
├── styles/
│ └── widget.css # Toàn bộ CSS (tự inject qua JS)
├── utils/
│ └── clientId.ts # Sinh/lưu clientId vào localStorage
├── widget/
│ ├── ChatBubbleButton.tsx # Bubble nổi (floating mode)
│ ├── ChatPanel.tsx # Panel chính — switch views
│ ├── ChatHeader.tsx # Header bar + navigation
│ ├── WelcomeView.tsx # Màn hình chào mừng
│ ├── TicketListView.tsx # Danh sách ticket
│ ├── TicketListItem.tsx # Item trong list
│ ├── NewTicketForm.tsx # Form tạo ticket mới
│ ├── TicketDetailView.tsx # Chi tiết ticket + messages
│ ├── ChatMessageList.tsx # Danh sách tin nhắn
│ ├── MessageBubble.tsx # Bubble tin nhắn (user/agent)
│ ├── ChatInputBar.tsx # Input gửi tin nhắn
│ ├── ClosedBanner.tsx # Banner khi ticket đã đóng
│ └── ChatSupportWidget.tsx # Widget root component
└── index.ts # Entry export — CSS auto-inject🏗 Kiến trúc
┌──────────────────────────────────────────────────────┐
│ App Host (React app bất kỳ) │
│ ┌────────────────────────────────────────────────┐ │
│ │ @mojo/chat-support │ │
│ │ ┌──────────┐ ┌───────────┐ ┌────────────┐ │ │
│ │ │ Config │→ │ Context + │→ │ Components │ │ │
│ │ │ init.ts │ │ Reducer │ │ (widget/*) │ │ │
│ │ └──────────┘ └───────────┘ └────────────┘ │ │
│ │ │ │ │ │ │
│ │ ▼ ▼ ▼ │ │
│ │ ┌──────────┐ ┌───────────┐ ┌────────────┐ │ │
│ │ │ apiMap │← │ API Client│ │ i18n │ │ │
│ │ │ (adapt) │ │ (fetch) │ │ (locales) │ │ │
│ │ └──────────┘ └───────────┘ └────────────┘ │ │
│ └────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 1 Backend chung (Express) │
└──────────────────────────────────────────────────────┘Nguyên tắc thiết kế
| Nguyên tắc | Chi tiết |
|------------|----------|
| API adapter | Component không bao giờ đọc field BE trực tiếp. Mọi dữ liệu đi qua apiMap.ts → FE model (FeTicket, FeMessage) |
| State management | Context + useReducer — không dependency ngoài (Redux, Zustand) |
| CSS variable | Toàn bộ theme dùng --chat-theme-color, không hardcode hex |
| CSS tự inject | vite-plugin-css-injected-by-js — consumer không cần import CSS |
| Peer deps | react + react-dom ở peerDependencies, tránh duplicate React |
🔄 User Flow
Mở widget
│
▼
┌─────────────┐ click CTA ┌──────────────┐ submit ┌────────────┐
│ Welcome │ ──────────────→ │ New Ticket │ ────────→ │ Ticket │
│ Screen │ │ Form │ │ Detail │
│ (appName) │ │ (subject + │ │ (chat) │
└─────────────┘ │ message) │ └────────────┘
│ └──────────────┘ │
│ có ticket cũ │
▼ │
┌─────────────┐ click ticket │
│ Ticket List │ ─────────────────────────────────────────────────→ │
│ (sort by │ │
│ updatedAt) │ ◄────────── back button ──────────────────────────┘
└─────────────┘Ticket Lifecycle
- Tạo ticket → status
open→ vào thẳng Detail view - Chat → gửi/nhận tin nhắn qua lại
- Đóng ticket → status
closed→ hiệnClosedBanner - Nhắn tiếp → BE auto-reopen → status
open(FE cập nhật ngay, không reload)
🧪 Development
Chạy demo
# Cài dependencies
npm install
# Chạy dev server với demo page
npm run dev
# Mở http://localhost:5173Demo page hiển thị 2 widget:
- Floating Mode (
locale: 'en', theme indigo) — bubble ở góc dưới phải - Inline Mode (
locale: 'vi', theme teal) — nhúng trực tiếp
Build production
npm run buildOutput: dist/index.js (ESM) + dist/index.cjs (CJS) + dist/index.d.ts
Cấu hình build
- Vite library mode — build ra ESM + CJS
- vite-plugin-dts — generate
.d.ts - vite-plugin-css-injected-by-js — CSS tự inject, không cần file riêng
- External —
react,react-domkhông bundle (peer deps)
📝 Quy tắc phát triển (FE Rules)
Package tuân thủ bộ 26 quy tắc bắt buộc. Dưới đây là 4 rule cốt lõi (vi phạm = reject PR):
| # | Rule | Lý do |
|---|------|-------|
| R1 | Không đọc field BE trực tiếp trong component | Đảm bảo khi BE đổi field, chỉ sửa apiMap.ts |
| R7 | Input chat luôn enabled, kể cả ticket closed | Gửi tin tự reopen — đây là core UX |
| R11 | Toàn bộ theme dùng CSS variable | Hỗ trợ multi-app, mỗi app 1 theme riêng |
| R16 | Build ESM + CJS + .d.ts | Tương thích mọi bundler |
Chi tiết đầy đủ: xem file .agents/docs/fe-rules.md
🤝 Tích hợp vào dự án mới
Bước 1: Cài package
npm install @mojo/chat-supportBước 2: Đăng ký app trên Backend
Liên hệ team backend để đăng ký appId cho ứng dụng của bạn.
Bước 3: Gọi initChatSupport()
// Ở entry point của app (vd: App.tsx, main.tsx)
import { initChatSupport } from '@mojo/chat-support';
useEffect(() => {
const cleanup = initChatSupport({
appId: 'your-registered-app-id',
apiBaseUrl: 'https://api.mojo.vn/support',
appName: 'Tên App Của Bạn',
locale: 'vi',
themeColor: '#FF6B35', // Tuỳ chọn màu
});
return cleanup;
}, []);Bước 4: Verify
- [ ] Widget bubble hiển thị ở góc dưới phải
- [ ] Click bubble → panel mở, hiện Welcome screen
- [ ] Tạo ticket → gửi tin → nhận phản hồi
- [ ] Đóng ticket → nhắn tiếp → auto-reopen
- [ ] Test trên 1 app khác với
appId/themeColorkhác để đảm bảo không lẫn dữ liệu
📄 License
Private — Inoria internal use only.
