npm package: @pagamio/frontend-commons-lib
Description: Pagamio library for Frontend reusable components like the form engine and table container
Author: Pagamio Dev Team
Latest version: 0.8.293

# pagamio-frontend-commons-library

A reusable React component library styled with Tailwind CSS, designed to streamline your frontend development process.

## :rocket: **Features**

- **A Form Engine**: A package with all possible input components that can be used to build a form. This helps render
  forms in your project.
- **Reusable Components**: A collection of customizable components like DateInput, TextInput, and more.
- **Tailwind CSS**: Utility-first CSS framework for rapid UI development.
- **TypeScript Support**: Fully typed components for enhanced developer experience.
- **Flexible Imports**: Import components individually or collectively.
- **Peer Dependencies**: Ensures compatibility with React and React DOM versions.
- **API Client**: Robust API integration with authentication support, SWR data fetching, and mocking capabilities.

## :package: **Installation**

Install the library via Yarn:

```bash
yarn add pagamio-frontend-commons-lib
```

## :art: Usage

- **Importing Styles**

  Import the compiled Tailwind CSS styles into your application's entry point (e.g., index.js or App.js):

  `import 'pagamio-frontend-commons-lib/lib/styles.css';`

- **Importing Components**

  You can import components individually or collectively.

- a. Named Imports

  Import multiple components from the main entry point:

  `import { DateInput, TextInput } from 'pagamio-frontend-commons-lib';`

### :gear: Environment Variables

Some utilities (for example the `useImageUpload` hook and `ImageUploader` component) require an API endpoint that issues presigned upload URLs. Make sure your host application exposes the following public environment variable before using those helpers:

| Variable | Description |
| --- | --- |
| `NEXT_PUBLIC_UPLOAD_URL_ENDPOINT` | HTTP endpoint that returns presigned URLs for file uploads. |

```bash
# .env (per app)
NEXT_PUBLIC_UPLOAD_URL_ENDPOINT=https://<your-upload-service>/upload-url
```

This value is resolved at runtime;

## :shield: **RBAC Module**

The Role-Based Access Control (RBAC) module provides a flexible system for implementing permission-based access control in your applications.

### **Features**

- Generic TypeScript implementation that works with any permission system
- Flexible configuration that can be initialized once at application startup
- Pure utility functions that can be used anywhere in your application
- React hooks for convenient use in components
- Support for role-based and permission-based access control
- Type-safe API with generics for custom user and permission types

### **Core Components**

#### **Initialization**

Initialize the RBAC system with your application-specific configuration:

```typescript
import { initializeRBAC } from 'pagamio-frontend-commons-lib/rbac';
import { Permissions } from './permissions';

// Define your permission enum
enum Permissions {
  ALL = 'ALL',
  VIEW_DASHBOARD = 'VIEW_DASHBOARD',
  MANAGE_USERS = 'MANAGE_USERS',
  // ... other permissions
}

// Define your RBAC configuration
const rbacConfig = {
  'ADMIN': [Permissions.ALL],
  'MANAGER': [Permissions.VIEW_DASHBOARD, Permissions.MANAGE_USERS],
  'USER': [Permissions.VIEW_DASHBOARD],
};

// Initialize RBAC with your configuration
initializeRBAC({
  rbacConfig,
  allPermissionValue: Permissions.ALL,
  allPermissions: Object.values(Permissions),
  roleKey: 'roleName' // The property in your user object that contains the role
});
```

#### **Utility Functions**

Use the RBAC utility functions to check permissions and roles:

```typescript
import { hasPermission, hasRole, getUserPermissions } from 'pagamio-frontend-commons-lib/rbac';
import { Permissions } from './permissions';

// Check if a user has a specific permission
const canViewDashboard = hasPermission(user, Permissions.VIEW_DASHBOARD);

// Check if a user has a specific role
const isAdmin = hasRole(user, 'ADMIN');

// Get all permissions for a user
const userPermissions = getUserPermissions(user);
```

#### **React Hooks**

Use the RBAC hooks in your React components:

```typescript
import { useHasPermission, useHasRole } from 'pagamio-frontend-commons-lib/rbac';
import { useAuth } from 'pagamio-frontend-commons-lib/auth';
import { Permissions } from './permissions';

function Dashboard() {
  const { user } = useAuth();
  const canManageUsers = useHasPermission(user, Permissions.MANAGE_USERS);
  
  return (
    <div>
      <h1>Dashboard</h1>
      {canManageUsers && <UserManagement />}
    </div>
  );
}
```

## :globe_with_meridians: **API Module**

The API module provides a robust system for making API requests with built-in authentication, caching, and error
handling capabilities.

### **Features**

- TypeScript support with generic types for type-safe API operations
- Authentication integration with token management
- SWR integration for data fetching, caching, and revalidation
- Support for RESTful operations (GET, POST, PUT, PATCH, DELETE)
- Pagination support for large datasets
- Configurable retry logic and timeout handling
- Request and response interceptors
- Error handling and logging
- Mock API support for testing and development

### **Core Components**

#### **ApiClient**

The `ApiClient` class provides a flexible HTTP client with authentication support, retry logic, and error handling.

```typescript
import { ApiClient, createApiClient } from 'pagamio-frontend-commons-lib';

// Create a client with your custom auth configuration
const apiClient = createApiClient<MyAuthConfig>({
  baseURL: 'https://api.example.com',
  tokenManager: tokenManager,
  defaultHeaders: {
    'Content-Type': 'application/json',
  },
  timeout: 5000,
  retries: 2,
});

// Making API calls
const data = await apiClient.get<MyResponseType>('/users');
const newUser = await apiClient.post<UserResponse>('/users', { name: 'John', email: 'john@example.com' });
```

#### **SWR Integration**

The API module includes SWR hooks for efficient data fetching with caching, revalidation, and focus refetching.

```typescript
import { useApiSWR, usePaginatedApiSWR } from 'pagamio-frontend-commons-lib';

// Basic data fetching with SWR
function UserProfile({ userId }) {
  const { data, error, isLoading } = useApiSWR<UserData>(`/users/${userId}`);

  if (isLoading) return <div>Loading
...
  </div>;
  if (error) return <div>Error
  loading
  user
  data < /div>;

  return <div>Hello, { data.name }! < /div>;
}

// Paginated data fetching
function UserList() {
  const { data, error, isLoading } = usePaginatedApiSWR<User>('/users', {
    params: { page: 0, size: 10 }
  });

  if (isLoading) return <div>Loading
  users
...
  </div>;

  return (
    <div>
      <h2>Users({ data.totalElements }) < /h2>
    < ul >
    {
      data.content.map(user => <li key = { user.id } > { user.name } < /li>)}
        < /ul>
        < /div>
      );
    }
```

#### **API Context Provider**

The `ApiProvider` component makes the API client available throughout your application.

```typescript
import { ApiProvider, createApiClient } from 'pagamio-frontend-commons-lib';

// Create API client
const apiClient = createApiClient<MyAuthConfig>({
  baseURL: 'https://api.example.com',
  tokenManager: tokenManager,
});

// Wrap your application with the provider
function App() {
  return (
    <ApiProvider apiClient = { apiClient } >
      <YourApplication / >
      </ApiProvider>
  );
}
```

#### **API Mutations**

The API module provides hooks for performing mutations (create, update, delete operations).

```typescript
import { useApiMutation, useApiSWRWithMutation } from 'pagamio-frontend-commons-lib';

// Using the mutation hook
function CreateUserForm() {
  const mutation = useApiMutation<UserResponse>();

  const handleSubmit = async (userData) => {
    try {
      const newUser = await mutation.post('/users', userData);
      alert(`User ${newUser.name} created successfully!`);
    } catch (error) {
      console.error('Failed to create user:', error);
    }
  };

  return <form onSubmit = { handleSubmit } > {/* form fields */ } < /form>;
}

// Combined SWR and mutation hook
function EditUserProfile({ userId }) {
  const { data, error, isLoading, mutate } = useApiSWRWithMutation<UserData>(
    `/users/${userId}`
  );

  const handleUpdate = async (updatedData) => {
    try {
      await mutate.patch(`/users/${userId}`, updatedData);
      alert('Profile updated successfully!');
    } catch (error) {
      console.error('Failed to update profile:', error);
    }
  };

  if (isLoading) return <div>Loading
...
  </div>;

  return <ProfileForm user = { data }
  onSubmit = { handleUpdate }
  />;
}
```

### **Mocking API Requests**

The API module supports mocking API requests for testing and development.

```typescript
import { ApiProvider, createApiClient, MockConfig } from 'pagamio-frontend-commons-lib';

// Define mock configurations
const mockConfig: MockConfig[] = [
  {
    path: '/users',
    method: 'GET',
    response: [
      { id: 1, name: 'John Doe', email: 'john@example.com' },
      { id: 2, name: 'Jane Smith', email: 'jane@example.com' }
    ]
  },
  {
    path: '/users',
    method: 'POST',
    params: { name: 'Alice', email: 'alice@example.com' },
    response: { id: 3, name: 'Alice', email: 'alice@example.com' }
  }
];

// Enable mocking in the provider
function TestApp() {
  return (
    <ApiProvider
      apiClient = { apiClient }
  mocked = { true }
  mockConfig = { mockConfig } >
    <YourApplication / >
    </ApiProvider>
)
  ;
}
```

### **Authentication Integration**

The API client integrates with the token manager for authentication.

```typescript
import { createApiClient, createTokenManager } from 'pagamio-frontend-commons-lib';

// Create a token manager
const tokenManager = createTokenManager<MyAuthConfig>({
  baseUrl: 'https://api.example.com',
  refreshEndpoint: '/auth/refresh',
  cookieOptions: {
    secure: true,
    sameSite: 'strict'
  }
});

// Create API client with authentication
const apiClient = createApiClient<MyAuthConfig>({
  baseURL: 'https://api.example.com',
  tokenManager: tokenManager,
  onUnauthorized: () => {
    // Handle unauthorized access (e.g., redirect to login)
    window.location.href = '/login';
  }
});
```

### **Advanced Configuration**

#### **Custom Request/Response Handling**

```typescript
const apiClient = createApiClient<MyAuthConfig>({
  baseURL: 'https://api.example.com',
  tokenManager: tokenManager,
  onRequest: async (config) => {
    // Add custom headers or modify request configuration
    config.headers = {
      ...config.headers,
      'X-Custom-Header': 'CustomValue'
    };
    return config;
  },
  onResponse: async (response, data) => {
    // Transform or process response data
    return data.results || data;
  },
  onError: async (error) => {
    // Log or handle errors
    console.error(`API Error (${error.status}):`, error.message);
  }
});
```

#### **Pagination and Filtering**

```typescript
// Custom pagination params
const { data } = useApiSWR<UserListResponse>('/users', {
  params: {
    page: 0,
    size: 25,
    sortBy: 'createdAt',
    sortDir: 'desc',
    name: 'John'
  }
});
```

### **TypeScript Integration**

The API module is fully typed with TypeScript, providing type safety and better developer experience.

```typescript
// Define your auth configuration
interface MyAuthConfig extends CustomAuthConfig {
  UserInfo: {
    id: string;
    username: string;
    email: string;
    roles: string[];
  };
  TokenInfo: {
    token: string;
    expiresIn: number;
  };
  Credentials: {
    username: string;
    password: string;
  };
}

// Use your custom types with the API client
const apiClient = createApiClient<MyAuthConfig>({
  baseURL: 'https://api.example.com',
  tokenManager: tokenManager
});

// Type-safe API calls
interface Product {
  id: string;
  name: string;
  price: number;
}

const { data: products } = useApiSWR<Product[]>('/products');
```

### **Best Practices**

1. **Centralize API Configuration**: Create a central API configuration file that sets up the API client and exports
   hooks for use throughout your application.

2. **Use TypeScript**: Leverage TypeScript definitions for type-safe API calls and better developer experience.

3. **Handle Loading and Error States**: Always handle loading and error states in your components when using API hooks.

4. **Implement Proper Error Handling**: Configure error handling with the `onError` callback and handle errors
   appropriately in your UI.

5. **Use Mocking for Development**: Enable mocking during development to work without a backend or to test specific
   scenarios.

6. **Optimize Cache Invalidation**: Use SWR's `mutate` function to keep your data fresh and update the UI after
   mutations.

7. **Set Appropriate Timeouts**: Configure request timeouts based on the expected response time of your API endpoints.

## :globe_with_meridians: **Translations**

The library provides a complete translation system that supports internationalization for your applications.

### **Languages Currently Supported**

The library comes with built-in support for the following languages:

- English (en)
- Spanish (es)
- French (fr)
- Portuguese (pt)

### **Implementation Guide**

Follow these steps to implement translations in your project:

#### 1. **Setup Translation Provider**

Wrap your application with the `TranslationProvider`:

```tsx
import { TranslationProvider } from 'pagamio-frontend-commons-lib';

function App() {
  return (
    <TranslationProvider
      defaultLocale="en"
      loadPath="/translations" // Path to your translation files
    >
      <YourApplicationComponents />
    </TranslationProvider>
  );
}
```

Alternatively, if you're using the `AppLayout` component:

```tsx
import { AppLayout } from 'pagamio-frontend-commons-lib';

function App() {
  return (
    <AppLayout
      // other props...
      enableTranslations={true}
      translationConfig={{
        defaultLocale: 'en',
        loadPath: '/translations',
      }}
    >
      {/* Your content */}
    </AppLayout>
  );
}
```

#### 2. **Create Translation Files**

Create JSON files for each language in your project:

```
/public
  /translations
    en.json
    es.json
    fr.json
    pt.json
```

Example of a translation file (`en.json`):

```json
{
  "common": {
    "save": "Save",
    "cancel": "Cancel",
    "submit": "Submit"
  },
  "auth": {
    "login": "Login",
    "register": "Register",
    "forgotPassword": "Forgot Password?"
  }
}
```

#### 3. **Add i18next-scanner Config**

Create an `i18next-scanner.config.js` in your project root:

```js
module.exports = {
  input: ['src/**/*.{js,jsx,ts,tsx}', '!src/**/*.test.{js,jsx,ts,tsx}', '!**/node_modules/**'],
  output: './public/translations/',
  options: {
    debug: true,
    func: {
      list: ['t', 'tLib'],
      extensions: ['.js', '.jsx', '.ts', '.tsx'],
    },
    lngs: ['en', 'es', 'fr', 'pt'],
    defaultLng: 'en',
    defaultValue: function(lng, ns, key) {
      return key;
    },
    resource: {
      loadPath: 'public/translations/{{lng}}.json',
      savePath: '{{lng}}.json',
      jsonIndent: 2,
      lineEnding: '\n',
    },
    removeUnusedKeys: false,
    nsSeparator: false,
    keySeparator: '.',
  },
};
```

#### 4. **Add Extraction Script to package.json**

```json
"scripts": {
"extract-translations": "i18next-scanner --config i18next-scanner.config.js 'src/**/*.{js,jsx,ts,tsx}'"
}
```

#### 5. **Install Required Dependencies**

Install the necessary dev dependencies:

```bash
yarn add -D i18next-scanner
```

Or if you're using npm:

```bash
npm install --save-dev i18next-scanner
```

#### 6. **Use Translations in Components**

```tsx
import { useTranslation } from 'pagamio-frontend-commons-lib';

function YourComponent() {
  const { t } = useTranslation();

  return (
    <div>
      <h1>{t('common.title', 'Default Title')}</h1>
      <p>{t('common.description', 'This is a default description')}</p>
      <button>{t('common.save', 'Save')}</button>
    </div>
  );
}
```

#### 7. **Use Library Translations**

The library provides common translations for frequently used UI elements:

```tsx
import { useLibTranslations } from 'pagamio-frontend-commons-lib';

function YourComponent() {
  const { tLib } = useLibTranslations();

  return (
    <div>
      <button>{tLib('save')}</button>
      <button>{tLib('cancel')}</button>
      <button>{tLib('submit')}</button>
    </div>
  );
}
```

#### 8. **Add Locale Switcher**

Add a language switcher to allow users to change the application language:

```tsx
import { LocaleSwitcher } from 'pagamio-frontend-commons-lib';

function Header() {
  return (
    <header>
      <nav>
        {/* Your other navigation elements */}
        <LocaleSwitcher />
      </nav>
    </header>
  );
}
```

You can customize language display names:

```tsx
<LocaleSwitcher
  localeNames={{
    en: 'English',
    es: 'Español',
    fr: 'Français',
    pt: 'Português'
  }}
/>
```

#### 9. **Run the Extraction Script**

Extract all translation keys from your application:

```bash
yarn extract-translations
```

This will scan your code for translation keys and update your translation files.

### **Advanced Configuration**

#### **Custom Locale Detector**

You can configure the translation system to detect the browser's locale:

```tsx
import { TranslationProvider, detectBrowserLocale } from 'pagamio-frontend-commons-lib';

function App() {
  const browserLocale = detectBrowserLocale(); // Returns 'en', 'es', etc.

  return (
    <TranslationProvider
      defaultLocale={browserLocale}
      loadPath="/translations"
    >
      <YourApplicationComponents />
    </TranslationProvider>
  );
}
```

#### **Direct Loading of Translation Data**

Instead of loading translations from files, you can provide them directly:

```tsx
import { TranslationProvider } from 'pagamio-frontend-commons-lib';

const translations = [
  {
    locale: 'en',
    messages: {
      common: {
        save: 'Save',
        cancel: 'Cancel'
      }
    }
  },
  {
    locale: 'es',
    messages: {
      common: {
        save: 'Guardar',
        cancel: 'Cancelar'
      }
    }
  }
];

function App() {
  return (
    <TranslationProvider
      defaultLocale="en"
      localeData={translations}
    >
      <YourApplicationComponents />
    </TranslationProvider>
  );
}
```