npm package: @shyguy_studio/shipkit-react
Description: Embed App Store Connect management in your React app. Status tracker, metadata editor, review submission.
Author: Shy Guy Studio
Latest version: 0.1.0

# @shyguy_studio/shipkit-react

Embed App Store Connect management in your React app. Pipeline tracker, app list, metadata editor, review submission — all as drop-in components.

## Install

```bash
npm install @shyguy_studio/shipkit-react
```

## Quick Start — Full Dashboard

Drop in a complete ASC dashboard with one component:

```tsx
import { ShipKitDashboard } from "@shyguy_studio/shipkit-react";

function AdminPage() {
  return <ShipKitDashboard proxyUrl="/api/shipkit/proxy" />;
}
```

This renders a connect form → app list with pipeline trackers → expandable details. Everything self-contained.

## Individual Components

Use components individually for custom layouts:

```tsx
import { ShipKitProvider, ConnectForm, AppList, PipelineTracker, useShipKit } from "@shyguy_studio/shipkit-react";

function App() {
  return (
    <ShipKitProvider proxyUrl="/api/shipkit/proxy">
      <ConnectForm />
      <AppList onSelect={(app) => console.log(app)} />
    </ShipKitProvider>
  );
}
```

### PipelineTracker

Standalone pizza tracker for any app state:

```tsx
<PipelineTracker state="WAITING_FOR_REVIEW" />
<PipelineTracker state="IN_REVIEW" />
<PipelineTracker state="READY_FOR_SALE" />
<PipelineTracker state="REJECTED" />
```

### useShipKit Hook

Access the client and state directly:

```tsx
function MyComponent() {
  const { client, apps, connected } = useShipKit();

  async function pushMetadata() {
    if (!client) return;
    const versions = await client.getVersions(apps[0].id);
    await client.pushMetadata(versions[0].id, "en-US", {
      whatsNew: "Bug fixes",
    });
  }

  async function submitForReview() {
    if (!client) return;
    const versions = await client.getVersions(apps[0].id);
    await client.submitForReview(apps[0].id, versions[0].id);
  }
}
```

## Proxy Setup (Required)

Apple's API doesn't support CORS. You need a server-side proxy. Copy one of these into your project:

### Next.js (App Router)

Save as `app/api/shipkit/proxy/route.ts`:

```typescript
import { NextRequest, NextResponse } from "next/server";
import { SignJWT, importPKCS8 } from "jose";

const ASC = "https://api.appstoreconnect.apple.com/v1";

export async function POST(request: NextRequest) {
  const { keyId, issuerId, keyPem, path, params, method, body } = await request.json();
  const key = await importPKCS8(keyPem, "ES256");
  const now = Math.floor(Date.now() / 1000);
  const jwt = await new SignJWT({})
    .setProtectedHeader({ alg: "ES256", kid: keyId, typ: "JWT" })
    .setIssuer(issuerId).setIssuedAt(now).setExpirationTime(now + 1200)
    .setAudience("appstoreconnect-v1").sign(key);
  const url = new URL(ASC + path);
  if (params) Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, v as string));
  const res = await fetch(url.toString(), {
    method: method || "GET",
    headers: { Authorization: "Bearer " + jwt, "Content-Type": "application/json" },
    body: body ? JSON.stringify(body) : undefined,
  });
  return NextResponse.json(await res.json(), { status: res.status });
}
```

Then install jose: `npm install jose`

### Or use the hosted proxy

For testing, you can use `https://shyguy.studio/api/shipkit/proxy` — but for production, self-host the proxy.

## Components

| Component | Description |
|-----------|-------------|
| `ShipKitDashboard` | Full drop-in dashboard (connect + app list + trackers) |
| `ShipKitProvider` | Context provider — wrap your app |
| `ConnectForm` | ASC API key input form |
| `AppList` | List of all apps with pipeline trackers |
| `AppCard` | Single app card with version + tracker |
| `PipelineTracker` | Standalone pipeline visualization |
| `useShipKit` | Hook for client, apps, loading state |

## License

MIT — Built by [Shy Guy Studio](https://shyguy.studio)