npm package: @spin-studio/react
Description: React SDK for Spin Studio - embeddable prize wheel component
Author: Spin Studio
Latest version: 0.1.2

# @spin-studio/react

React SDK for Spin Studio - embeddable prize wheel components with full TypeScript support.

## Installation

```bash
npm install @spin-studio/react
```

**Requirements:** React 18.0.0 or higher

## Quick Start

### 1. Get Your API Key & Create a Wheel

1. Sign up at [spin-studio.weez.boo](https://spin-studio.weez.boo)
2. Create an organization
3. Go to **Settings → API Keys → Create API Key**
4. Go to **Dashboard → Wheels → Create Wheel**
5. Add segments, customize, and **Publish**

### 2. Add Provider

Wrap your app (or the part that uses the wheel) with `SpinStudioProvider`:

```tsx
import { SpinStudioProvider } from "@spin-studio/react";

function App() {
  return (
    <SpinStudioProvider
      config={{
        apiKey: "sk_live_your_api_key_here",
        baseUrl: "https://spin-studio.weez.boo",
      }}
    >
      <YourApp />
    </SpinStudioProvider>
  );
}
```

### 3. Use the SpinWheel Component

```tsx
import { SpinWheel } from "@spin-studio/react";

function CheckoutPage() {
  const handlePrizeWon = (event) => {
    console.log("Won:", event.prize);
    console.log("Code:", event.redemptionCode);

    // Apply discount to cart
    applyDiscount(event.prize.value);
  };

  return <SpinWheel wheelId="your-wheel-id" onPrizeWon={handlePrizeWon} />;
}
```

## Usage Examples

### Basic Inline Wheel

```tsx
import { SpinStudioProvider, SpinWheel } from "@spin-studio/react";

function DiscountWheel() {
  return (
    <SpinStudioProvider config={{ apiKey: "sk_live_xxx" }}>
      <div style={{ width: 400, height: 400 }}>
        <SpinWheel wheelId="wheel-id" size={400} onPrizeWon={(e) => console.log("Won:", e.prize)} />
      </div>
    </SpinStudioProvider>
  );
}
```

### With All Event Handlers

```tsx
<SpinWheel
  wheelId="wheel-id"
  size={400}
  onReady={(e) => console.log("Wheel ready")}
  onSpinStart={(e) => console.log("Spinning...")}
  onSpinEnd={(e) => console.log("Spin ended")}
  onPrizeWon={(e) => {
    const { prize, redemptionCode } = e;

    // prize.type: 'discount_percentage' | 'discount_fixed' | 'free_shipping' | ...
    // prize.value: 15 (for 15% off)
    // redemptionCode: 'SPIN-ABC123'

    applyToCart(prize, redemptionCode);
  }}
  onLevelUp={(e) => console.log(`Level up! ${e.fromLevel} → ${e.toLevel}`)}
  onError={(e) => console.error("Error:", e.message)}
/>
```

### Using the Hook

```tsx
import { useSpinStudio } from "@spin-studio/react";

function CustomWheel() {
  const { openWheel, closeWheel, isReady } = useSpinStudio();

  const handleClick = async () => {
    const result = await openWheel("wheel-id", {
      mode: "modal",
      onPrizeWon: (e) => console.log("Won:", e.prize),
    });
  };

  return (
    <button onClick={handleClick} disabled={!isReady}>
      🎡 Spin to Win!
    </button>
  );
}
```

### With Ref for Programmatic Control

```tsx
import { useRef } from "react";
import { SpinWheel, SpinWheelRef } from "@spin-studio/react";

function ControlledWheel() {
  const wheelRef = useRef<SpinWheelRef>(null);

  const handleSpin = async () => {
    if (wheelRef.current) {
      const result = await wheelRef.current.spin();
      console.log("Result:", result);
    }
  };

  const handleReset = () => {
    wheelRef.current?.reset();
  };

  return (
    <div>
      <SpinWheel ref={wheelRef} wheelId="wheel-id" hideSpinButton />
      <button onClick={handleSpin}>Spin!</button>
      <button onClick={handleReset}>Reset</button>
    </div>
  );
}
```

### Custom Spin Button

```tsx
<SpinWheel
  wheelId="wheel-id"
  spinButton={<button className="my-custom-button">🎰 Try Your Luck!</button>}
/>
```

### Pre-loaded Wheel Data

```tsx
// If you already have wheel data from your backend
<SpinWheel wheelId="wheel-id" wheelData={preloadedWheelData} onPrizeWon={handlePrizeWon} />
```

## API Reference

### `SpinStudioProvider`

Context provider for SDK configuration.

```tsx
interface SpinStudioProviderProps {
  config: {
    apiKey: string; // Required
    baseUrl?: string; // Default: 'https://spin-studio.weez.boo'
    debug?: boolean; // Default: false
  };
  children: React.ReactNode;
}
```

### `SpinWheel`

The wheel component.

```tsx
interface SpinWheelProps {
  wheelId: string; // Required
  wheelData?: WheelData; // Optional pre-loaded data
  className?: string;
  style?: React.CSSProperties;
  size?: number; // Default: 400
  spinnable?: boolean; // Default: true
  initialLevel?: number; // For multi-level wheels
  spinButton?: React.ReactNode; // Custom spin button
  hideSpinButton?: boolean; // Hide default button

  // Events
  onReady?: (event: ReadyEvent) => void;
  onSpinStart?: (event: SpinStartEvent) => void;
  onSpinEnd?: (event: SpinEndEvent) => void;
  onPrizeWon?: (event: PrizeWonEvent) => void;
  onLevelUp?: (event: LevelUpEvent) => void;
  onLevelDown?: (event: LevelDownEvent) => void;
  onError?: (event: ErrorEvent) => void;
}
```

### `SpinWheelRef`

Ref methods for programmatic control.

```tsx
interface SpinWheelRef {
  spin: () => Promise<SpinResult>;
  reset: () => void;
  isSpinning: boolean;
}
```

### `useSpinStudio`

Hook for SDK interaction.

```tsx
const {
  openWheel, // (wheelId, options?) => Promise<WheelInstance>
  closeWheel, // () => void
  preloadWheel, // (wheelId) => Promise<void>
  isReady, // boolean
  isLoading, // boolean
} = useSpinStudio();
```

## Events

### PrizeWonEvent

```typescript
interface PrizeWonEvent {
  wheelId: string;
  sessionId: string;
  timestamp: number;
  prize: {
    id: string;
    name: string;
    type:
      | "discount_percentage"
      | "discount_fixed"
      | "free_shipping"
      | "free_product"
      | "points"
      | "coupon"
      | "custom";
    value?: number;
    code?: string;
    imageUrl?: string;
  };
  redemptionCode?: string; // e.g., "SPIN-ABC123"
}
```

## Storing Discounts (Recommended)

For production use, add discount fields to your Order model:

```prisma
model Order {
  // ... your existing fields
  discountCode     String?   // "SPIN-ABC123"
  discountType     String?   // "percentage", "fixed", "free_shipping"
  discountPercent  Float?    // 15
  discountAmount   Float?    // 15.00
  originalTotal    Float?    // Total before discount
}
```

> Without these fields, discounts work visually but won't be persisted in your database.

## TypeScript

Full TypeScript support included. All types are exported:

```tsx
import type {
  SpinWheelProps,
  SpinWheelRef,
  WheelData,
  Prize,
  PrizeWonEvent,
  SpinResult,
} from "@spin-studio/react";
```

## Next.js / SSR

The component is SSR-compatible. For server components, use dynamic import:

```tsx
import dynamic from "next/dynamic";

const SpinWheel = dynamic(() => import("@spin-studio/react").then((mod) => mod.SpinWheel), {
  ssr: false,
});
```

## Related Packages

- **Vanilla JS:** `@spin-studio/sdk` - For any JavaScript project
- **Vue 3:** `@spin-studio/vue` - For Vue applications

## License

MIT