npm package: @quocvan/react-feature-flags
Description: A simple and flexible React toolkit for managing feature flags with a custom fetcher.
Author: Your Name
Latest version: 0.7.1

# @quocvan/react-feature-flags

Một bộ công cụ React đơn giản, linh hoạt và an toàn về kiểu dữ liệu (type-safe) để quản lý feature flag trong ứng dụng của bạn.

---

## 📦 Cài đặt

```bash
npm install @quocvan/react-feature-flags
```

## 🚀 Hướng dẫn nhanh

### 1. Tạo hàm `fetcher`

Hàm này có nhiệm vụ lấy dữ liệu flag từ API và trả về một object có dạng `Record<string, boolean>`.

**Ví dụ với `createStrapiFetcher` (Cách được khuyến nghị):**

```tsx
// src/services/flagFetcher.ts
import { createStrapiFetcher } from "@quocvan/react-feature-flags";

// Lấy các biến môi trường của dự án bạn
const apiUrl = import.meta.env.VITE_API_URL;
const currentEnv = import.meta.env.VITE_APP_ENV;

// Dùng factory để tạo fetcher một cách nhanh chóng
export const myAppFetcher = createStrapiFetcher({
  apiUrl: `${apiUrl}/feature-flags`,
  currentEnv: currentEnv,
});
```

### 2. Bọc ứng dụng trong `FeatureFlagProvider`

Trong file `main.tsx`, hãy bọc component gốc của bạn.

```tsx
// src/main.tsx
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.tsx";
import { FeatureFlagProvider } from "@quocvan/react-feature-flags";
import { myAppFetcher } from "./services/flagFetcher.ts";

ReactDOM.createRoot(document.getElementById("root")!).render(
  <React.StrictMode>
    <FeatureFlagProvider
      fetcher={myAppFetcher}
      loadingComponent={<h1>Đang tải cấu hình...</h1>}
    >
      <App />
    </FeatureFlagProvider>
  </React.StrictMode>
);
```

### 3. Sử dụng flag trong Component

Sử dụng hook `useFeatureFlags` để truy cập vào các flag.

```tsx
// src/components/Dashboard.tsx
import { useFeatureFlags } from "@quocvan/react-feature-flags";

function Dashboard() {
  const { flags, isLoading } = useFeatureFlags();

  if (isLoading) {
    return <p>Đang tải trạng thái tính năng...</p>;
  }

  return (
    <div>{flags.enableNewLayout && <h2>Giao diện mới đã được bật!</h2>}</div>
  );
}
```

---

## 📚 API chi tiết

### `FeatureFlagProvider`

Component gốc cung cấp context cho toàn bộ ứng dụng.

**Props:**

| Prop               | Kiểu                                     | Bắt buộc | Mô tả                                                         |
| :----------------- | :--------------------------------------- | :------- | :------------------------------------------------------------ |
| `fetcher`          | `() => Promise<Record<string, boolean>>` | Có       | Một hàm `async` trả về một object chứa các flag.              |
| `children`         | `React.ReactNode`                        | Có       | Các component con của ứng dụng.                               |
| `loadingComponent` | `React.ReactNode`                        | Không    | Một component React sẽ được hiển thị khi `fetcher` đang chạy. |

### `useFeatureFlags()`

Hook chính để truy cập vào trạng thái của các flag.

**Giá trị trả về:**

- `flags: Record<string, boolean>`: Object chứa các cặp key-value của flag.
- `isLoading: boolean`: `true` khi `fetcher` đang chạy.
- `error: unknown`: Chứa lỗi nếu `fetcher` thất bại.

### `<Feature>` Component

Một cách khai báo để render UI có điều kiện.

**Props:**

| Prop       | Kiểu              | Bắt buộc | Mô tả                                        |
| :--------- | :---------------- | :------- | :------------------------------------------- |
| `name`     | `string`          | Có       | Tên của flag cần kiểm tra.                   |
| `children` | `React.ReactNode` | Có       | Nội dung sẽ được render nếu flag là `true`.  |
| `fallback` | `React.ReactNode` | Không    | Nội dung sẽ được render nếu flag là `false`. |

**Ví dụ:**

```tsx
import { Feature } from "@quocvan/react-feature-flags";

<Feature name="showNewWidget" fallback={<OldWidget />}>
  <NewWidget />
</Feature>;
```

### `filterByFeature()`

Hàm tiện ích để lọc các mảng (menu, tabs). Mỗi item trong mảng cần có một thuộc tính tùy chọn là `featureFlag: string`.

**Ví dụ:**

```tsx
import { useFeatureFlags, filterByFeature } from "@quocvan/react-feature-flags";

const { flags } = useFeatureFlags();

const allMenuItems = [
  { key: "/dashboard", label: "Dashboard" },
  { key: "/analytics", label: "Analytics", featureFlag: "enableAnalyticsPage" },
];

const visibleMenuItems = filterByFeature(allMenuItems, flags);

// <Menu items={visibleMenuItems} />
```

---

### `createStrapiFetcher()`

Một hàm factory để nhanh chóng tạo `fetcher` cho các API của Strapi. Dưới đây là các ví dụ về các tùy chọn nâng cao.

**Tham số `options`:**

| Option              | Kiểu                     | Bắt buộc | Mô tả                                                                                                   |
| :------------------ | :----------------------- | :------- | :------------------------------------------------------------------------------------------------------ |
| `apiUrl`            | `string`                 | Có       | URL đầy đủ để fetch danh sách các flag.                                                                 |
| `currentEnv`        | `string`                 | Có       | Tên môi trường hiện tại (`uat`, `production`,...).                                                      |
| `axiosInstance`     | `AxiosInstance`          | Không    | Một instance axios đã được cấu hình sẵn.                                                                |
| `envToKeyMap`       | `Record<string, string>` | Không    | Một object để ánh xạ tên môi trường vào tên key trong dữ liệu flag. Mặc định cho `uat` và `production`. |
| `transformResponse` | `(res: any) => any[]`    | Không    | Một hàm để trích xuất mảng dữ liệu thô từ response. Mặc định là `(res) => res.data.data`.               |

#### Ví dụ nâng cao cho `createStrapiFetcher`

**1. Sử dụng `axiosInstance` tùy chỉnh**

Nếu ứng dụng của bạn đã có một instance axios được cấu hình sẵn với interceptor (ví dụ: để xử lý token, logging), bạn có thể tái sử dụng nó.

```ts
// src/config/axios.ts
import axios from "axios";
export const myAxios = axios.create({
  timeout: 5000,
  headers: { "X-Custom-Header": "my-app" },
});

// src/services/flagFetcher.ts
import { createStrapiFetcher } from "@quocvan/react-feature-flags";
import { myAxios } from "../config/axios";

export const myAppFetcher = createStrapiFetcher({
  apiUrl: "...",
  currentEnv: "...",
  axiosInstance: myAxios, // <-- Tái sử dụng instance đã có
});
```

**2. Sử dụng `envToKeyMap` tùy chỉnh**

Nếu backend của bạn dùng các tên khác, ví dụ `is_active_on_staging` cho môi trường staging.

```ts
import { createStrapiFetcher } from "@quocvan/react-feature-flags";

export const myAppFetcher = createStrapiFetcher({
  apiUrl: "...",
  currentEnv: "staging", // Môi trường của bạn tên là 'staging'
  envToKeyMap: {
    // Thêm mapping mới
    staging: "is_active_on_staging",
    // Bạn vẫn có thể giữ các mapping cũ
    production: "is_active_on_production",
  },
});
```

**3. Sử dụng `transformResponse` tùy chỉnh**

Nếu API của bạn trả về một cấu trúc khác, ví dụ `{ results: [...] }` thay vì `{ data: { data: [...] } }`.

```ts
import { createStrapiFetcher } from "@quocvan/react-feature-flags";

export const myAppFetcher = createStrapiFetcher({
  apiUrl: "...",
  currentEnv: "...",
  // Cung cấp một hàm để chỉ cho fetcher cách lấy đúng mảng dữ liệu
  transformResponse: (response) => response.data.results,
});