
Security News
OpenClaw Skill Marketplace Emerges as Active Malware Vector
Security researchers report widespread abuse of OpenClaw skills to deliver info-stealing malware, exposing a new supply chain risk as agent ecosystems scale.
@fenixblack/growthkit
Advanced tools
React SDK for GrowthKit - Intelligent waitlist and referral management
React SDK for GrowthKit - Intelligent waitlist and referral management system for client-side applications.
📝 Version History: See CHANGELOG.md for updates
🔧 Advanced Setup: See MIDDLEWARE.md for Next.js server-side integration
npm install @fenixblack/growthkit
That's it! Just use your public key:
import { GrowthKitAccountWidget } from '@fenixblack/growthkit';
function App() {
return (
<GrowthKitAccountWidget
config={{ publicKey: 'pk_your_public_key_here' }}
position="top-right"
>
<YourApp />
</GrowthKitAccountWidget>
);
}
✅ Works with: Static sites, SPAs, React, Next.js, Vue, GitHub Pages, Netlify, Vercel
✅ No backend required: Direct secure communication with GrowthKit
✅ Safe for client-side: Public keys are designed to be exposed
npm install @fenixblack/growthkit
# or
yarn add @fenixblack/growthkit
Before you start, you'll need your public API key:
pk_)🔒 Security Note: Public keys are safe to use in client-side code. They're designed to be exposed and automatically handle secure token generation.
Here's a complete, copy-paste ready example showing how to integrate the GrowthKit widget in your app:
import React from 'react';
import { GrowthKitAccountWidget } from '@fenixblack/growthkit';
function App() {
return (
<GrowthKitAccountWidget
config={{
publicKey: 'pk_your_public_key_here', // Get this from your dashboard
theme: 'auto', // Options: 'light', 'dark', 'auto'
language: 'en', // Options: 'en', 'es'
}}
position="top-right" // Where to display the widget
showName={true} // Show user's name
showEmail={true} // Show user's email
>
{/* Your entire app goes here */}
<div>
<h1>Welcome to My App</h1>
<p>The widget appears in the top-right corner automatically!</p>
{/* Your app content */}
<YourComponents />
</div>
</GrowthKitAccountWidget>
);
}
export default App;
That's it! The widget automatically:
// app/layout.tsx
'use client';
import { GrowthKitAccountWidget } from '@fenixblack/growthkit';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<GrowthKitAccountWidget
config={{
publicKey: 'pk_your_public_key_here',
theme: 'auto',
}}
position="top-right"
>
{children}
</GrowthKitAccountWidget>
</body>
</html>
);
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My App with GrowthKit</title>
<script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/@fenixblack/growthkit@latest/dist/index.umd.js"></script>
</head>
<body>
<div id="root">
<h1>Welcome to My App</h1>
<p>Your content here...</p>
</div>
<script>
// Initialize GrowthKit widget
const { GrowthKitAccountWidget } = window.GrowthKit;
const config = {
publicKey: 'pk_your_public_key_here',
theme: 'auto'
};
// Wrap your app with the widget
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
React.createElement(GrowthKitAccountWidget, {
config,
position: 'top-right'
}, document.getElementById('root').innerHTML)
);
</script>
</body>
</html>
Once integrated, your users will see:
If you need more control, use the hook directly:
import { useGrowthKit, GrowthKitProvider } from '@fenixblack/growthkit';
function MyComponent() {
const gk = useGrowthKit();
return (
<div>
<h1>Credits: {gk.credits}</h1>
<button onClick={() => gk.share()}>Share & Earn</button>
<p>Your link: {gk.getReferralLink()}</p>
{/* Track custom actions */}
<button
onClick={() => gk.completeAction('custom_action')}
disabled={!gk.canPerformAction('custom_action')}
>
Do Something (costs credits)
</button>
</div>
);
}
function App() {
return (
<GrowthKitProvider config={{ publicKey: 'pk_your_key_here' }}>
<MyComponent />
</GrowthKitProvider>
);
}
import { useGrowthKit, GrowthKitProvider } from '@fenixblack/growthkit';
function App() {
return (
<GrowthKitProvider config={{ publicKey: 'pk_your_key_here' }}>
<MyApp />
</GrowthKitProvider>
);
}
function MyApp() {
const { track, credits, share } = useGrowthKit();
return (
<div>
<h1>Credits: {credits}</h1>
<button onClick={() => track('feature_used')}>Use Feature</button>
<button onClick={() => share()}>Share & Earn</button>
</div>
);
}
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/@fenixblack/growthkit@latest/dist/index.js"></script>
</head>
<body>
<div id="credits">Loading...</div>
<button onclick="useFeature()">Use Feature</button>
<script>
const gk = new GrowthKit({
publicKey: 'pk_your_key_here'
});
gk.initialize().then(() => {
document.getElementById('credits').textContent = `Credits: ${gk.credits}`;
});
function useFeature() {
gk.track('feature_used');
}
</script>
</body>
</html>
'use client';
import { useGrowthKit } from '@fenixblack/growthkit';
export default function ClientWidget() {
const gk = useGrowthKit({
publicKey: 'pk_your_key_here'
});
return (
<div className="p-4 border rounded">
<h3>Credits: {gk.credits}</h3>
<button onClick={() => gk.track('action')}>
Track Action
</button>
</div>
);
}
The SDK supports multiple languages with real-time language switching capabilities.
en) - Defaultes)Set the default language in your configuration:
const config = {
publicKey: 'pk_your_key_here', // Use public key for client-side
language: 'es', // Set Spanish as default
theme: 'dark', // Set dark theme (options: 'light' | 'dark' | 'auto')
};
// Or for middleware mode:
const middlewareConfig = {
// No keys needed - handled by middleware
language: 'es',
theme: 'dark',
};
import { useGrowthKit, GrowthKitAccountWidget } from '@fenixblack/growthkit';
function App() {
const config = {
publicKey: 'pk_your_key_here', // Public key for client-side
language: 'es', // Spanish by default
theme: 'auto', // Auto-detect system theme preference
};
return (
<GrowthKitAccountWidget config={config}>
<YourApp />
</GrowthKitAccountWidget>
);
}
Configure the widget with your public key and optional settings:
const config = {
publicKey: 'pk_your_key_here', // Required: Get from dashboard → API Tokens
theme: 'auto', // Optional: 'light' | 'dark' | 'auto' (default: 'auto')
language: 'en', // Optional: 'en' | 'es' (default: 'en')
debug: false, // Optional: Enable debug logging (default: false)
};
All options:
🔧 Advanced: For server-side middleware integration in Next.js apps, see MIDDLEWARE.md
You can change the language dynamically from the parent app:
import { useRef } from 'react';
import { GrowthKitAccountWidget, GrowthKitAccountWidgetRef } from '@fenixblack/growthkit';
function App() {
const [language, setLanguage] = useState<'en' | 'es'>('en');
const widgetRef = useRef<GrowthKitAccountWidgetRef>(null);
const toggleLanguage = () => {
const newLang = language === 'en' ? 'es' : 'en';
setLanguage(newLang);
// Update the widget language programmatically
widgetRef.current?.setLanguage(newLang);
};
return (
<>
<button onClick={toggleLanguage}>
🌍 {language === 'en' ? 'ES' : 'EN'}
</button>
<GrowthKitAccountWidget
ref={widgetRef}
config={{ apiKey: 'your-key', language }}
>
<YourApp />
</GrowthKitAccountWidget>
</>
);
}
All user-facing components support localization:
Access the translation system directly:
import { useTranslation, useLocalization } from '@fenixblack/growthkit';
function CustomComponent() {
const { t } = useTranslation(); // Translation function with interpolation
const { language, setLanguage } = useLocalization(); // Current language state
return (
<div>
<p>{t('waitlist.joinWaitlistMessage')}</p>
<p>{t('modal.earnCreditsName', { credits: 5 })}</p>
<p>Current language: {language}</p>
</div>
);
}
The useGrowthKit hook provides access to all GrowthKit functionality:
const gk = useGrowthKit();
// User State
gk.credits: number // Current credit balance
gk.usage: number // Total credits used
gk.name: string | null // User's claimed name
gk.email: string | null // User's claimed email
gk.hasClaimedName: boolean // Whether user has claimed a name
gk.hasClaimedEmail: boolean // Whether user has claimed an email
gk.hasVerifiedEmail: boolean // Whether email is verified
// Referral System
gk.referralCode: string | null // User's unique referral code
gk.getReferralLink(): string // Get shareable referral link
gk.share(options?): void // Share referral link (opens native share or copies)
// Actions
gk.completeAction(action: string, options?): Promise<boolean>
gk.canPerformAction(action: string): boolean
gk.claimName(name: string): Promise<boolean>
gk.claimEmail(email: string): Promise<boolean>
gk.verifyEmail(token: string): Promise<boolean>
// App State
gk.loading: boolean // Initial loading state
gk.initialized: boolean // Whether SDK is initialized
gk.app?: AppBranding // App branding information
gk.error: Error | null // Any error that occurred
gk.policy: GrowthKitPolicy // App credit policy
gk.refresh(): Promise<void> // Refresh user data
// Customization
gk.language: 'en' | 'es' // Current language
gk.setLanguage(lang): void // Change language
gk.setTheme(theme): void // Change theme ('light' | 'dark' | 'auto')
Example Usage:
function MyComponent() {
const gk = useGrowthKit();
return (
<div>
<h1>You have {gk.credits} credits</h1>
<button
onClick={() => gk.completeAction('generate')}
disabled={!gk.canPerformAction('generate')}
>
Generate Image (1 credit)
</button>
<button onClick={() => gk.share()}>
Share & Earn 5 Credits
</button>
<p>Your link: {gk.getReferralLink()}</p>
</div>
);
}
The share() method supports sharing user-generated content like images and videos along with your referral link. This is perfect for viral loops where users can share what they created with your app.
import { useGrowthKit } from '@fenixblack/growthkit';
function ShareButton() {
const { share } = useGrowthKit();
return (
<button onClick={() => share()}>
Share & Earn Credits
</button>
);
}
function CustomShare() {
const { share } = useGrowthKit();
const handleShare = () => {
share({
title: 'Check out my creation!',
text: 'I made this with MyApp - try it yourself!',
// url automatically includes referral link
});
};
return <button onClick={handleShare}>Share</button>;
}
Perfect for image generators, editors, design tools:
function ImageGenerator() {
const { share } = useGrowthKit();
const canvasRef = useRef<HTMLCanvasElement>(null);
const handleShare = () => {
const canvas = canvasRef.current;
if (!canvas) return;
// Convert canvas to blob
canvas.toBlob((blob) => {
if (!blob) return;
share({
title: 'Check out what I created!',
text: 'Made with MyApp - get started for free!',
files: [blob],
filenames: ['my-creation.png'], // Optional custom filename
});
}, 'image/png');
};
return (
<div>
<canvas ref={canvasRef} width={800} height={600} />
<button onClick={handleShare}>Share My Creation</button>
</div>
);
}
Perfect for video editors, screen recorders, animation tools:
function VideoShare() {
const { share } = useGrowthKit();
const [videoBlob, setVideoBlob] = useState<Blob | null>(null);
const handleRecord = async () => {
// Example: Record screen or generate video
const stream = await navigator.mediaDevices.getDisplayMedia();
const mediaRecorder = new MediaRecorder(stream);
const chunks: Blob[] = [];
mediaRecorder.ondataavailable = (e) => chunks.push(e.data);
mediaRecorder.onstop = () => {
const blob = new Blob(chunks, { type: 'video/webm' });
setVideoBlob(blob);
};
mediaRecorder.start();
setTimeout(() => mediaRecorder.stop(), 5000); // Record 5 seconds
};
const handleShare = () => {
if (!videoBlob) return;
share({
title: 'My awesome video!',
text: 'Created with MyApp',
files: [videoBlob],
filenames: ['my-video.webm'],
});
};
return (
<div>
<button onClick={handleRecord}>Record Video</button>
<button onClick={handleShare} disabled={!videoBlob}>
Share Video
</button>
</div>
);
}
function MultipleImagesShare() {
const { share } = useGrowthKit();
const handleShareGallery = async () => {
// Generate or collect multiple images
const blobs = await generateMultipleImages();
share({
title: 'My gallery',
text: 'Check out these images I created!',
files: blobs,
// Auto-generated filenames: share-1234567890-0.png, share-1234567890-1.png, etc.
});
};
return <button onClick={handleShareGallery}>Share Gallery</button>;
}
interface ShareOptions {
// Text content
title?: string; // Share dialog title
text?: string; // Message to share
url?: string; // Override URL (default: referral link)
// File sharing (NEW)
files?: (File | Blob)[]; // Images, videos, or other files
filenames?: string[]; // Custom filenames (optional)
}
On Mobile (Native Share)
On Desktop (Fallback)
1. Always provide meaningful content
// ✅ Good - specific message
share({
title: 'AI-Generated Artwork',
text: 'I just created this with AI! Try it yourself:',
files: [imageBlob]
});
// ❌ Avoid - generic message
share({ files: [imageBlob] });
2. Use appropriate file formats
// ✅ Good - widely supported
canvas.toBlob(blob => share({ files: [blob] }), 'image/png');
// ⚠️ Less compatible
canvas.toBlob(blob => share({ files: [blob] }), 'image/webp');
3. Handle errors gracefully
const handleShare = async () => {
const success = await share({
title: 'My creation',
files: [blob]
});
if (success) {
toast.success('Shared successfully!');
} else {
toast.info('Share cancelled or downloaded');
}
};
4. Optimize file sizes
// Compress images before sharing
canvas.toBlob((blob) => {
share({ files: [blob] });
}, 'image/jpeg', 0.8); // 80% quality
AI Image Generator
function AIImageGenerator() {
const { share, completeAction } = useGrowthKit();
const [generatedImage, setGeneratedImage] = useState<Blob | null>(null);
const generateImage = async () => {
await completeAction('generate', { creditsRequired: 1 });
const blob = await createAIImage();
setGeneratedImage(blob);
};
const shareImage = () => {
if (!generatedImage) return;
share({
title: 'My AI Art',
text: 'Just created this with AI! Get free credits:',
files: [generatedImage],
filenames: ['ai-art.png']
});
};
return (
<div>
<button onClick={generateImage}>Generate (1 credit)</button>
{generatedImage && (
<button onClick={shareImage}>Share & Earn 5 Credits</button>
)}
</div>
);
}
Meme Generator
function MemeGenerator() {
const { share } = useGrowthKit();
const exportAndShare = async () => {
const canvas = document.getElementById('meme-canvas') as HTMLCanvasElement;
canvas.toBlob((blob) => {
if (!blob) return;
share({
title: 'My Meme',
text: 'Made this meme in seconds! Make yours:',
files: [blob],
filenames: ['my-meme.png']
});
}, 'image/png');
};
return <button onClick={exportAndShare}>Share Meme</button>;
}
Video Clip Editor
function VideoClipEditor() {
const { share } = useGrowthKit();
const [editedVideo, setEditedVideo] = useState<Blob | null>(null);
const shareVideo = () => {
if (!editedVideo) return;
share({
title: 'My Video Clip',
text: 'Edited with MyApp - try it free!',
files: [editedVideo],
filenames: [`clip-${Date.now()}.mp4`]
});
};
return <button onClick={shareVideo}>Share Video</button>;
}
| Feature | Chrome | Safari | Firefox | Edge |
|---|---|---|---|---|
| Text Share | ✅ | ✅ | ✅ | ✅ |
| File Share (Mobile) | ✅ | ✅ | ⚠️ | ✅ |
| File Share (Desktop) | ⚠️ | ❌ | ❌ | ⚠️ |
Note: When file sharing is not supported, the SDK automatically falls back to downloading files and copying the message to clipboard.
The SDK provides comprehensive theming support with light, dark, and auto modes that maintains the GrowthKit + FenixBlack brand identity.
light): Clean, bright interfacedark): Dark theme with proper contrastauto): Automatically follows system color scheme preferenceSet the theme in your configuration:
const config = {
apiKey: 'your-api-key',
theme: 'dark', // Options: 'light' | 'dark' | 'auto'
};
Change themes programmatically using the setTheme method:
import { useGrowthKit } from '@fenixblack/growthkit';
function ThemeToggle() {
const { setTheme } = useGrowthKit();
return (
<button onClick={() => setTheme('dark')}>
Switch to Dark Theme
</button>
);
}
import { useState } from 'react';
import { GrowthKitAccountWidget, useGrowthKit } from '@fenixblack/growthkit';
function App() {
const [currentTheme, setCurrentTheme] = useState('auto');
const config = {
apiKey: 'your-api-key',
theme: currentTheme,
};
return (
<GrowthKitAccountWidget config={config}>
<ThemeControls onThemeChange={setCurrentTheme} />
<YourApp />
</GrowthKitAccountWidget>
);
}
function ThemeControls({ onThemeChange }) {
const { setTheme } = useGrowthKit();
const handleThemeChange = (newTheme) => {
setTheme(newTheme); // Update SDK theme
onThemeChange(newTheme); // Update app state
};
return (
<div>
<button onClick={() => handleThemeChange('light')}>☀️ Light</button>
<button onClick={() => handleThemeChange('dark')}>🌙 Dark</button>
<button onClick={() => handleThemeChange('auto')}>⚡ Auto</button>
</div>
);
}
For advanced theming needs, the SDK exports utility functions and types:
import {
getThemeColors,
getEffectiveTheme,
lightTheme,
darkTheme,
createThemeVariables,
onSystemThemeChange
} from '@fenixblack/growthkit';
// Get current theme colors
const colors = getThemeColors('dark');
// Resolve 'auto' to actual theme
const effectiveTheme = getEffectiveTheme('auto'); // Returns 'light' or 'dark'
// Access color palettes directly
console.log(lightTheme.primary); // '#10b981'
console.log(darkTheme.background); // '#1e293b'
// Generate CSS custom properties
const cssVars = createThemeVariables('dark');
// Listen to system theme changes
const cleanup = onSystemThemeChange((isDark) => {
console.log('System theme changed:', isDark ? 'dark' : 'light');
});
Protect content behind waitlist or paywall:
import { GrowthKitGate } from '@fenixblack/growthkit';
<GrowthKitGate config={{ publicKey: 'pk_your_key_here' }}>
{/* Protected content */}
<YourApp />
</GrowthKitGate>
All-in-one account widget with credit display and profile management:
import { GrowthKitAccountWidget } from '@fenixblack/growthkit';
<GrowthKitAccountWidget
config={{
publicKey: 'pk_your_key_here',
language: 'es' // Optional: Set language
}}
position="top-right"
showName={true}
showEmail={true}
theme="auto"
slim={false} // Optional: Enable ultra-minimal mode
slim_labels={true} // Optional: Show labels in slim mode
ref={widgetRef} // Optional: For programmatic control
>
<YourApp />
</GrowthKitAccountWidget>
Features:
slim={true}
slim_labels={true} (default): Shows "X credits, Name"slim_labels={false}: Shows minimal "X" format onlyModern, brandable waitlist screen with three layout options and full app branding support.
import { WaitlistForm } from '@fenixblack/growthkit';
<WaitlistForm
message="Join our exclusive beta"
onSuccess={(position) => console.log(`Position: ${position}`)}
/>
The waitlist screen automatically displays your app's branding when configured in the GrowthKit admin:
Configurable in Admin Dashboard:
The SDK automatically receives and displays:
// No additional code needed! The SDK receives app branding from the API
const gk = useGrowthKit({ publicKey: 'pk_your_key' });
// App branding is automatically available in gk.app
{
name: "MyApp",
description: "Build things faster",
logoUrl: "https://...",
primaryColor: "#6366f1",
waitlistLayout: "centered" // or "split" or "minimal"
}
Centered Layout (Default)
Split Layout
Minimal Layout
<WaitlistForm
message="Custom override message"
onSuccess={(position) => {
console.log(`You're #${position} on the waitlist!`);
// Track conversion, show celebration, etc.
}}
className="custom-class"
style={{ /* custom styles */ }}
/>
Automatic Display:
Smart Fallbacks:
When user joins the waitlist, shows:
// Success state is handled automatically
// But you can hook into it:
<WaitlistForm
onSuccess={(position) => {
// Track analytics
analytics.track('waitlist_joined', { position });
// Show custom celebration
showConfetti();
// Update UI
setUserOnWaitlist(true);
}}
/>
import { WaitlistFormProps, AppBranding } from '@fenixblack/growthkit';
// Full type safety
const props: WaitlistFormProps = {
message: "Join our beta",
onSuccess: (position: number) => console.log(position),
};
// Access app branding type
const branding: AppBranding = {
name: "MyApp",
description: "Build faster",
logoUrl: "https://...",
primaryColor: "#6366f1",
waitlistLayout: "centered",
hideGrowthKitBranding: false,
};
GrowthKit supports multiple waitlists per app using a tag-based system. This allows you to create separate waitlists for different products, features, pricing tiers, or launch phases.
Product Waitlists are ideal for:
App-Level Waitlist is better for:
| Feature | App Waitlist | Product Waitlists |
|---|---|---|
| Number of Lists | Single | Multiple per app |
| Position Tracking | ✅ Yes | ❌ No |
| Credit Rewards | ✅ Yes | ❌ No |
| Same Email | Once only | Multiple products |
| Analytics | App-level | Per-product |
| Auto-Invites | Single schedule | Per-product schedule |
| Custom Fields | Basic | Product-specific |
Product waitlists are configured in the GrowthKit admin dashboard:
The SDK automatically detects product waitlists and provides seamless integration:
import { useGrowthKit } from '@fenixblack/growthkit';
function ProductSignup() {
const { app } = useGrowthKit();
// Access product waitlist configuration from app metadata
const productWaitlists = app?.metadata?.productWaitlists || [];
return (
<div>
{productWaitlists.map((product: any) => (
<ProductWaitlistForm
key={product.tag}
productTag={product.tag}
productName={product.name}
description={product.description}
/>
))}
</div>
);
}
Users can join product waitlists through API calls:
import { useGrowthKit } from '@fenixblack/growthkit';
function PremiumSignup() {
const gk = useGrowthKit();
const [email, setEmail] = useState('');
const [joined, setJoined] = useState(false);
const handleJoin = async () => {
try {
// Join the premium-plan waitlist
const response = await fetch('/api/growthkit/waitlist/product', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email,
productTag: 'premium-plan',
customFields: {
companySize: '50-100',
industry: 'SaaS'
}
})
});
if (response.ok) {
setJoined(true);
}
} catch (error) {
console.error('Failed to join waitlist:', error);
}
};
if (joined) {
return (
<div className="success">
<h3>✨ You're in!</h3>
<p>We'll notify you when Premium is available.</p>
</div>
);
}
return (
<div>
<h2>Join Premium Waitlist</h2>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="your@email.com"
/>
<button onClick={handleJoin}>
Join Waitlist
</button>
</div>
);
}
A single email can join multiple product waitlists:
function MultiProductSignup() {
const [selectedProducts, setSelectedProducts] = useState<string[]>([]);
const joinMultipleWaitlists = async (email: string) => {
// User can join multiple product waitlists
const promises = selectedProducts.map(productTag =>
fetch('/api/growthkit/waitlist/product', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, productTag })
})
);
await Promise.all(promises);
};
return (
<div>
<h2>Which products interest you?</h2>
<label>
<input
type="checkbox"
value="premium-plan"
onChange={(e) => {
if (e.target.checked) {
setSelectedProducts([...selectedProducts, 'premium-plan']);
} else {
setSelectedProducts(selectedProducts.filter(p => p !== 'premium-plan'));
}
}}
/>
Premium Plan
</label>
<label>
<input
type="checkbox"
value="mobile-app"
onChange={(e) => {
if (e.target.checked) {
setSelectedProducts([...selectedProducts, 'mobile-app']);
} else {
setSelectedProducts(selectedProducts.filter(p => p !== 'mobile-app'));
}
}}
/>
Mobile App
</label>
{/* Join button */}
</div>
);
}
GrowthKit supports embedded waitlist widgets that can be placed anywhere on your page using auto-injection or manual placement.
Automatically inject the waitlist widget into any element using CSS selectors:
import { GrowthKitProvider, AutoWaitlistInjector } from '@fenixblack/growthkit';
function App() {
return (
<GrowthKitProvider config={{ publicKey: 'pk_your_key' }}>
{/* Auto-injector watches for configured selector */}
<AutoWaitlistInjector />
{/* Your page content */}
<div className="hero">
<h1>Welcome to Our App</h1>
{/* Widget will auto-inject here if configured */}
<div id="waitlist-placeholder"></div>
</div>
</GrowthKitProvider>
);
}
Configuration in Admin Dashboard:
waitlistLayout to "embed"metadata.waitlistTargetSelector to specify CSS selector (e.g., "#waitlist-placeholder")Place the embedded widget exactly where you need it:
import { EmbedWaitlistWidget, GrowthKitProvider } from '@fenixblack/growthkit';
function LandingPage() {
return (
<GrowthKitProvider config={{ publicKey: 'pk_your_key' }}>
<div className="hero">
<h1>Join Our Beta</h1>
<p>Get early access to amazing features</p>
{/* Manual widget placement */}
<EmbedWaitlistWidget
variant="standard"
onSuccess={(position) => {
console.log(`User joined at position ${position}`);
}}
/>
</div>
</GrowthKitProvider>
);
}
The EmbedWaitlistWidget supports different display variants:
// Standard variant - Full form with branding
<EmbedWaitlistWidget variant="standard" />
// Compact variant - Minimal inline form
<EmbedWaitlistWidget variant="compact" />
// Custom styling
<EmbedWaitlistWidget
variant="standard"
className="custom-waitlist"
style={{ maxWidth: '400px', margin: '0 auto' }}
/>
Use Auto-Injection when:
Use Manual Placement when:
import { GrowthKitProvider, EmbedWaitlistWidget } from '@fenixblack/growthkit';
function MarketingLandingPage() {
const [signupCount, setSignupCount] = useState(0);
return (
<GrowthKitProvider config={{ publicKey: 'pk_your_key' }}>
<div className="landing-page">
{/* Hero Section */}
<section className="hero">
<h1>Revolutionary SaaS Platform</h1>
<p>Join {signupCount}+ others on the waitlist</p>
</section>
{/* Features */}
<section className="features">
{/* Feature content */}
</section>
{/* Embedded Waitlist */}
<section className="signup">
<h2>Get Early Access</h2>
<EmbedWaitlistWidget
variant="standard"
onSuccess={(position) => {
setSignupCount(position);
// Track conversion
analytics.track('waitlist_joined', { position });
}}
/>
</section>
{/* Footer */}
<footer>
{/* Footer content */}
</footer>
</div>
</GrowthKitProvider>
);
}
The SDK is written in TypeScript and provides full type definitions:
import type {
GrowthKitConfig,
GrowthKitHook,
Language,
Translations,
GrowthKitTheme,
ThemeColors,
GrowthKitAccountWidgetRef,
AppBranding,
WaitlistFormProps
} from '@fenixblack/growthkit';
All components, hooks, and utilities are fully typed for the best developer experience.
MIT
FAQs
React SDK for GrowthKit - Intelligent waitlist and referral management
The npm package @fenixblack/growthkit receives a total of 22 weekly downloads. As such, @fenixblack/growthkit popularity was classified as not popular.
We found that @fenixblack/growthkit demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Security researchers report widespread abuse of OpenClaw skills to deliver info-stealing malware, exposing a new supply chain risk as agent ecosystems scale.

Security News
Claude Opus 4.6 has uncovered more than 500 open source vulnerabilities, raising new considerations for disclosure, triage, and patching at scale.

Research
/Security News
Malicious dYdX client packages were published to npm and PyPI after a maintainer compromise, enabling wallet credential theft and remote code execution.