npm package: @macellan/dux-core
Description: Dux core for building and navigating rich dashboard apps
Latest version: 0.3.0

## Introduction

Dux is a simple boilerplate reducing tamplate for creating dashboards written in [TypeScript](https://www.typescriptlang.org/) and [React](https://beta.reactjs.org/). While the [Dux Core](https://www.npmjs.com/package/@macellan/dux-core) doesn't have much dependencies, [Dux Components](https://www.npmjs.com/package/@macellan/dux-components) package heavily relies on [Mantine](https://mantine.dev/) as a UI kit and hook library of choice. Dux is unopinonated about your state management of choice.


## Installation

To get started with Dux, install the core and components package to your existing app or create a template with [Codux](#) tool (coming soon).

<sub>Using npm:</sub>

```bash
npm install @macellan/dux-core @macellan/dux-components
```

<sub>Using yarn:</sub>

```bash
yarn add @macellan/dux-core @macellan/dux-components
```

<sub>Using pnpm:</sub>

```bash
pnpm install @macellan/dux-core @macellan/dux-components
```

## Usage

To start using the Dux Core you should create a new dux instance, best place to this would be a config file or your `main.tsx` file where you initialize your react app.

> This documentation will examplify the code in TypeScript but things shouldn't change much for JavaScript.

```typescript
import { createDux } from '@macellan/dux-core';

const app = createDux();
```

Dux expects some essential options for creating an instance, like your views. Under the hood Dux uses `react-router` to implement client side routing. You can pass a router object to the `views` property as a router. This object can be extended with `allowedRoles` propery to do authenticated routing. Dux provides 3 builtin roles to give you a headstart and handle internal mechanisms. These are;

- `*` Anyone can access this route.
- `authenticated` Only users that are authenticated with given Dux primitive can access this route.
- `unauthenticated` Only users that are unauthenticated with given Dux primitive can access this route. This is useful for creating a login or signup page that only the users that are not logged in can see.

```tsx
import { createDux } from '@macellan/dux-core';

const app = createDux({
  views: [
    {
      path: '/',
      element: <div>This is the home page</div>,
      // Anyone can see this page
      allowedRoles: ['*'],
    },
    {
      path: '/dashboard',
      element: <div>This is the dashboard page</div>,
      // Only logged in users can see this page
      allowedRoles: ['authenticated'],
    },
    {
      path: '/admin',
      element: <div>This is the admin page</div>,
      // You can define your arbitrary roles as well
      // Only logged in users that has the admin role can see this page
      allowedRoles: ['authenticated', 'admin'],
    },
    {
      path: '/login',
      element: <div>This is the login page</div>,
      // Only logged out users can see this page
      allowedRoles: ['unauthenticated'],
    },
  ],
});
```

> `allowedRoles` property only works at the root level so you have to manage your routes accordingly.

Another neccessary property a Dux instance needs is the initial authentication state. Implementing the authentication is on you but we're planning a `@macellan/dux-auth` package to speed up this process as well.

```tsx
import { createDux } from '@macellan/dux-core';

const app = createDux({
  views: [
    {
      path: '/',
      element: <div>This is the home page</div>,
      // Anyone can see this page
      allowedRoles: ['*'],
    },
    {
      path: '/dashboard',
      element: <div>This is the dashboard page</div>,
      // Only logged in users can see this page
      allowedRoles: ['authenticated'],
    },
    {
      path: '/admin',
      element: <div>This is the admin page</div>,
      // You can define your arbitrary roles as well
      // Only logged in users that has the admin role can see this page
      allowedRoles: ['authenticated', 'admin'],
    },
    {
      path: '/login',
      element: <div>This is the login page</div>,
      // Only logged out users can see this page
      allowedRoles: ['unauthenticated'],
    },
  ],
  auth: {
    // For kepping track on if the user is logged in. Implement your own logic here
    initialState: false,
    // You should fetch your user before reaching here and provide your roles
    initialRoles: [],
  },
});
```

`initialRoles` accepts a `RoleObject` array, this is a simple object with a name and an identifier properties. You can provide your arbitrary roles here.

```typescript
export interface RoleObject {
  identifier: string;
  name: string;
}
```

With that, you are ready to render your app. Simply create your app provider and render it with react.

```tsx
// This is an async funtion that you need to wait
app.createProvider().then(DuxProvider => {
  ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
    <React.StrictMode>
      <DuxProvider />
    </React.StrictMode>
  );
});
```

## Authorization

At some point you may need an authorization manager. For instance you might want to redirect unauthroized pages or simply show an "unauthorized" view. `app` instance provides a method to manage your views. You basically provide a function that would be called when Dux encounters an unauthorized view visit.

You get the view and the roles the user has in this callback function. You need to provide an object that has 2 properties;

- redirects for redirection
- replaces for displaying a custom view

You should pass null for at least one of these properties but redirection will take precedence otherwise.

```tsx
app.configureAuthorizationManager((view: ViewObject, roles: RoleObject[]) => {
  // Let's redirect the client if the user is 'unauthenticated' 
  if (roles.map(role => role.identifier).includes('unauthenticated')) {
    return {
      redirects: "/login",
      replaces: null,
    };
  }

  return {
    redirects: null,
    replaces: null,
  };
})
```

We might want to show an "unauthorized" view for other pages.


```tsx
app.configureAuthorizationManager((view: ViewObject, roles: RoleObject[]) => {
  if (roles.map(role => role.identifier).includes('unauthenticated')) {
    return {
      redirects: "/login",
      replaces: null,
    };
  }

  // Let's first exlude views like login and signup
  const authRedirectRoutes = ["/login", "/signup"]
  if (authRedirectRoutes.includes(view.path as string)) {
    return {
      redirects: "/",
      replaces: null,
    };
  }

  // Otherwise show the unauthorized view
  return {
    redirects: null,
    replaces: () => <div>You are not authorized to see this page</div>,
  };
});
```

Dux provides a simple factory for this simple usage. Simply pass your primary authentication page, your non authenticated view paths, default redirect and unauthorized view to this function and you're good to go.

```tsx
import { createDux, defaultAuthorizationManager } from "@macellan/dux-core"

// ...

app.configureAuthorizationManager(
  defaultAuthorizationManager('/login', ['/login', '/signup'], '/', () => <div>You are not authorized to see this page</div>)
);
```

## Authentication

Authentication is quite simple with Dux. Simply use the `useAuth` hook and login and logout your user.

> Dux Core does NOT handle the storage or management of the user, that is on you. Dux simply governs the auth state and the roles.

```tsx
function Login() {
  // use your app instance here
  const { login } = app.useAuth()

  const handleLogin = () => {
    // Provide your users roles as a RoleObject array. 
    // You can pass an empty array for basic users
    login([{ identifier: "admin", name: "Admin" }]);
  }

  return // ...
}
```

```tsx
function UserMenu() {
  // use your app instance here
  const { logout } = app.useAuth()

  const handleLogout = () => {
    logout();
  }

  return // ...
}
```

These functions will automatically redirect the user.