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

# @namphuongtechnologi/react-feature-flags

Thư viện React đơn giản và linh hoạt để quản lý feature flags với khả năng tùy biến fetcher.

## 📦 Cài đặt

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

## ⚙️ Yêu cầu hệ thống

Thư viện này yêu cầu:

-   React >= 17.0.0
-   React DOM >= 17.0.0
-   Axios >= 1.13.2 (tùy chọn, chỉ cần khi sử dụng `createFetcher`)

## 🚀 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>`.

Bạn có thể sử dụng hàm `createFetcher` để tạo `fetcher` một cách nhanh chóng, hoặc tự tạo một hàm `fetcher` tùy chỉnh.

**Ví dụ 1: Sử dụng `createFetcher` (khuyến nghị)**

```typescript
// /flagFetcher.ts
import { createFetcher } from '@namphuongtechnologi/react-feature-flags';
import { myAxios } from '../config/axios'; // Giả sử bạn đã có một instance axios

const currentEnv = import.meta.env.VITE_APP_ENV; // Lấy môi trường hiện tại

// Tạo fetcher với các tùy chọn nâng cao
export const myAppFetcher = createFetcher({
    apiUrl: `/feature-flags`,
    currentEnv: currentEnv,
    axiosInstance: myAxios,
    envToKeyMap: {
        staging: 'is_active_on_staging', // Ánh xạ môi trường 'staging' với key 'is_active_on_staging'
    },
    transformResponse: (response) => response.data.results, // Lấy dữ liệu từ cấu trúc trả về khác
});
```

-   `apiUrl`: Endpoint API để lấy danh sách các flag.
-   `currentEnv`: Tên môi trường hiện tại của ứng dụng (`development`, `staging`, `production`,...).
-   `axiosInstance` (Tùy chọn): Tái sử dụng một instance axios đã có sẵn trong dự án của bạn.
-   `envToKeyMap` (Tùy chọn): Ghi đè hoặc bổ sung ánh xạ giữa tên môi trường và tên thuộc tính trong dữ liệu API.
-   `transformResponse` (Tùy chọn): Một hàm để chỉ cho `fetcher` cách trích xuất đúng mảng dữ liệu từ response của API.

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

Trong file `main.tsx` (hoặc file gốc của ứng dụng), hãy bọc component `App` của bạn.

```tsx
import { FeatureFlagProvider } from '@namphuongtechnologi/react-feature-flags';
import { myAppFetcher } from './flagFetcher';

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 trạng thái của các flag hoặc dùng component `<FeatureComponent>` để render UI có điều kiện.

**Cách 1: Dùng hook `useFeatureFlags`**

```tsx
import { useFeatureFlags } from '@namphuongtechnologi/react-feature-flags';

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

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

    if (error) {
        return <p>Đã xảy ra lỗi khi tải feature flags</p>;
    }

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

**Cách 2: Dùng component `<FeatureComponent>`**

```tsx
import { FeatureComponent } from '@namphuongtechnologi/react-feature-flags';

function MyComponent() {
    return (
        <FeatureComponent name='showNewWidget' fallback={<OldWidget />}>
            <NewWidget />
        </FeatureComponent>
    );
}
```

---

## 📚 API chi tiết

### `FeatureFlagProvider`

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

| Prop               | Kiểu                                     | Bắt buộc | Mô tả                                             |
| :----------------- | :--------------------------------------- | :------- | :------------------------------------------------ |
| `fetcher`          | `() => Promise<Record<string, boolean>>` | Có       | 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    | Component hiển thị trong khi `fetcher` đang chạy. |

### `useFeatureFlags()`

Hook để 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.

### `<FeatureComponent>` Component

Một cách khai báo để render UI có điều kiện dựa trên flag.

| 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ẽ render nếu flag là `true`.  |
| `fallback` | `React.ReactNode` | Không    | Nội dung sẽ render nếu flag là `false`. |

### `filterByFeature()`

Hàm tiện ích để lọc một mảng (ví dụ: menu, tabs) dựa trên các flag. Mỗi item trong mảng cần có thuộc tính `featureFlag: string`. Những item không có thuộc tính này sẽ luôn được giữ lại.

Hàm này hỗ trợ lọc đệ quy cho các item có `children` (menu đa cấp).

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

interface MenuItem {
    key: string;
    label: string;
    featureFlag?: string;
    children?: MenuItem[];
}

const allMenuItems: MenuItem[] = [
    { key: '/dashboard', label: 'Dashboard' },
    {
        key: '/analytics',
        label: 'Analytics',
        featureFlag: 'enableAnalyticsPage',
    },
    {
        key: '/settings',
        label: 'Settings',
        featureFlag: 'enableSettingsV2',
        children: [
            { key: '/settings/profile', label: 'Profile' },
            {
                key: '/settings/advanced',
                label: 'Advanced',
                featureFlag: 'enableAdvancedSettings',
            },
        ],
    },
];

function MainMenu() {
    const { flags } = useFeatureFlags();
    const visibleMenuItems = filterByFeature(allMenuItems, flags);

    // Render menu với các item đã được lọc (bao gồm cả children)
    return <Menu items={visibleMenuItems} />;
}
```

### `createFetcher(options)`

Hàm factory để nhanh chóng tạo `fetcher` cho API. Hàm này sẽ tự động fetch dữ liệu từ API và chuyển đổi thành format `Record<string, boolean>`.

| Option              | Kiểu                          | Bắt buộc | Mô tả                                                                                      |
| :------------------ | :---------------------------- | :------- | :----------------------------------------------------------------------------------------- |
| `apiUrl`            | `string`                      | Có       | URL endpoint để fetch danh sách các flag.                                                  |
| `axiosInstance`     | `AxiosInstance`               | Không    | Một instance axios đã được cấu hình sẵn. Nếu không cung cấp, sẽ tạo một instance mặc định. |
| `transformResponse` | `(res: any) => GenericFlag[]` | Không    | Hàm để trích xuất mảng dữ liệu từ response. Mặc định: `(response) => response.data.data`   |
| `activeKey`         | `string`                      | Không    | Tên thuộc tính trong object flag để kiểm tra trạng thái active. Mặc định: `"isActive"`     |

**Lưu ý:**

-   Mỗi flag trong mảng cần có thuộc tính `code: string` để làm key trong object kết quả.
-   Thuộc tính được chỉ định bởi `activeKey` phải là kiểu `boolean`.

---

## 🔧 TypeScript

Thư viện được viết hoàn toàn bằng TypeScript và export các types sau:

```typescript
import type {
    Flags,
    FlagFetcher,
    FeatureFlagContextType,
    FeatureFlagProviderProps,
    FetcherOptions,
} from '@namphuongtechnologi/react-feature-flags';
```

---

## 📝 Ví dụ đầy đủ

Xem thêm các ví dụ chi tiết trong phần [Hướng dẫn nhanh](#-hướng-dẫn-nhanh) phía trên.

---

## 🧪 Testing và Publishing

### Kiểm tra build local

Trước khi publish, bạn nên test build và kiểm tra package local:

```bash
# Build thư viện
npm run build

# Tạo file .tgz để test local
npm run test:local
```

File `.tgz` sẽ được tạo trong thư mục gốc. Bạn có thể test package này trong một dự án khác:

```bash
# Trong dự án test
npm install /path/to/namphuongtechnologi-react-feature-flags-1.1.2.tgz
```

### Kiểm tra nội dung package

Kiểm tra xem những file nào sẽ được đưa lên npm:

```bash
# Xem danh sách files sẽ được publish
npm pack --dry-run
```

### Publish lên npm

#### 1. Đăng nhập vào npm

```bash
# Đăng nhập vào npm (lần đầu tiên)
npm login

# Kiểm tra tài khoản hiện tại
npm whoami
```

#### 2. Kiểm tra thông tin package

Đảm bảo các thông tin trong `package.json` đã chính xác:

-   `name`: Tên package (phải match với scope trên npm)
-   `version`: Version hiện tại
-   `description`: Mô tả package
-   `author`: Tác giả
-   `license`: Giấy phép
-   `repository`: Link repository (nếu có)

#### 3. Tăng version và build

Bạn có thể sử dụng các script có sẵn để tăng version tự động:

```bash
# Tăng patch version (1.1.2 -> 1.1.3)
npm run release:patch

# Tăng minor version (1.1.2 -> 1.2.0)
npm run release:minor

# Tăng major version (1.1.2 -> 2.0.0)
npm run release:major
```

Hoặc tăng version thủ công:

```bash
# Tăng version (sẽ tự động update package.json)
npm version patch   # hoặc minor, major

# Build lại sau khi tăng version
npm run build
```

#### 4. Publish lên npm

```bash
# Publish lên npm registry
npm publish --access public
```

**Lưu ý:** Với scoped package (`@namphuongtechnologi/react-feature-flags`), bạn cần thêm flag `--access public` để publish công khai.

#### 5. Xác nhận publish thành công

Kiểm tra package đã được publish:

```bash
# Xem thông tin package trên npm
npm view @namphuongtechnologi/react-feature-flags

# Hoặc truy cập trực tiếp
# https://www.npmjs.com/package/@namphuongtechnologi/react-feature-flags
```

### Workflow đầy đủ

```bash
# 1. Đảm bảo code đã được commit
git status

# 2. Chạy test (nếu có)
# npm test

# 3. Tăng version và build
npm run release:patch  # hoặc minor, major

# 4. Test package local (tùy chọn)
npm run test:local

# 5. Publish lên npm
npm publish --access public

# 6. Commit và push version mới
git add package.json package-lock.json
git commit -m "chore: bump version to x.x.x"
git push

# 7. Tạo git tag (nếu cần)
git tag v1.1.3
git push origin v1.1.3
```

### Troubleshooting

**Lỗi: "You do not have permission to publish"**

-   Đảm bảo bạn đã đăng nhập đúng tài khoản có quyền publish
-   Kiểm tra xem package name đã được sử dụng bởi người khác chưa
-   Với scoped package, đảm bảo bạn là owner của organization `@namphuongtechnologi`

**Lỗi: "Package version already exists"**

-   Tăng version mới trước khi publish
-   Kiểm tra version hiện tại: `npm view @namphuongtechnologi/react-feature-flags version`

**Lỗi: "Invalid package name"**

-   Đảm bảo package name tuân theo quy tắc đặt tên của npm
-   Scoped package phải có format: `@scope/package-name`

---

## 🤝 Đóng góp

Mọi đóng góp đều được chào đón! Vui lòng mở issue hoặc pull request trên GitHub.

---

## 📄 License

MIT