React Integration Example
Complete React integration example showing event tracking, perk claiming, and wallet management with Loyalteez.
Quick Start
1. Install Dependencies
npm install @privy-io/react-auth ethers
2. Set Up Privy Provider
// src/App.tsx
import { PrivyProvider } from '@privy-io/react-auth';
function App() {
return (
<PrivyProvider
appId={import.meta.env.VITE_PRIVY_APP_ID}
config={{
loginMethods: ['email', 'wallet', 'google'],
appearance: {
theme: 'light',
accentColor: '#6C33EA',
},
}}
>
<YourApp />
</PrivyProvider>
);
}
export default App;
3. Initialize Loyalteez SDK
// src/hooks/useLoyalteez.ts
import { useEffect } from 'react';
export function useLoyalteez() {
useEffect(() => {
// Load SDK script
const script = document.createElement('script');
script.src = 'https://api.loyalteez.app/sdk.js';
script.async = true;
script.onload = () => {
// Initialize SDK
if (typeof LoyalteezAutomation !== 'undefined') {
LoyalteezAutomation.init(import.meta.env.VITE_LOYALTEEZ_BRAND_ID, {
debug: import.meta.env.DEV,
autoTrack: true,
});
}
};
document.head.appendChild(script);
return () => {
document.head.removeChild(script);
};
}, []);
}
4. Track Events
// src/components/SignupForm.tsx
import { useState } from 'react';
declare global {
interface Window {
LoyalteezAutomation: any;
}
}
export function SignupForm() {
const [email, setEmail] = useState('');
const [loading, setLoading] = useState(false);
const handleSignup = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
try {
// Your signup logic here
await createUserAccount(email);
// Track event for LTZ reward
if (window.LoyalteezAutomation) {
await window.LoyalteezAutomation.track('account_creation', {
userEmail: email,
metadata: {
source: 'web_app',
timestamp: Date.now(),
},
});
}
alert('Account created! You earned 100 LTZ! 🎉');
} catch (error) {
console.error('Signup failed:', error);
} finally {
setLoading(false);
}
};
return (
<form onSubmit={handleSignup}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Enter your email"
required
/>
<button type="submit" disabled={loading}>
{loading ? 'Creating account...' : 'Sign Up'}
</button>
</form>
);
}
Complete Example with Perk Claiming
// src/components/PerkCard.tsx
import { usePrivy } from '@privy-io/react-auth';
import { useState } from 'react';
import { ethers } from 'ethers';
const PERK_NFT_ABI = [
'function claimPerk(uint256 perkId) external',
'function getPerkCost(uint256 perkId) external view returns (uint256)',
];
interface PerkCardProps {
perkId: string;
name: string;
description: string;
cost: string;
imageUrl: string;
}
export function PerkCard({ perkId, name, description, cost, imageUrl }: PerkCardProps) {
const { getAccessToken, user, authenticated } = usePrivy();
const [claiming, setClaiming] = useState(false);
const [txHash, setTxHash] = useState<string | null>(null);
const claimPerk = async () => {
if (!authenticated) {
alert('Please sign in first!');
return;
}
setClaiming(true);
try {
// Get Privy access token
const token = await getAccessToken();
// Encode contract function call
const iface = new ethers.Interface(PERK_NFT_ABI);
const data = iface.encodeFunctionData('claimPerk', [perkId]);
// Call gas relayer for gasless transaction
const response = await fetch('https://relayer.loyalteez.app/relay', {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
to: import.meta.env.VITE_PERK_NFT_ADDRESS,
data: data,
userAddress: user?.wallet?.address,
metadata: {
action: 'claim_perk',
perkId: perkId,
},
}),
});
const result = await response.json();
if (result.success) {
setTxHash(result.transactionHash);
alert(`Perk claimed! Transaction: ${result.transactionHash}`);
} else {
throw new Error(result.error || 'Failed to claim perk');
}
} catch (error) {
console.error('Claim failed:', error);
alert('Failed to claim perk. Please try again.');
} finally {
setClaiming(false);
}
};
return (
<div className="perk-card">
<img src={imageUrl} alt={name} />
<h3>{name}</h3>
<p>{description}</p>
<p className="cost">{cost} LTZ</p>
{!authenticated ? (
<button onClick={() => alert('Please sign in first!')}>
Sign In to Claim
</button>
) : (
<button onClick={claimPerk} disabled={claiming}>
{claiming ? 'Claiming...' : 'Claim Perk (No Gas!)'}
</button>
)}
{txHash && (
<a
href={`https://soneium.blockscout.com/tx/${txHash}`}
target="_blank"
rel="noopener noreferrer"
>
View Transaction
</a>
)}
</div>
);
}
Environment Variables
Create a .env file:
# Privy Configuration
VITE_PRIVY_APP_ID=your_privy_app_id
# Loyalteez Configuration
VITE_LOYALTEEZ_BRAND_ID=0x47511fc1c6664c9598974cb112965f8b198e0c725e
# Contract Addresses (Soneium Mainnet)
VITE_LOYALTEEZ_ADDRESS=0x5242b6DB88A72752ac5a54cFe6A7DB8244d743c9
VITE_PERK_NFT_ADDRESS=0x6ae30d6Dcf3e75456B6582b057f1Bf98A90F2CA0
VITE_POINTS_SALE_ADDRESS=0x5269B83F6A4E31bEdFDf5329DC052FBb661e3c72
# Network Configuration
VITE_CHAIN_ID=1868
VITE_RPC_URL=https://rpc.soneium.org
Full App Example
// src/App.tsx
import { PrivyProvider } from '@privy-io/react-auth';
import { useLoyalteez } from './hooks/useLoyalteez';
import { SignupForm } from './components/SignupForm';
import { PerkCard } from './components/PerkCard';
function AppContent() {
// Initialize Loyalteez SDK
useLoyalteez();
const perks = [
{
perkId: '1',
name: 'Free Coffee',
description: 'Redeem for a free coffee at our cafe',
cost: '100',
imageUrl: '/images/coffee.jpg',
},
{
perkId: '2',
name: 'Premium Feature',
description: 'Unlock premium features for 1 month',
cost: '500',
imageUrl: '/images/premium.jpg',
},
];
return (
<div className="app">
<header>
<h1>Loyalteez React Demo</h1>
<p>Earn and redeem rewards with zero blockchain complexity</p>
</header>
<section className="signup-section">
<h2>Sign Up & Earn 100 LTZ</h2>
<SignupForm />
</section>
<section className="perks-section">
<h2>Available Perks</h2>
<div className="perks-grid">
{perks.map((perk) => (
<PerkCard key={perk.perkId} {...perk} />
))}
</div>
</section>
</div>
);
}
function App() {
return (
<PrivyProvider
appId={import.meta.env.VITE_PRIVY_APP_ID}
config={{
loginMethods: ['email', 'wallet', 'google'],
appearance: {
theme: 'light',
accentColor: '#6C33EA',
},
}}
>
<AppContent />
</PrivyProvider>
);
}
export default App;
TypeScript Types
// src/types/loyalteez.d.ts
interface LoyalteezSDK {
init: (brandId: string, options?: LoyalteezOptions) => void;
track: (eventType: string, data: EventData) => Promise<EventResponse>;
}
interface LoyalteezOptions {
debug?: boolean;
autoTrack?: boolean;
}
interface EventData {
userEmail: string;
metadata?: Record<string, any>;
}
interface EventResponse {
success: boolean;
eventId: string;
message: string;
rewardAmount?: number;
transactionHash?: string;
}
declare global {
interface Window {
LoyalteezAutomation: LoyalteezSDK;
}
}
export {};
Testing
// src/components/__tests__/SignupForm.test.tsx
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { SignupForm } from '../SignupForm';
// Mock Loyalteez SDK
window.LoyalteezAutomation = {
init: jest.fn(),
track: jest.fn().mockResolvedValue({
success: true,
eventId: 'test-id',
message: 'Success',
rewardAmount: 100,
}),
};
describe('SignupForm', () => {
it('tracks event on signup', async () => {
render(<SignupForm />);
const emailInput = screen.getByPlaceholderText('Enter your email');
const submitButton = screen.getByRole('button');
fireEvent.change(emailInput, { target: { value: '[email protected]' } });
fireEvent.click(submitButton);
await waitFor(() => {
expect(window.LoyalteezAutomation.track).toHaveBeenCalledWith(
'account_creation',
expect.objectContaining({
userEmail: '[email protected]',
})
);
});
});
});
Next Steps
- SDK Reference - Complete SDK API documentation
- Gas Relayer Integration - Gasless transactions guide
- REST API - Backend integration
- Mobile Integration - React Native version
Support
- GitHub: github.com/Alpha4-Labs/loyalteez-examples
- Email: [email protected]
- Docs: docs.loyalteez.app