npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@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 appId riê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-support

Yêu cầu: react >= 18react-dom >= 18 phả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

  1. Tạo ticket → status open → vào thẳng Detail view
  2. Chat → gửi/nhận tin nhắn qua lại
  3. Đóng ticket → status closed → hiện ClosedBanner
  4. 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:5173

Demo 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 build

Output: 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
  • Externalreact, react-dom khô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-support

Bướ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/themeColor khác để đảm bảo không lẫn dữ liệu

📄 License

Private — Inoria internal use only.