npm package: @reqdesk/widget
Description: Embeddable support widget SDK for Reqdesk — ticket submission, tracking, and support portal.
Latest version: 0.8.1

# @reqdesk/widget

Embeddable support widget SDK for [Reqdesk](https://reqdesk.com). Add a floating support button to any website — ticket submission with file attachments, ticket tracking, My Tickets, and optional Keycloak SSO authentication.

## Features

- **Floating Action Button** — circular FAB that opens a slide-up support panel
- **Submit Tickets** — form with title, description, email, priority, and file attachments (drag-drop)
- **My Tickets** — view ticket history by email or automatically when authenticated
- **Ticket Detail** — full ticket view with reply thread, attachments, and reply composer
- **Track by Token** — anonymous ticket tracking via tracking tokens
- **Dual Auth** — API key mode (anonymous/email) + optional Keycloak OIDC login
- **Preferences** — end-user language (EN/AR with RTL), theme (light/dark/system), accent color
- **Branding** — custom logo, brand name, and "Powered by Reqdesk" footer (hideable)
- **Shadow DOM** — fully isolated styles, no CSS conflicts with host page
- **i18n** — English and Arabic built-in, custom translations supported

## Installation

```bash
npm install @reqdesk/widget
# or
bun add @reqdesk/widget
# or
yarn add @reqdesk/widget
```

## Quick Start

### Script Tag (any website)

```html
<script src="https://unpkg.com/@reqdesk/widget/dist/index.iife.js"></script>
<script>
  ReqdeskWidget.init({
    apiKey: 'rqd_live_your_api_key_here',
    position: 'bottom-right',
    language: 'en',
    theme: {
      primaryColor: '#42b983',
      mode: 'light',
      brandName: 'Acme Support',
      logo: 'https://example.com/logo.png',
    },
  });
</script>
```

### React Component

```tsx
import { ReqdeskProvider, FloatingWidget } from '@reqdesk/widget/react';

function App() {
  return (
    <ReqdeskProvider
      apiKey="rqd_live_your_api_key_here"
      language="en"
      theme={{ primaryColor: '#42b983', mode: 'light' }}
    >
      <FloatingWidget position="bottom-right" />
    </ReqdeskProvider>
  );
}
```

## Configuration

### `ReqdeskWidgetConfig`

| Property | Type | Required | Default | Description |
|----------|------|----------|---------|-------------|
| `apiKey` | `string` | Yes | — | Project API key from Reqdesk dashboard |
| `auth` | `OidcAuthConfig` | No | — | Keycloak OIDC config for authenticated mode |
| `position` | `'bottom-right' \| 'bottom-left'` | No | `'bottom-right'` | FAB button position |
| `language` | `string` | No | `'en'` | Language code (`'en'` or `'ar'`) |
| `theme` | `ThemeConfig` | No | — | Visual customization |
| `customer` | `CustomerConfig` | No | — | Pre-fill customer info |
| `translations` | `Record<string, string>` | No | — | Override built-in translations |
| `inline` | `boolean` | No | `false` | Render inline instead of floating |
| `container` | `string \| HTMLElement` | No | — | Target element for inline mode |

### `ThemeConfig`

| Property | Type | Default | Description |
|----------|------|---------|-------------|
| `primaryColor` | `string` | `'#42b983'` | Accent color (hex) |
| `mode` | `'light' \| 'dark' \| 'auto'` | `'light'` | Color scheme |
| `borderRadius` | `string` | `'8px'` | Border radius for panels and inputs |
| `fontFamily` | `string` | `'inherit'` | Font family |
| `zIndex` | `number` | `9999` | Z-index for FAB and panel |
| `logo` | `string` | — | URL to brand logo (shown in header) |
| `brandName` | `string` | — | Brand name (shown in header instead of "Support") |
| `hideBranding` | `boolean` | `false` | Hide "Powered by Reqdesk" footer |

### `OidcAuthConfig`

| Property | Type | Description |
|----------|------|-------------|
| `issuerUri` | `string` | Keycloak realm URL (e.g., `https://auth.example.com/realms/reqdesk`) |
| `clientId` | `string` | Keycloak client ID (e.g., `reqdesk-widget`) |

## Authentication

The widget supports two authentication modes that can be used together:

### API Key Mode (default)

Every widget requires an API key. In this mode:

- Visitors submit tickets anonymously or with an email address
- Email identifies the visitor for "My Tickets" (trust-based, like a contact form)
- Tracking tokens allow anonymous ticket access from any browser
- A "Remember me" checkbox saves the email to localStorage

```ts
ReqdeskWidget.init({
  apiKey: 'rqd_live_your_api_key_here',
});
```

### Keycloak OIDC Mode (optional)

When `auth` config is provided, the widget shows a Login button. After authenticating via Keycloak:

- User is automatically identified (no email entry needed)
- "My Tickets" shows all tickets instantly
- Tickets are attributed to the real Keycloak user account
- Session persists via silent refresh (one-time login)

```ts
ReqdeskWidget.init({
  apiKey: 'rqd_live_your_api_key_here',
  auth: {
    issuerUri: 'https://auth.example.com/realms/reqdesk',
    clientId: 'reqdesk-widget',
  },
});
```

The widget's OIDC session is **completely independent** from your host app's auth. It uses its own Keycloak client (`reqdesk-widget`) with separate tokens.

#### Keycloak Client Setup

Add a `reqdesk-widget` client to your Keycloak realm:

- **Client Type**: Public (`publicClient: true`)
- **Flow**: Authorization Code with PKCE (`standardFlowEnabled: true`)
- **Redirect URIs**: `*` (or restrict to your domains)
- **Web Origins**: `+` (CORS for any origin)
- **Client Scopes**: `basic, openid, profile, email`

## Events

```ts
// Vanilla JS
ReqdeskWidget.on('ticket:created', (ticket) => {
  console.log('Ticket created:', ticket.ticketNumber);
});

ReqdeskWidget.on('open', () => console.log('Widget opened'));
ReqdeskWidget.on('close', () => console.log('Widget closed'));
ReqdeskWidget.on('error', (err) => console.error(err));
```

| Event | Payload | Description |
|-------|---------|-------------|
| `open` | — | Widget panel opened |
| `close` | — | Widget panel closed |
| `ticket:created` | `TicketResult` | Ticket submitted successfully |
| `ticket:tracked` | `TrackedTicketResult` | Ticket tracked by token |
| `reply:sent` | — | Reply submitted |
| `error` | `WidgetError` | Error occurred |

## Vanilla JS API

```ts
ReqdeskWidget.init(config)      // Initialize the widget
ReqdeskWidget.open()            // Open the panel
ReqdeskWidget.close()           // Close the panel
ReqdeskWidget.toggle()          // Toggle open/close
ReqdeskWidget.setLanguage('ar') // Change language (supports RTL)
ReqdeskWidget.setTheme({ mode: 'dark' })  // Update theme
ReqdeskWidget.identify({ email: 'user@example.com', name: 'Alice' })
ReqdeskWidget.on(event, callback)
ReqdeskWidget.destroy()         // Remove widget from DOM
```

## React API

### Components

```tsx
import {
  ReqdeskProvider,  // Context provider (required wrapper)
  FloatingWidget,   // FAB + slide-up panel
  TicketForm,       // Standalone ticket form (inline mode)
  SupportPortal,    // Standalone support portal
  ShadowRoot,       // Shadow DOM wrapper for custom components
} from '@reqdesk/widget/react';
```

### `ReqdeskProvider` Props

| Prop | Type | Required | Description |
|------|------|----------|-------------|
| `apiKey` | `string` | Yes | Project API key |
| `auth` | `OidcAuthConfig` | No | Keycloak OIDC config |
| `theme` | `ThemeConfig` | No | Visual customization |
| `language` | `string` | No | Language code |
| `customer` | `CustomerConfig` | No | Pre-fill customer info |
| `translations` | `Record<string, string>` | No | Custom translations |

### `FloatingWidget` Props

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `position` | `'bottom-right' \| 'bottom-left'` | `'bottom-right'` | FAB position |
| `contained` | `boolean` | `false` | Use absolute positioning (for previews) |
| `onTicketCreated` | `(ticket: TicketResult) => void` | — | Callback on ticket creation |
| `onError` | `(error: WidgetError) => void` | — | Callback on error |

### Hooks

```tsx
import { useReqdesk } from '@reqdesk/widget/react';

function CustomForm() {
  const { submitTicket, trackTicket, isLoading, error } = useReqdesk();

  const handleSubmit = async () => {
    const result = await submitTicket({
      title: 'Bug report',
      email: 'user@example.com',
      priority: 'high',
    });
    console.log('Created:', result.ticketNumber);
  };
}
```

## File Attachments

The widget supports file attachments on ticket submission and replies:

- **Drag-and-drop** or click to browse
- **Max 5 files** per submission, 5 per reply
- **Max 10MB** per file (configurable per project)
- **Allowed types**: Images (JPEG, PNG, GIF, WebP), PDF, Office documents, text, CSV, ZIP
- **Blocked**: Executables (.exe, .bat, .cmd, .sh, .ps1, .msi, .dll, .scr)
- **Upload progress** shown per-file after submission

Files are uploaded sequentially after the ticket/reply is created.

## Customization

### Custom Translations

Override any built-in string:

```ts
ReqdeskWidget.init({
  apiKey: '...',
  translations: {
    'widget.title': 'Help Center',
    'form.submit': 'Send Request',
    'menu.newTicket': 'New Request',
  },
});
```

See `src/i18n/en.ts` for all available translation keys.

### RTL Support

Set `language: 'ar'` for full RTL layout. The widget automatically mirrors all UI elements.

### End-User Preferences

Visitors can customize their experience from the Preferences menu:

- **Language**: English / Arabic toggle
- **Theme**: Light / Dark / System
- **Accent Color**: 5 preset colors (green, blue, purple, orange, red)

Preferences are persisted to `localStorage` per API key.

## Architecture

- **Shadow DOM**: Complete CSS isolation — host page styles cannot affect the widget
- **Zero global state**: Widget context is self-contained, no window globals
- **ofetch**: HTTP client with automatic retry, timeout, and auth interceptors
- **TanStack Query**: Data fetching with caching, optimistic updates, and loading states (React only)
- **Optional dependencies**: React, oidc-spa, and TanStack Query are all optional peer deps

### Bundle Sizes

| Entry | Size (gzip) |
|-------|-------------|
| `index.iife.js` (vanilla) | ~12 KB |
| `react.js` (ESM) | ~6 KB |

React, oidc-spa, and TanStack Query are **not bundled** — they're peer dependencies.

## Environment Variables

See `.env.example` for all available environment variables. These are for use in your application's build process — the widget SDK receives configuration via props/init, not directly from env vars.

## TypeScript

Full TypeScript support with exported types:

```ts
import type {
  ReqdeskWidgetConfig,
  ThemeConfig,
  OidcAuthConfig,
  CustomerConfig,
  TicketResult,
  WidgetError,
  TrackedTicketResult,
  PublicReply,
  SubmitTicketData,
  WidgetConfigPersist,
} from '@reqdesk/widget/react';
```

## Browser Support

Chrome, Firefox, Safari, and Edge (latest versions). Requires Shadow DOM support.

## License

Proprietary. See LICENSE file for details.