@spin-studio/react
v0.1.2
Published
React SDK for Spin Studio - embeddable prize wheel component
Maintainers
Readme
@spin-studio/react
React SDK for Spin Studio - embeddable prize wheel components with full TypeScript support.
Installation
npm install @spin-studio/reactRequirements: React 18.0.0 or higher
Quick Start
1. Get Your API Key & Create a Wheel
- Sign up at spin-studio.weez.boo
- Create an organization
- Go to Settings → API Keys → Create API Key
- Go to Dashboard → Wheels → Create Wheel
- Add segments, customize, and Publish
2. Add Provider
Wrap your app (or the part that uses the wheel) with SpinStudioProvider:
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
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
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
<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
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
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
<SpinWheel
wheelId="wheel-id"
spinButton={<button className="my-custom-button">🎰 Try Your Luck!</button>}
/>Pre-loaded Wheel Data
// 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.
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.
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.
interface SpinWheelRef {
spin: () => Promise<SpinResult>;
reset: () => void;
isSpinning: boolean;
}useSpinStudio
Hook for SDK interaction.
const {
openWheel, // (wheelId, options?) => Promise<WheelInstance>
closeWheel, // () => void
preloadWheel, // (wheelId) => Promise<void>
isReady, // boolean
isLoading, // boolean
} = useSpinStudio();Events
PrizeWonEvent
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:
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:
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:
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
