# React và Next.js (`@hatang/chat-react`)

`@hatang/chat-react` nạp widget chat Hạ Tầng trong ứng dụng React hoặc Next.js. Gói có một component không render gì, chỉ chèn script widget đúng một lần, và một hook trả về [JS API `$hatang`](https://hatang.dev/vi/docs/guides/support/web-widget/javascript-api.md) đã có kiểu. Widget vẫn là widget web, cùng giao diện và cùng cài đặt trong dashboard; gói này chỉ lo phần nạp và kiểu TypeScript.

> **Info: Phiên bản**
>
> `@hatang/chat-react` **0.1.2** · React 18+ · Next.js 14+ (App Router và Pages Router) · [npm](https://www.npmjs.com/package/@hatang/chat-react) · changelog: tệp `CHANGELOG.md` trong gói

## Trước khi bắt đầu

- Một kênh website trong Hạ Tầng. Chép **Site ID** (`site_...`) ở **Hỗ trợ → Kênh** → chọn website.
- Tên miền ứng dụng của bạn chạy phải là tên miền của website (hoặc tên miền phụ), hoặc nằm trong **Origin được phép thêm** ở cài đặt website. Thêm `http://localhost:3000` vào đó khi phát triển trên máy.

## Cài đặt

```bash
npm install @hatang/chat-react
```

## Nạp widget {#load}

Render `<HatangChat />` một lần trong root layout. Module bắt đầu bằng `"use client"` nên dùng thẳng được trong App Router.

```tsx title="app/layout.tsx"
import { HatangChat } from "@hatang/chat-react";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="vi">
      <body>
        {children}
        <HatangChat siteId="site_123..." />
      </body>
    </html>
  );
}
```

Ở lần render đầu, component đặt `window.HATANG_SITE_ID`, đặt `window.HATANG_CONFIG` nếu bạn truyền `config`, rồi chèn `https://chat.hatang.dev/widget.js` với `async`. Script không bao giờ bị chèn hai lần, kể cả khi component render lại hay được render nhiều chỗ. Sau khi trang đầu tiên tải xong, website trong dashboard hiện trạng thái đã cài.

### Props {#props}

| Prop | Kiểu | Mặc định | Tác dụng |
|---|---|---|---|
| `siteId` | `string` | bắt buộc | Site ID của website |
| `host` | `string` | `https://chat.hatang.dev` | Nơi phục vụ `widget.js`. Chỉ đổi khi chạy gateway local |
| `config` | `HatangConfig` | — | `{ position?: "left" \| "right"; locale?: "vi" \| "en"; hideLauncher?: boolean }`, ghi vào `window.HATANG_CONFIG` trước khi script tải |
| `user` | `HatangIdentity` | — | `{ userId?, email?, name?, phone?, hash?, data? }`. Gọi `identify` khi widget sẵn sàng, và gọi lại mỗi khi `userId`, `email`, `name` hoặc `hash` đổi |

`config` chỉ được đọc một lần, lúc script được chèn; đổi sau đó không tới được widget. Muốn đổi ngôn ngữ lúc chạy, gọi `setLocale`. Tuỳ chọn `offset` và `mobileOffset` của `HATANG_CONFIG` chưa có trong kiểu `HatangConfig`; nếu cần, tự đặt `window.HATANG_CONFIG` trước khi component render (xem [Widget web và JS API](https://hatang.dev/vi/docs/guides/support/web-widget/javascript-api.md)).

## Gọi widget từ code {#api}

`useHatang()` (hook) và `hatang()` (hàm thường, dùng ngoài component) trả về cùng một object. Lệnh gọi trước khi widget tải xong được xếp hàng, nên gọi lúc nào trong trình duyệt cũng được. Ở server, các lệnh không làm gì.

```tsx
"use client";
import { useHatang } from "@hatang/chat-react";

export function SupportButton() {
  const chat = useHatang();
  return <button onClick={() => chat.open()}>Chat với chúng tôi</button>;
}
```

| Hàm | Tác dụng |
|---|---|
| `open()`, `close()`, `toggle()` | Mở, đóng hoặc bật/tắt cửa sổ chat |
| `hide()`, `show()` | Ẩn, hiện nút chat (ví dụ trên trang thanh toán) |
| `identify(identity)` | Gắn người dùng đã đăng nhập; `hash` tính ở server ([Nhận diện khách](https://hatang.dev/vi/docs/guides/support/web-widget/identity-verification.md)) |
| `setData(data)` | Thuộc tính tuỳ chỉnh hiển thị cho nhân viên |
| `setLocale("vi" \| "en")` | Đổi ngôn ngữ widget |
| `pushEvent(name, data?)` | Ghi một sự kiện vào dòng thời gian của khách |
| `on(event, cb)`, `off(event, cb)` | Nghe `ready`, `open`, `close`, `message:received`, `message:sent`, `conversation:status` |
| `reset()` | Quên khách hiện tại (khi đăng xuất). Widget tải lại trang để bắt đầu phiên mới |

```tsx
"use client";
import { useEffect } from "react";
import { hatang } from "@hatang/chat-react";

export function ChatAnalytics() {
  useEffect(() => {
    const chat = hatang();
    const onReply = (m?: unknown) => track("chat_reply", m);
    chat.on("message:received", onReply);
    return () => chat.off("message:received", onReply);
  }, []);
  return null;
}
```

Widget còn nhận những lệnh mà API có kiểu này chưa liệt kê, như `lead`. Tự đẩy chúng vào hàng đợi: `window.$hatang.push(["lead", { email, name }])`. Danh sách lệnh và sự kiện đầy đủ ở [Widget web và JS API](https://hatang.dev/vi/docs/guides/support/web-widget/javascript-api.md).

## Nhận diện người dùng đã đăng nhập {#identify}

Tính `hash`, tức HMAC-SHA256 của user id với identity secret của site, ở server rồi truyền xuống cùng người dùng. Đừng bao giờ đưa secret vào bundle phía client.

```tsx title="app/layout.tsx (Server Component)"
import { createHmac } from "node:crypto";
import { HatangChat } from "@hatang/chat-react";
import { currentUser } from "@/lib/auth";

export default async function RootLayout({ children }: { children: React.ReactNode }) {
  const user = await currentUser();
  const identity = user
    ? {
        userId: user.id,
        email: user.email,
        name: user.name,
        hash: createHmac("sha256", process.env.HATANG_IDENTITY_SECRET!).update(user.id).digest("hex"),
      }
    : undefined;
  return (
    <html lang="vi">
      <body>
        {children}
        <HatangChat siteId="site_123..." user={identity} />
      </body>
    </html>
  );
}
```

Với `hash` hợp lệ, hộp thư đánh dấu khách là đã xác minh và gộp hội thoại với các phiên trước của cùng người, kể cả phiên trong ứng dụng di động. Gọi `hatang().reset()` khi người dùng đăng xuất để người tiếp theo trên cùng trình duyệt không mang danh tính cũ.

## Không dùng `useEffect` {#snippet}

Với Pages Router, framework khác hoặc template HTML thuần, `hatangSnippet(siteId, host?, config?)` trả về đúng đoạn script inline mà dashboard hiển thị, dưới dạng một chuỗi. Đặt nó vào thẻ `<script>` hoặc `next/script`:

```tsx
import Script from "next/script";
import { hatangSnippet } from "@hatang/chat-react";

<Script id="hatang" strategy="afterInteractive">
  {hatangSnippet("site_123...", undefined, { locale: "vi" })}
</Script>
```

## Các export {#exports}

`HatangChat`, `HatangChatProps`, `hatang`, `useHatang`, `HatangApi`, `HatangIdentity`, `HatangEvent`, `HatangConfig`, `hatangSnippet`, `inlineSnippet` và `widgetUrl`.

## Changelog {#changelog}

**0.1.1**: trang npm mô tả widget và Hạ Tầng, dẫn tới hướng dẫn này; README có thêm phần About Ha Tang. Không đổi code.

**0.1.0**: bản đầu tiên. `<HatangChat siteId host config user />` nạp widget một lần; `useHatang()` / `hatang()` với `open`, `close`, `toggle`, `hide`, `show`, `identify`, `setData`, `setLocale`, `pushEvent`, `on`, `off` và `reset`, được xếp hàng đến khi widget tải xong.

## Bước tiếp theo

- [Widget web và JS API](https://hatang.dev/vi/docs/guides/support/web-widget/javascript-api.md): mọi tuỳ chọn `HATANG_CONFIG`, lệnh `lead`, theo dõi chuyển đổi.
- [Nhận diện khách](https://hatang.dev/vi/docs/guides/support/web-widget/identity-verification.md): danh tính có chữ ký và cách gộp phiên.
- [Cài widget](https://hatang.dev/vi/docs/guides/support/web-widget/install.md): Shopify, Google Tag Manager, Webflow và HTML thuần.

Source: https://hatang.dev/vi/docs/guides/sdks/react
