# @induseuro-labs/auth-ui

A beautiful, reusable authentication UI template with RBAC (Role-Based Access Control) support for **Next.js and React** applications. Perfect for quickly adding professional login, signup, and password reset flows to any project.

## Features

### Authentication UI (MUI)

- **Forms**: `LoginForm`, `SignupForm`, `ForgotPasswordForm`, `ResetPasswordForm` — email/password, Google entry point, OTP tab on login, forgot-password email request, and **token-based “set new password”** (`?token=` from email links).
- **Full pages**: `LoginPage`, `SignupPage`, `ForgotPasswordPage`, `ResetPasswordPage` — same forms inside `AuthPageLayout` with left **image or slider** and right form.
- **Layout primitive**: `AuthPageLayout` for custom compositions.

### Payment UI

- **Component**: `PaymentGatewayChoice` — lets users choose between gateways (e.g., Stripe, Razorpay) and redirects them directly to the generated checkout session URL.
- **Full page**: `PaymentGatewayChoicePage` — full page layout wrapping the payment choice component.
- **Billing**: `BillingOverview` — tabs for subscription, orders, payments, and invoices; Razorpay cancel and Stripe customer portal.
- **Full page**: `BillingOverviewPage` — full page layout for billing.
- **Backend integration**: `createPaymentCheckoutHandlers` (alias `createPaymentApiHandlers`) for checkout, payment history, commerce lists, subscription cancel, and Stripe portal.

### Session and state

- **`AuthProvider` + `useAuth()`** — login, signup, Google, OTP login, refresh, logout, send reset email, **complete password reset** (`token` + `newPassword`).
- **Persisted session** in `localStorage` only (not `sessionStorage`; default key `auth_data` via `AUTH_STORAGE_KEY`) with `jwtToken`, `refreshToken`, `username`, `id`, `roles` inside `data`.
- **Cross-tab sync** when the session key changes in another tab.

### Built-in routing (no React Router required)

- **`AuthRoutes`** — serves `/login`, `/signup`, `/forgot-password`, `/reset-password`, `/dashboard`, `/unauthorized` using the browser history API.
- **`lockToAuthRoutes`** — unknown paths redirect to `/login` or `/dashboard` depending on auth; **`allowedPaths`** for extra public routes.

### RBAC

- Roles: **admin**, **user**, **moderator** (`UserRole` enum).
- **`ProtectedRoute`** — gate by authentication and optional `requiredRoles`.
- **`useRole`** — convenience helpers (`isAdmin`, etc.).

### Theming

- **`createMuiTheme`** / **`defaultTheme`** — dark-friendly defaults; works with your own MUI `ThemeProvider`.

### Backend integration (no UI)

- **`createAuthApiHandlers`** — builds `AuthProvider` callbacks from a **`baseURL`** + default REST paths (JSON `fetch`). Override paths or merge with custom `onLogin`, etc.
- **HTTP constants** — `AUTH_STORAGE_KEY` (default `auth_data`), deprecated alias `AUTH_TOKEN_STORAGE_KEY`, `AUTH_HEADER_KEYS` (`X-tenant`, `Authorization`, …), `AUTH_CONTENT_TYPES` for consistent headers.
- **`createAuthAxiosClient`** — **Axios** instance factory with tenant/accept headers, bearer from the **same** `localStorage` key as `AuthProvider` (default `auth_data`), and explicit **`refreshToken()`** via `POST /api/v1/auth/refreshToken` with `{ refreshToken }` (no automatic refresh-and-retry on failed resource requests). Clears auth on refresh failures **400 / 401 / 403** (or `success: false`), not on network or 5xx-only errors. Paths default to `/api/v1/auth/*`. **`axios` is a peer dependency** — install it alongside this package.

### Utilities

- **`useRouter` / `createDefaultRouter`** — small history-based router for SPAs not using Next.js.

## Installation

```bash
npm install @induseuro-labs/auth-ui @mui/material @mui/icons-material @emotion/react @emotion/styled
# or
yarn add @induseuro-labs/auth-ui @mui/material @mui/icons-material @emotion/react @emotion/styled
# or
pnpm add @induseuro-labs/auth-ui @mui/material @mui/icons-material @emotion/react @emotion/styled
```

**Peer dependency:** install **axios** in your app (required for `createAuthAxiosClient`):

```bash
npm install axios
```

**Note:** Next.js is optional. The package works with both Next.js and regular React applications.

## Quick Start

### 1. Setup MUI Theme Provider

Wrap your app with MUI's ThemeProvider:

```tsx
// app/layout.tsx (Next.js) or App.tsx (React)
import { ThemeProvider } from "@mui/material/styles";
import CssBaseline from "@mui/material/CssBaseline";
import { createMuiTheme } from "@induseuro-labs/auth-ui";

const theme = createMuiTheme();

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <ThemeProvider theme={theme}>
          <CssBaseline />
          {children}
        </ThemeProvider>
      </body>
    </html>
  );
}
```

### 2. Wrap your app with AuthProvider

```tsx
// app/layout.tsx (Next.js) or App.tsx (React)
import { AuthProvider } from "@induseuro-labs/auth-ui";

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <AuthProvider
          onLogin={async (credentials) => {
            // Your login API call
            const response = await fetch("/api/login", {
              method: "POST",
              body: JSON.stringify(credentials),
            });
            return response.json();
          }}
          onSignup={async (data) => {
            // Your signup API call
            const response = await fetch("/api/signup", {
              method: "POST",
              body: JSON.stringify(data),
            });
            return response.json();
          }}
          onGoogleLogin={async (payload) => {
            const response = await fetch("/api/auth/googleLogin", {
              method: "POST",
              headers: { "Content-Type": "application/json" },
              body: JSON.stringify({ idToken: payload?.idToken }),
            });
            if (!response.ok) throw new Error("Google login failed");
            return response.json();
          }}
        >
          {children}
        </AuthProvider>
      </body>
    </html>
  );
}
```

### 3. Create Login Page

```tsx
// app/login/page.tsx
"use client";

import { LoginPage } from "@induseuro-labs/auth-ui";

export default function LoginPage() {
  return (
    <LoginPage
      redirectTo="/dashboard"
      media="/images/auth-left.jpg" // single image mode
    />
  );
}
```

### 3A. Use Built-in Routes (No app routes required)

If you do not want to define `/login`, `/signup`, `/forgot-password`, `/reset-password` in each project, use `AuthRoutes` directly:

```tsx
import { AuthProvider, AuthRoutes } from "@induseuro-labs/auth-ui";

export default function App() {
  return (
    <AuthProvider>
      <AuthRoutes
        // default paths:
        // /login, /signup, /forgot-password, /reset-password, /dashboard, /unauthorized
        lockToAuthRoutes
        media={["/images/slide-1.jpg", "/images/slide-2.jpg"]}
        mediaMode="auto"
        dashboardComponent={<div>Dashboard content</div>}
      />
    </AuthProvider>
  );
}
```

With `lockToAuthRoutes={true}` (default), unknown routes are redirected:

- Not logged in -> `/login`
- Logged in -> `/dashboard`

### Full-page layout with image/slider

You can use ready-made full pages for login, signup, and forgot password.

```tsx
import { LoginPage, SignupPage, ForgotPasswordPage, ResetPasswordPage } from '@induseuro-labs/auth-ui'

// Single image
<LoginPage media="/images/auth.jpg" mediaMode="single" />

// Multiple images -> slider (auto when mediaMode="auto")
<SignupPage
  media={[
    '/images/slide-1.jpg',
    '/images/slide-2.jpg',
    '/images/slide-3.jpg',
  ]}
  mediaMode="auto"
  sliderIntervalMs={5000}
/>

// With rich slide content
<ForgotPasswordPage
  media={[
    {
      src: '/images/recover.jpg',
      title: 'Recover Account Access',
      description: 'Reset password and continue securely.',
    },
    {
      src: '/images/security.jpg',
      title: 'Your Security Matters',
      description: 'Use strong passwords and keep your account protected.',
    },
  ]}
/>

<ResetPasswordPage />
```

### 4. Create Signup Page

```tsx
// app/signup/page.tsx (Next.js) or pages/Signup.tsx (React)
"use client"; // Only needed in Next.js

import { SignupForm } from "@induseuro-labs/auth-ui";

export default function SignupPage() {
  return (
    <div className="min-h-screen flex items-center justify-center bg-slate-900 p-8">
      <SignupForm redirectTo="/dashboard" />
    </div>
  );
}
```

### 5. Protect Routes

```tsx
// app/dashboard/page.tsx (Next.js) or pages/Dashboard.tsx (React)
"use client"; // Only needed in Next.js

import { ProtectedRoute, UserRole } from "@induseuro-labs/auth-ui";

export default function DashboardPage() {
  return (
    <ProtectedRoute>
      <div>Your protected content</div>
    </ProtectedRoute>
  );
}

// Admin only page
export default function AdminPage() {
  return (
    <ProtectedRoute requiredRoles={[UserRole.ADMIN]}>
      <div>Admin only content</div>
    </ProtectedRoute>
  );
}
```

### 6. Payment Gateway Selection

```tsx
// app/payment-choose/page.tsx (Next.js) or pages/PaymentChoose.tsx (React)
"use client";

import { PaymentGatewayChoice, createPaymentCheckoutHandlers } from "@induseuro-labs/auth-ui";

const handlers = createPaymentCheckoutHandlers({
  baseURL: "https://your-api.example.com",
});

export default function PaymentChoosePage() {
  return (
    <div className="min-h-screen flex items-center justify-center bg-slate-900 p-8">
      <PaymentGatewayChoice
        gateways={['stripe', 'razorpay']}
        planId={1}
        onCreateCheckout={(payload) => handlers.createCheckoutSession(payload)}
        // By default, redirects directly to the checkout URL returned by the API
      />
    </div>
  );
}
```

### 7. Billing (subscription, orders, payments, invoices)

```tsx
"use client";

import {
  BillingOverview,
  createPaymentCheckoutHandlers,
  AUTH_STORAGE_KEY,
  AUTH_HEADER_KEYS,
  readTokensFromLocalStorage,
} from "@induseuro-labs/auth-ui";

const pay = createPaymentCheckoutHandlers({
  baseURL: process.env.NEXT_PUBLIC_AUTH_API_BASE_URL ?? "",
  getHeaders: () => {
    const t = readTokensFromLocalStorage(AUTH_STORAGE_KEY);
    return t?.jwtToken ? { [AUTH_HEADER_KEYS.authorization]: `Bearer ${t.jwtToken}` } : {};
  },
});

export default function BillingPage() {
  return (
    <BillingOverview
      onLoadSubscription={() => pay.getCurrentSubscription()}
      onLoadOrders={() => pay.listOrders()}
      onLoadPayments={() => pay.getPaymentHistory()}
      onLoadInvoices={() => pay.listInvoices()}
      onDownloadInvoice={(inv) => pay.downloadInvoice(inv.id)}
      onCancelRazorpaySubscription={() => pay.cancelRazorpaySubscription()}
      onOpenStripePortal={async () => (await pay.createPortalSession()).url}
    />
  );
}
```

Override `paths` on the handler if your app uses different routes for `getCurrentSubscription` or `listOrders`.

## Backend integration

### Environment variables (consumer app)

Add these in the app that uses the package (`.env.local`), not in the published package source:

```env
NEXT_PUBLIC_GOOGLE_CLIENT_ID=YOUR_CLIENT_ID
NEXT_PUBLIC_AUTH_API_BASE_URL=http://localhost:8080
NEXT_PUBLIC_AUTH_TENANT=dev
```

If you are testing inside this repository demo app, place them in `auth-ui-library/.env.local`.

### Fetch-based handlers for `AuthProvider`

If your backend matches the default JSON routes (see `createAuthApiHandlers` defaults), you can avoid repeating `fetch` boilerplate. The Google default endpoint is `/api/auth/googleLogin` and expects `{ idToken }`. **`createAuthApiHandlers` does not call a logout URL** — `logout()` only clears `localStorage` and session state. Redirect users to login when they have no session by wrapping protected pages with **`ProtectedRoute`** (or your own guard): when there is no token/session, `isAuthenticated` is false and **`redirectTo`** (default `/login`) runs.

```tsx
import { AuthProvider, createAuthApiHandlers } from "@induseuro-labs/auth-ui";

const api = createAuthApiHandlers({
  baseURL: process.env.NEXT_PUBLIC_AUTH_API_BASE_URL ?? "",
  tenant: process.env.NEXT_PUBLIC_AUTH_TENANT,
  getGoogleIdToken: async () => {
    // Return the Google ID token from your frontend Google SDK flow.
    const idToken = await getGoogleIdTokenFromYourApp();
    return idToken;
  },
  paths: {
    // optional overrides, e.g. login: '/api/v1/auth/login'
  },
});

export function Root({ children }: { children: React.ReactNode }) {
  return (
    <AuthProvider
      {...api}
      // merge or override any single handler:
      // onLogin={customLogin}
      storageKey="auth_data"
    >
      {children}
    </AuthProvider>
  );
}
```

### Axios client + shared header constants

Use this when you want a shared Axios instance for **app APIs** (not only auth forms), with **token refresh** and tokens in **`localStorage`** under the **same** key as `AuthProvider` (default `AUTH_STORAGE_KEY` / `auth_data`):

```tsx
import {
  AUTH_STORAGE_KEY,
  AUTH_HEADER_KEYS,
  AUTH_CONTENT_TYPES,
  createAuthAxiosClient,
} from "@induseuro-labs/auth-ui";

const authHttp = createAuthAxiosClient({
  baseURL: process.env.NEXT_PUBLIC_AUTH_API_BASE_URL ?? "",
  tenant: process.env.NEXT_PUBLIC_AUTH_TENANT ?? "dev",
  tokenStorageKey: AUTH_STORAGE_KEY,
  paths: {
    // optional: override defaults under /api/v1/auth/*
  },
});

// Use returned methods or the underlying instance:
const api = authHttp.getAxios();

// After login via this client, tokens are stored; interceptors attach:
// AUTH_HEADER_KEYS.authorization, AUTH_HEADER_KEYS.tenant, etc.
```

Exported TypeScript types include `AuthTokens`, `LoginResponse`, `AuthUserResponse`, `AuthAxiosClient`, `CreateAuthAxiosClientConfig`, and `AuthAxiosPaths`.

## API Reference

### AuthProvider Props

| Prop                          | Type                                                                 | Required | Description                                                                                     |
| ----------------------------- | -------------------------------------------------------------------- | -------- | ----------------------------------------------------------------------------------------------- |
| `onLogin`                     | `(credentials: LoginCredentials) => Promise<AuthResponse \| User>`   | No       | Custom login handler                                                                            |
| `onSignup`                    | `(data: SignupData) => Promise<AuthResponse \| User>`                | No       | Custom signup handler                                                                           |
| `onGoogleLogin`               | `(payload?: { idToken: string }) => Promise<AuthResponse \| User>`   | No       | Google OAuth handler. `createAuthApiHandlers` posts `{ idToken }` to `/api/auth/googleLogin` |
| `onOTPLogin`                  | `(otpData: OTPData) => Promise<AuthResponse \| User>`                | No       | OTP login handler                                                                               |
| `onRefreshToken`              | `(refreshToken: string) => Promise<AuthResponse>`                    | No       | Refresh token handler                                                                           |
| `onLogout`                    | `(session: AuthResponse \| null) => Promise<void> \| void`           | No       | Optional server revoke; default logout is client-only (clears `storageKey`)                    |
| `additionalLogoutStorageKeys` | `string[]`                                                           | No       | Extra `localStorage` keys removed on logout (e.g. legacy `auth_session` / `auth_tokens`); default none |
| `onSendOTP`                   | `(email: string) => Promise<void>`                                   | No       | Send OTP handler                                                                                |
| `onResetPassword`             | `(email: string) => Promise<void>`                                   | No       | Password reset handler                                                                          |
| `onCompletePasswordReset`     | `(payload: { token: string; newPassword: string }) => Promise<void>` | No       | Complete reset with token + new password                                                        |
| `storageKey`                  | `string`                                                             | No       | localStorage key (default: `auth_data` / `AUTH_STORAGE_KEY`)                                     |

### LoginForm Props

| Prop              | Type         | Default                               | Description                  |
| ----------------- | ------------ | ------------------------------------- | ---------------------------- |
| `onSuccess`       | `() => void` | -                                     | Callback on successful login |
| `redirectTo`      | `string`     | `'/dashboard'`                        | Redirect path after login    |
| `showBackButton`  | `boolean`    | `true`                                | Show back button             |
| `showGoogleLogin` | `boolean`    | `true`                                | Show Google login button     |
| `showOTPTab`      | `boolean`    | `true`                                | Show OTP tab                 |
| `title`           | `string`     | `'Log in'`                            | Form title                   |
| `subtitle`        | `string`     | `"Choose how you'd like to sign in."` | Form subtitle                |
| `className`       | `string`     | `''`                                  | Additional CSS classes       |

### SignupForm Props

Similar to LoginForm with signup-specific defaults.

### ProtectedRoute Props

| Prop                     | Type         | Default           | Description                   |
| ------------------------ | ------------ | ----------------- | ----------------------------- |
| `requiredRoles`          | `UserRole[]` | -                 | Required roles to access      |
| `redirectTo`             | `string`     | `'/login'`        | Redirect if not authenticated |
| `unauthorizedRedirectTo` | `string`     | `'/unauthorized'` | Redirect if wrong role        |

### AuthRoutes Props

| Prop                    | Type              | Default              | Description                                                     |
| ----------------------- | ----------------- | -------------------- | --------------------------------------------------------------- |
| `loginPath`             | `string`          | `'/login'`           | Login route path                                                |
| `signupPath`            | `string`          | `'/signup'`          | Signup route path                                               |
| `forgotPasswordPath`    | `string`          | `'/forgot-password'` | Forgot password route path                                      |
| `resetPasswordPath`     | `string`          | `'/reset-password'`  | Reset password route path                                       |
| `dashboardPath`         | `string`          | `'/dashboard'`       | Post-login route path                                           |
| `unauthorizedPath`      | `string`          | `'/unauthorized'`    | Unauthorized route path                                         |
| `lockToAuthRoutes`      | `boolean`         | `true`               | Redirect unknown paths to auth/dashboard routes                 |
| `allowedPaths`          | `string[]`        | `[]`                 | Extra paths allowed when route lock is enabled                  |
| `dashboardComponent`    | `React.ReactNode` | `null`               | Component rendered for dashboard path                           |
| `unauthorizedComponent` | `React.ReactNode` | `null`               | Component rendered for unauthorized path                        |
| `notFoundComponent`     | `React.ReactNode` | `null`               | Component rendered for unknown path when route lock is disabled |

## Hooks

### useAuth

```tsx
import { useAuth } from "@induseuro-labs/auth-ui";

function MyComponent() {
  const { user, session, isAuthenticated, refreshSession, logout, hasRole } =
    useAuth();

  // Check if user has specific role
  if (hasRole(UserRole.ADMIN)) {
    // Admin content
  }

  // Refresh access token when needed
  const handleRefresh = async () => {
    await refreshSession();
  };
}
```

### useRole

```tsx
import { useRole } from "@induseuro-labs/auth-ui";

function MyComponent() {
  const { isAdmin, isUser, isModerator } = useRole();

  if (isAdmin()) {
    // Admin content
  }
}
```

## Auth Response Shape

The package supports and persists this API response shape in localStorage:

```json
{
  "data": {
    "jwtToken": "string",
    "refreshToken": "string",
    "username": "string",
    "id": 0,
    "roles": ["string"]
  },
  "message": "string",
  "success": true
}
```

## Types

```tsx
import {
  User,
  UserRole,
  LoginCredentials,
  SignupData,
  AuthResponse,
  AuthData,
} from "@induseuro-labs/auth-ui";

enum UserRole {
  ADMIN = "admin",
  USER = "user",
  MODERATOR = "moderator",
}
```

## Using in React Apps (without Next.js)

This package works seamlessly with regular React applications. See [REACT_APP_USAGE.md](./REACT_APP_USAGE.md) for detailed instructions.

**Quick Example:**

```tsx
// App.tsx
import React from "react";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import { AuthProvider, LoginForm } from "@induseuro-labs/auth-ui";
import { ThemeProvider } from "@mui/material/styles";
import { createMuiTheme } from "@induseuro-labs/auth-ui";
import CssBaseline from "@mui/material/CssBaseline";

const theme = createMuiTheme();

function App() {
  return (
    <ThemeProvider theme={theme}>
      <CssBaseline />
      <BrowserRouter>
        <AuthProvider>
          <Routes>
            <Route
              path="/login"
              element={
                <div
                  style={{
                    minHeight: "100vh",
                    display: "flex",
                    alignItems: "center",
                    justifyContent: "center",
                  }}
                >
                  <LoginForm
                    onSuccess={() => (window.location.href = "/dashboard")}
                  />
                </div>
              }
            />
          </Routes>
        </AuthProvider>
      </BrowserRouter>
    </ThemeProvider>
  );
}

export default App;
```

**Note:** For React apps, use the `onSuccess` callback with your router's navigate function for SPA navigation, or let the component use `window.location` for full page navigation.

## Customization

### Custom MUI Theme

All components use Material-UI. You can customize the theme:

```tsx
import { createTheme } from "@mui/material/styles";
import { createMuiTheme } from "@induseuro-labs/auth-ui";

// Use the default theme
const theme = createMuiTheme();

// Or create your own
const customTheme = createTheme({
  palette: {
    mode: "dark",
    primary: {
      main: "#9333ea",
    },
  },
});
```

### Custom Callbacks

```tsx
<AuthProvider
  onLogin={async (credentials) => {
    // Your API integration
    const response = await fetch("/api/auth/login", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(credentials),
    });

    if (!response.ok) throw new Error("Login failed");

    const authResponse = await response.json();
    return authResponse;
  }}
  onRefreshToken={async (refreshToken) => {
    const response = await fetch("/api/auth/refresh", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ refreshToken }),
    });
    if (!response.ok) throw new Error("Refresh failed");
    const authResponse = await response.json();
    return authResponse;
  }}
>
  {children}
</AuthProvider>
```

## Examples

### Full Page Layout with Sidebar

```tsx
// app/login/page.tsx
"use client";

import { LoginForm } from "@induseuro-labs/auth-ui";

export default function LoginPage() {
  return (
    <div className="min-h-screen flex">
      {/* Left Section - Image/Content */}
      <div className="hidden lg:flex lg:w-1/2 relative bg-gradient-to-br from-slate-800 to-slate-900">
        <div className="absolute inset-0 bg-[url('/your-image.jpg')] bg-cover bg-center opacity-20"></div>
        <div className="relative z-10 flex flex-col justify-between p-12 text-white">
          <div className="space-y-4">
            <h2 className="text-4xl font-bold">Welcome Back</h2>
            <p className="text-lg text-slate-300">
              Sign in to continue to your account.
            </p>
          </div>
        </div>
      </div>

      {/* Right Section - Login Form */}
      <div className="flex-1 flex items-center justify-center p-8 bg-slate-900">
        <LoginForm />
      </div>
    </div>
  );
}
```

## Publishing to npm

1. Update version in `package.json`
2. Build the library:
   ```bash
   npm run build:lib
   ```
3. Publish:
   ```bash
   npm publish --access public
   ```

## License

MIT

## Support

For issues and feature requests, please open an issue on GitHub.
