mustafair-carvid-package-hackathon
Version:
CARV ID implementation files for MustaFair decentralized social media platform
195 lines (163 loc) • 5.64 kB
text/typescript
import { useState, useCallback, useEffect } from 'react';
import { useSession } from 'next-auth/react';
import { useAccount } from 'wagmi';
interface Web2Achievement {
provider: 'github' | 'google';
data: any;
achievements: {
tier: 'Bronze' | 'Silver' | 'Gold' | 'Platinum';
badges: string[];
score: number;
};
}
interface Web2AchievementData {
totalScore: number;
overallTier: 'Bronze' | 'Silver' | 'Gold' | 'Platinum';
providers: Web2Achievement[];
combinedBadges: string[];
metadata: {
githubCommits?: number;
githubRepos?: number;
googleAccountAge?: number;
googleEmailCount?: number;
lastUpdated: string;
};
achievementHash: string;
}
interface UseWeb2AchievementsReturn {
achievements: Web2AchievementData | null;
loading: boolean;
error: string | null;
fetchAchievements: () => Promise<void>;
lastFetched: Date | null;
}
// Global cache to prevent redundant API calls
const achievementCache = new Map<string, {
data: Web2AchievementData;
timestamp: number;
expiresAt: number;
}>();
const CACHE_DURATION = 5 * 60 * 1000; // 5 minutes
const RATE_LIMIT_DELAY = 2000; // 2 seconds between calls
let lastCallTime = 0;
export function useWeb2Achievements(): UseWeb2AchievementsReturn {
const { data: session } = useSession();
const { address } = useAccount();
const [achievements, setAchievements] = useState<Web2AchievementData | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [lastFetched, setLastFetched] = useState<Date | null>(null);
const fetchAchievements = useCallback(async () => {
if (!session) return;
// Rate limiting protection
const now = Date.now();
if (now - lastCallTime < RATE_LIMIT_DELAY) {
console.log('Rate limiting: Skipping API call, too soon since last call');
return;
}
// Create cache key based on session data
const cacheKey = `${session.provider}-${session.username || session.user?.email}`;
// Check cache first
const cached = achievementCache.get(cacheKey);
if (cached && now < cached.expiresAt) {
console.log('Using cached achievements data');
setAchievements(cached.data);
setLastFetched(new Date(cached.timestamp));
setError(null);
return;
}
setLoading(true);
setError(null);
lastCallTime = now;
try {
console.log('Fetching fresh achievements data...');
const identities = [];
if (session.provider === 'github' && session.username) {
identities.push({ provider: 'github', username: session.username });
} else if (session.provider === 'google' && session.user?.email) {
identities.push({ provider: 'google', email: session.user.email });
}
if (identities.length === 0) {
throw new Error('No valid identities found');
}
const response = await fetch('/api/web2/achievements', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ identities })
});
if (!response.ok) {
throw new Error(`Failed to fetch achievements: ${response.statusText}`);
}
const data = await response.json();
// Cache the result
achievementCache.set(cacheKey, {
data,
timestamp: now,
expiresAt: now + CACHE_DURATION
});
setAchievements(data);
setLastFetched(new Date());
console.log('Successfully fetched and cached achievements');
} catch (err) {
console.error('Failed to fetch Web2 achievements:', err);
setError(err instanceof Error ? err.message : 'Unknown error');
} finally {
setLoading(false);
}
}, [session]);
// Remove auto-fetch on mount - let user manually initiate
// useEffect(() => {
// if (session && (session.username || session.provider === 'carv-id')) {
// fetchAchievements();
// }
// }, [session, fetchAchievements]);
const bindToWallet = useCallback(async (walletAddress: string): Promise<boolean> => {
if (!achievements) {
setError('No achievements to bind');
return false;
}
try {
const response = await fetch(`/api/reputation/${walletAddress}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
address: walletAddress,
web2Achievements: achievements,
// In production, you'd include a signature here
signature: 'mock_signature'
})
});
if (!response.ok) {
throw new Error(`Failed to bind achievements: ${response.statusText}`);
}
return true;
} catch (err) {
const errorMessage = err instanceof Error ? err.message : 'Unknown error';
setError(errorMessage);
console.error('Failed to bind achievements to wallet:', err);
return false;
}
}, [achievements]);
const getReputationByAddress = useCallback(async (addressToQuery: string) => {
try {
const response = await fetch(`/api/reputation/${addressToQuery}`);
if (!response.ok) {
if (response.status === 404) {
return null; // No reputation data found
}
throw new Error(`Failed to fetch reputation: ${response.statusText}`);
}
return await response.json();
} catch (err) {
console.error('Failed to fetch reputation by address:', err);
throw err;
}
}, []);
return {
achievements,
loading,
error,
fetchAchievements,
lastFetched,
};
}