# @fluid-app/portal-sdk

SDK for building custom Fluid portals. Provides React hooks, providers, and an API client for integrating with the Fluid Commerce platform.

## Installation

```bash
npm install @fluid-app/portal-sdk
# or
pnpm add @fluid-app/portal-sdk
# or
bun add @fluid-app/portal-sdk
```

### Peer Dependencies

This package requires the following peer dependencies:

```json
{
  "react": ">=18.0.0",
  "@tanstack/react-query": ">=5.0.0"
}
```

## Quick Start

Wrap your application with `FluidProvider`:

```tsx
import { FluidProvider } from "@fluid-app/portal-sdk";

function App() {
  return (
    <FluidProvider
      config={{
        baseUrl: "https://api.fluid.app/api",
        getAuthToken: () => localStorage.getItem("fluid_token"),
        onAuthError: () => {
          // Handle 401 errors (e.g., redirect to login)
          window.location.href = "/login";
        },
      }}
    >
      <YourApp />
    </FluidProvider>
  );
}
```

## Remote widget architecture

Remote widgets cross a runtime boundary: widget code runs in a worker while the
portal SDK runs in the browser host. The implementation is split by ownership:

- `src/widgets/remote/contract/` owns the shared, JSON-safe protocol:
  capability declarations, portal-function metadata, serializable types, and
  custom-element event contracts.
- `src/widgets/remote/worker/` owns worker authoring and execution:
  `defineWidget`, `defineWidgetPackage`, `startWidgetPackage`, and worker-side
  element wrappers.
- `src/widgets/remote/host/` owns portal integration: loading remote packages,
  capability handlers, registry providers, host element registration, and
  fullscreen coordination.

The dependency direction is `worker → contract ← host`. Worker code must never
import host code or host-only dependencies such as portal providers, DOM
renderers, or host capability adapters.

### Public facades

- `@fluid-app/portal-sdk/widgets/worker` is the stable worker-safe authoring
  facade. Widget packages should import worker APIs only from this subpath.
- `@fluid-app/portal-sdk` is the host application facade. It exposes portal
  setup and the shared contract types needed to configure the host.
- Existing exported host utilities, such as
  `@fluid-app/portal-sdk/utils/build-widget-registry`, remain compatibility
  facades over the host implementation. Paths below `src/widgets/remote/` are
  internal and are not package entry points.

### Glossary

- **Contract** — Shared serializable vocabulary understood by both runtimes; it
  contains no host implementation.
- **Worker** — The isolated runtime that renders a remote widget and calls
  declared portal functions.
- **Host** — The portal browser runtime that loads workers and implements their
  declared capabilities.
- **Capability** — A versioned group of portal functions a widget declares and
  the host may implement.
- **Facade** — A stable package entry that re-exports an internal implementation
  without exposing its directory layout.

## Hooks

### useFluidProfile

Fetch the portal profile (themes, navigation, screens):

```tsx
import { useFluidProfile } from "@fluid-app/portal-sdk";

function Navigation() {
  const { data: profile, isLoading } = useFluidProfile();

  if (isLoading) return <Spinner />;

  return (
    <nav>
      {profile?.navigation.navigation_items.map((item) => (
        <NavItem key={item.id} item={item} />
      ))}
    </nav>
  );
}
```

### useFluidTheme

Control theme settings:

```tsx
import { useFluidTheme } from "@fluid-app/portal-sdk";
import type { Theme } from "@fluid-app/portal-sdk";

function ThemeSwitcher({ themes }: { themes: Theme[] }) {
  const { currentTheme, setTheme, setThemeMode, mode } = useFluidTheme();

  return (
    <div>
      <select
        value={currentTheme?.name}
        onChange={(e) => {
          const theme = themes.find((t) => t.name === e.target.value);
          if (theme) setTheme(theme);
        }}
      >
        {themes.map((theme) => (
          <option key={theme.name} value={theme.name}>
            {theme.name}
          </option>
        ))}
      </select>

      <button onClick={() => setThemeMode(mode === "dark" ? "light" : "dark")}>
        Toggle {mode === "dark" ? "Light" : "Dark"} Mode
      </button>
    </div>
  );
}
```

## Providers

### FluidProvider

The main provider that sets up the SDK. It wraps:

- `QueryClientProvider` (TanStack Query)
- `FluidThemeProvider` (theme management)

```tsx
import { FluidProvider } from "@fluid-app/portal-sdk";
import { QueryClient } from "@tanstack/react-query";

// Optional: provide your own QueryClient
const queryClient = new QueryClient({
  defaultOptions: {
    queries: { staleTime: 5 * 60 * 1000 },
  },
});

function App() {
  return (
    <FluidProvider
      config={{
        baseUrl: "https://api.fluid.app/api",
        getAuthToken: () => getToken(),
      }}
      queryClient={queryClient}
      initialTheme={defaultTheme}
    >
      <YourApp />
    </FluidProvider>
  );
}
```

### FluidThemeProvider

Can be used standalone if you need theme management without the full SDK:

```tsx
import { FluidThemeProvider, useFluidTheme } from "@fluid-app/portal-sdk";

function App() {
  return (
    <FluidThemeProvider initialTheme={myTheme}>
      <ThemedContent />
    </FluidThemeProvider>
  );
}
```

Theme CSS variables are applied to the document root (or a custom container) with the `--fluid-` prefix.

## Types

All types are exported for use in your application:

```tsx
import type {
  // Core types
  Profile,
  Navigation,
  NavigationItem,
  ScreenDefinition,

  // Client types
  FluidSDKConfig,
  RequestOptions,
} from "@fluid-app/portal-sdk";
```

## Query Keys

Query keys are exported for cache invalidation and prefetching:

```tsx
import { PROFILE_QUERY_KEY } from "@fluid-app/portal-sdk";
import { useQueryClient } from "@tanstack/react-query";

function RefreshButton() {
  const queryClient = useQueryClient();

  const handleRefresh = () => {
    queryClient.invalidateQueries({ queryKey: PROFILE_QUERY_KEY });
  };

  return <button onClick={handleRefresh}>Refresh Profile</button>;
}
```

## Error Handling

The SDK provides an `ApiError` class for structured error handling:

```tsx
import { ApiError, isApiError } from "@fluid-app/portal-sdk";

try {
  await someApiCall();
} catch (error) {
  if (isApiError(error)) {
    console.log("Status:", error.status);
    console.log("Message:", error.message);
    console.log("Data:", error.data); // Server error details
  }
}
```

## License

Private - Fluid Commerce
