@potion5/react
v1.0.0
Published
Embeddable React components for the Potion API - AI-powered beverage formulation platform
Downloads
97
Maintainers
Readme
@potion5/react
Embeddable React components for the Potion API. Build beverage formulation features into your application with pre-built, customizable components.
Installation
npm install @potion5/react @potion5/sdk
# or
yarn add @potion5/react @potion5/sdk
# or
pnpm add @potion5/react @potion5/sdkQuick Start
React Applications
import { PotionProvider, FormulationBuilder } from '@potion5/react';
import '@potion5/react/styles';
function App() {
return (
<PotionProvider apiKey="pk_live_your_api_key">
<FormulationBuilder
enableAI={true}
onComplete={(formulation) => {
console.log('Created formulation:', formulation);
}}
/>
</PotionProvider>
);
}Vanilla JavaScript (Non-React Apps)
<script src="https://cdn.potion.com/embed.js"></script>
<div id="potion-container"></div>
<script>
PotionEmbed.create({
apiKey: 'pk_live_your_api_key',
container: '#potion-container',
component: 'FormulationBuilder',
props: {
enableAI: true,
onComplete: function(formulation) {
console.log('Created:', formulation);
}
},
theme: {
mode: 'light',
primaryColor: '#6366f1'
}
});
</script>Components
FormulationBuilder
Create and edit beverage formulations with AI assistance.
<FormulationBuilder
mode="create" // 'create' | 'edit'
formulationId="..." // Required for edit mode
category="energy-drink"
enableAI={true}
onComplete={(formulation) => void}
onCancel={() => void}
onChange={(formulation) => void}
showProperties={true}
/>IngredientSearch
Smart ingredient finder with allergen warnings and GRAS status.
<IngredientSearch
onSelect={(ingredient) => void}
grasOnly={false}
excludeAllergens={['peanuts', 'milk']}
maxResults={20}
showDetails={true}
placeholder="Search ingredients..."
compact={false}
/>ComplianceChecker
Comprehensive compliance checking with checklist generation.
<ComplianceChecker
formulationId="..."
generateChecklist={true}
generateLabelingRequirements={true}
includeRegulatoryCitations={true}
jurisdictions={['CA', 'NY']}
onChecklistGenerated={(items) => void}
onLabelingGenerated={(requirements) => void}
onExport={(format, data) => void}
/>SOPEditor
Full SOP lifecycle management - create, view, and edit.
<SOPEditor
mode="create" // 'create' | 'view' | 'edit'
formulationId="..."
sopId="..." // Required for view/edit
scale="pilot" // 'lab' | 'pilot' | 'commercial'
generateWithAI={true}
showCriticalControlPoints={true}
onSave={(sop) => void}
onPublish={(sop) => void}
onStepComplete={(stepNumber) => void}
onExport={(format, blob) => void}
/>PotionAssistant
Unified AI chat interface for formulation assistance.
<PotionAssistant
formulationId="..." // Optional context
position="inline" // 'bottom-right' | 'bottom-left' | 'inline' | 'fullscreen'
defaultExpanded={true}
placeholder="Ask anything..."
welcomeMessage="Hi! I'm your formulation assistant."
suggestedPrompts={[
"What's the shelf life?",
"Check compliance issues"
]}
enableVisualization={true}
enableActions={true}
onAction={(action, data) => void}
height="600px"
/>Theming
CSS Variables
Override CSS custom properties to match your brand:
:root {
--potion-color-primary: #your-brand-color;
--potion-color-accent: #your-accent-color;
--potion-font-family: 'Your Font', sans-serif;
--potion-radius-default: 8px;
}Theme Provider
<PotionProvider
apiKey="pk_live_xxx"
theme={{
mode: 'light', // 'light' | 'dark' | 'system'
primaryColor: '#6366f1',
accentColor: '#8b5cf6',
borderRadius: 'md', // 'none' | 'sm' | 'md' | 'lg' | 'full'
fontFamily: 'Inter, sans-serif',
}}
>
{/* Your components */}
</PotionProvider>Dynamic Theme Updates
function ThemeSwitcher() {
const { theme, setTheme } = usePotionTheme();
return (
<button onClick={() => setTheme({ mode: 'dark' })}>
Toggle Dark Mode
</button>
);
}Hooks
useFormulation
const { formulation, isLoading, error, refetch, update, remove } = useFormulation({
id: 'formulation-id',
autoFetch: true,
});useFormulationGenerate
const { generate, isGenerating, error, generatedFormulation } = useFormulationGenerate();
await generate({
category: 'energy-drink',
description: 'A refreshing citrus energy drink',
constraints: {
max_ingredients: 10,
exclude_allergens: ['milk'],
},
});useIngredientSearch
const { query, setQuery, results, isSearching, clear } = useIngredientSearch({
debounce: 300,
limit: 20,
grasOnly: true,
});useComplianceCheck
const { result, isChecking, error, check } = useComplianceCheck({
formulationId: 'xxx',
autoCheck: false,
});useComplianceChecklist
const { checklist, isGenerating, generate, updateItem, exportChecklist } = useComplianceChecklist({
formulationId: 'xxx',
includeFederal: true,
states: ['CA', 'NY'],
});useLabelingRequirements
const { requirements, isLoading, isGenerating, fetch, generate } = useLabelingRequirements({
formulationId: 'xxx',
autoFetch: false,
});Vanilla JS Embed API
Create Instance
const instance = PotionEmbed.create({
apiKey: 'pk_live_xxx',
container: '#my-container',
component: 'FormulationBuilder',
props: { enableAI: true },
theme: { mode: 'dark' },
onReady: () => console.log('Ready!'),
onError: (error) => console.error(error),
});Update Props
instance.updateProps({
formulationId: 'new-id',
mode: 'edit',
});Destroy Instance
instance.destroy();
// or
PotionEmbed.destroy('#my-container');
// or destroy all
PotionEmbed.destroyAll();Available Components
console.log(PotionEmbed.components);
// ['FormulationBuilder', 'IngredientSearch', 'ComplianceChecker', 'SOPEditor', 'PotionAssistant']Error Handling
PotionErrorBoundary
Wrap components with error boundary for production-ready error handling:
import { PotionErrorBoundary } from '@potion5/react';
<PotionErrorBoundary
fallback={<CustomErrorUI />}
onError={(error, errorInfo) => {
// Log to error reporting service
logError(error, errorInfo);
}}
showDetails={process.env.NODE_ENV === 'development'}
reportErrors={true}
reportEndpoint="/api/errors"
>
<FormulationBuilder />
</PotionErrorBoundary>Custom Fallback with Error Details
<PotionErrorBoundary
fallback={(details) => (
<div>
<h3>Error: {details.error.message}</h3>
<p>Component: {details.componentName}</p>
<p>Time: {details.timestamp}</p>
{details.errorInfo?.componentStack && (
<pre>{details.errorInfo.componentStack}</pre>
)}
</div>
)}
onRetry={() => console.log('Retrying...')}
>
<FormulationBuilder />
</PotionErrorBoundary>Note: The default fallback UI includes "Try Again" and "Reset" buttons automatically.
For custom fallbacks, use the onRetry and onReset props on the error boundary.
Testing
MockPotionProvider
Test your integrations without hitting real APIs:
import { MockPotionProvider, mockFormulation } from '@potion5/react/testing';
import { render, screen } from '@testing-library/react';
test('renders formulation builder', async () => {
render(
<MockPotionProvider
mockData={{
formulations: [mockFormulation({ name: 'Test Drink' })],
}}
config={{
delay: 0, // No delay for tests
debug: false,
}}
>
<FormulationBuilder />
</MockPotionProvider>
);
expect(await screen.findByText('Create Formulation')).toBeInTheDocument();
});Mock Factories
Create test data with customizable factories:
import {
mockFormulation,
mockIngredient,
mockComplianceCheckResult,
mockSOPDocument,
mockAllergenIngredient,
} from '@potion5/react/testing';
// Basic mock
const formulation = mockFormulation();
// With overrides
const customFormulation = mockFormulation({
name: 'Custom Energy Drink',
category: 'energy-drink',
status: 'approved',
});
// Allergen ingredient
const milkIngredient = mockAllergenIngredient('milk', {
ingredient_name: 'Milk Protein Isolate',
});Test Utilities
import {
createPotionWrapper,
waitForMockDelay,
createMockHandler,
} from '@potion5/react/testing';
// Create wrapper for multiple tests
const wrapper = createPotionWrapper({
mockData: { formulations: [] },
});
render(<FormulationBuilder />, { wrapper });
// Wait for async operations
await waitForMockDelay(100);
// Track callbacks
const onComplete = createMockHandler<Formulation>();
expect(onComplete.calls).toHaveLength(1);Simulating Errors
<MockPotionProvider
config={{
errors: {
formulations: new Error('Network error'),
compliance: 'Service unavailable',
},
}}
>
<FormulationBuilder />
</MockPotionProvider>Iframe Embedding
For non-technical teams or complete isolation, embed via iframe:
Basic Iframe
<iframe
id="potion-embed"
src="https://embed.potion.com/iframe?apiKey=pk_live_xxx&component=FormulationBuilder"
style="width: 100%; height: 600px; border: none;"
></iframe>With Configuration
<iframe
src="https://embed.potion.com/iframe?apiKey=pk_live_xxx&component=FormulationBuilder&themeMode=dark&primaryColor=%236366f1"
></iframe>PostMessage Communication
Listen for events from the iframe:
import { PotionIframeClient } from '@potion5/react/iframe';
// Wait for iframe to be ready
const iframe = document.getElementById('potion-embed');
await PotionIframeClient.waitForReady(iframe);
// Listen for events
const unsubscribe = PotionIframeClient.listen((message) => {
switch (message.type) {
case 'potion:formulation:complete':
console.log('Formulation created:', message.payload);
break;
case 'potion:error':
console.error('Error:', message.payload);
break;
}
});
// Send commands to iframe
PotionIframeClient.updateConfig(iframe, {
props: { mode: 'edit', formulationId: 'xxx' },
});
// Clean up
unsubscribe();URL Parameters
| Parameter | Description | Example |
|-----------|-------------|---------|
| apiKey | API key (required) | pk_live_xxx |
| component | Component name (required) | FormulationBuilder |
| props | JSON props object | %7B%22enableAI%22%3Atrue%7D |
| themeMode | Theme mode | light, dark, system |
| primaryColor | Hex color | %236366f1 |
| debug | Enable debug mode | 1 |
Building Iframe URL
import { buildIframeUrl } from '@potion5/react/iframe';
const url = buildIframeUrl('https://embed.potion.com/iframe', {
apiKey: 'pk_live_xxx',
component: 'FormulationBuilder',
props: { enableAI: true },
theme: { mode: 'dark', primaryColor: '#6366f1' },
});TypeScript Support
All components and hooks are fully typed. Import types as needed:
import type {
Formulation,
Ingredient,
ComplianceCheckResult,
SOPDocument,
FormulationBuilderProps,
PotionErrorBoundaryProps,
ErrorDetails,
} from '@potion5/react';
// Testing types
import type {
MockData,
MockConfig,
MockPotionProviderProps,
} from '@potion5/react/testing';
// Iframe types
import type {
IframeConfig,
PotionMessage,
PotionMessageType,
} from '@potion5/react/iframe';Browser Support
- Chrome (latest)
- Firefox (latest)
- Safari (latest)
- Edge (latest)
License
MIT
