npm package: @ippis/profile-management-sdk
Description: Embeddable Profile Management SDK for Node and Browser.
Latest version: 1.0.1

# Embedded Profile Management SDK

TypeScript SDK for profile APIs (identity, education, experience, and related sections), optional outbound webhooks, and a **React `ProfileWidget`** you can drop into an existing app.

## Features

- API key authentication (`x-api-key`) for profile HTTP calls.
- `userId`-scoped helpers for identity, status, education, experience, certificates, disabilities, languages, publications, and referees.
- Entry points for **Node**, **Browser**, and the **React widget** bundle, plus a **pre-built `widget.css`** (no Tailwind required in the host app).
- Outbound webhook POST helper and inbound signature verification.

## Install

```bash
npm install @ippis/profile-management-sdk
```

The **profile widget** is a React component. Your app should already use **React 18+** (and typically `react-dom` for mounting).

**You do not need Tailwind in your application.** The package ships a pre-built stylesheet that only applies **inside** the widget (scoped under `.ippis-profile-widget-root`). It **does not** include Tailwind Preflight, so it will not reset `body`, headings, or other global styles in your host app.

---

## Integrating the `ProfileWidget` (React)

### 1. Import the component

Either import from the package root (re-exported) or from the widget subpath:

```tsx
import { ProfileWidget } from "@ippis/profile-management-sdk";
// or:
import { ProfileWidget } from "@ippis/profile-management-sdk/widget";
```

`ProfileWidgetHeader` is also available from the same modules if you need the header-only layout.

Import the stylesheet **once** in your app entry (or layout), before rendering the widget:

```ts
import "@ippis/profile-management-sdk/widget.css";
```

### 2. Provide SDK config and `userId`

`ProfileWidget` loads data with `createProfileSdk`-compatible settings passed as `sdkConfig`. You must resolve **`userId`** in your app (for example via your own “resolve identity” API) before rendering.

```tsx
import { ProfileWidget, type ProfileSdkConfig } from "@ippis/profile-management-sdk";
import "@ippis/profile-management-sdk/widget.css";
import { useMemo } from "react";

const sdkConfig: ProfileSdkConfig = useMemo(
  () => ({
    baseUrl: "/internal/profile-api", // or full URL; often proxied in dev
    apiKey: process.env.NEXT_PUBLIC_PROFILE_API_KEY!, // never expose private keys in public clients without a proxy
    timeoutMs: 15000,
    retry: { maxRetries: 2, backoffMs: 400 },
    webhook: {
      url: "/internal/profile-events",
      secret: process.env.PROFILE_WEBHOOK_SECRET, // prefer server-side webhooks in production
    },
    styles: {
      theme: "light",
      widthClass: "w-full max-w-4xl",
      containerRadius: "rounded",
      containerBgClass: "bg-slate-50",
      cardBgClass: "bg-white/90",
      textPrimaryClass: "text-slate-900",
      textSecondaryClass: "text-slate-700",
      textMutedClass: "text-slate-500",
    },
  }),
  [],
);

export function ProfilePage({ userId }: { userId: string }) {
  return (
    <ProfileWidget
      userId={userId}
      sdkConfig={sdkConfig}
      profileImage={null}
      positionName="Chief Digital Officer"
      unitName="MININVEST"
      residenceDistrict="Kicukiro"
      residenceSector="Kanombe"
      residenceCell="Karama"
      residenceVillage="Cyurusagara"
      onEditIdentityField={(field) => {
        /* open your editor for ID, phone, or residence fields */
      }}
      onEditEducation={(education, index) => {}}
      onAddEducation={() => {}}
      onEditExperience={(experience, index) => {}}
      onAddExperience={() => {}}
      onEditCertificate={(certificate, index) => {}}
      onAddCertificate={() => {}}
      onEditDisability={(disability, index) => {}}
      onDeleteDisability={(disability, index) => {}}
      onAddDisability={() => {}}
      onEditLanguage={(language, index) => {}}
      onAddLanguage={() => {}}
      onEditPublication={(publication, index) => {}}
      onEditReferee={(referee, index) => {}}
      onAddReferee={() => {}}
    />
  );
}
```

All `onEdit*` / `onAdd*` / `onDelete*` handlers are optional. The UI still shows edit/add controls; without handlers you can wire them later or ignore the actions.

### 3. Optional: `ProfileWidgetStyles` and Tailwind in the host

The pre-built `widget.css` includes every utility the component uses **by default** and from the shipped source. If you pass **extra** Tailwind class names through `sdkConfig.styles` (for example a custom `cardBgClass` you invented), those names might not exist in `widget.css` yet. In that case you can:

- Stick to classes similar to the defaults in this repo’s `widget.tsx` / README examples, **or**
- Add a small Tailwind build in your app that covers your custom tokens, **or**
- Open an issue / extend the package `safelist` when forking this library.

If your **entire** app already uses Tailwind, you may still import `widget.css` (simplest, matches the published build). Avoid duplicating work by also scanning `node_modules` unless you know you need it.

### 4. Security notes

- Prefer a **backend or edge proxy** that adds the API key so the browser never holds a privileged secret.
- Keep webhook secrets on the server when possible; browser `webhook.secret` is only appropriate for tightly controlled environments.

---

## Quick start (Node)

```ts
import { createProfileSdk } from "@ippis/profile-management-sdk/node";

const sdk = createProfileSdk({
  baseUrl: "https://api.example.com/profile",
  apiKey: process.env.PROFILE_API_KEY ?? "",
  webhook: {
    url: "https://receiver.example.com/hooks/profile",
    secret: process.env.PROFILE_WEBHOOK_SECRET,
  },
});

const userId = "2207169020";
const identity = await sdk.profile.getIdentity(userId);
```

Use `@ippis/profile-management-sdk/node` when you want the Node-oriented entry re-exports; behavior matches the default SDK with the same types.

## Quick start (browser / `fetch`)

```ts
import { createProfileSdk } from "@ippis/profile-management-sdk/browser";

const sdk = createProfileSdk({
  baseUrl: "/internal/profile-api",
  apiKey: "your-api-key-or-empty-if-proxy-injects",
});
```

## Vite dev server proxy (example)

```ts
// vite.config.ts
import { defineConfig } from "vite";

export default defineConfig({
  server: {
    proxy: {
      "/internal/profile-api": {
        target: "https://internal-api.example.com",
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/internal\/profile-api/, "/profile"),
      },
    },
  },
});
```

## Webhooks

### Send (outbound)

```ts
await sdk.webhooks.send({
  eventType: "profile.updated",
  userId,
  data: { identityType, identityId, identity, experiences },
});
```

### Verify (inbound, backend)

```ts
import { verifyWebhookSignature } from "@ippis/profile-management-sdk/node";

const verified = await verifyWebhookSignature(rawBody, signatureHeader, secret, {
  toleranceSeconds: 300,
});
```

## Further reading

- [`INTEGRATION.md`](./INTEGRATION.md) — HTTP usage, section fetches, and webhook flow in more detail.

## Development

```bash
npm install
npm run build
npm run test
npm run dev:demo   # local widget demo (see demo/)
```