Skip to main content

useAchievements

Get all available achievements.
import { useAchievements } from '@zkscore/react';

function AchievementsList() {
  const { achievements, loading, error } = useAchievements();

  if (loading) return <div>Loading achievements...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <div className="achievements-grid">
      {achievements?.map((achievement) => (
        <div key={achievement.id} className="achievement-card">
          <img src={achievement.icon} alt={achievement.name} />
          <h3>{achievement.name}</h3>
          <p>{achievement.description}</p>
          <span className="points">+{achievement.points} points</span>
        </div>
      ))}
    </div>
  );
}

useUserAchievements

Get achievements earned by a user.
import { useUserAchievements } from '@zkscore/react';

function UserAchievements({ address }: { address: string }) {
  const { achievements, loading } = useUserAchievements(address);

  if (loading) return <div>Loading...</div>;

  return (
    <div className="user-achievements">
      <h2>Achievements ({achievements?.length || 0})</h2>
      <div className="grid">
        {achievements?.map((achievement) => (
          <div key={achievement.id} className="earned-achievement">
            <img src={achievement.badge} alt={achievement.name} />
            <h4>{achievement.name}</h4>
            <time>{new Date(achievement.earnedAt).toLocaleDateString()}</time>
          </div>
        ))}
      </div>
    </div>
  );
}

useAchievementProgress

Track progress towards an achievement.
import { useAchievementProgress } from '@zkscore/react';

function AchievementProgress({ address, achievementId }: Props) {
  const { progress, loading } = useAchievementProgress(address, achievementId);

  if (loading) return <div>Loading progress...</div>;
  if (!progress) return null;

  return (
    <div className="progress-card">
      <h3>{progress.achievement.name}</h3>
      <div className="progress-bar">
        <div 
          className="fill" 
          style={{ width: `${progress.percentage}%` }}
        />
      </div>
      <p>{progress.percentage.toFixed(0)}% complete</p>
      
      <div className="requirements">
        {Object.entries(progress.requirements).map(([key, req]) => (
          <div key={key} className="requirement">
            <span>{req.name}</span>
            <span>{req.current} / {req.required}</span>
          </div>
        ))}
      </div>
    </div>
  );
}

useClaimAchievement

Claim an earned achievement.
import { useClaimAchievement } from '@zkscore/react';

function ClaimButton({ achievementId }: { achievementId: string }) {
  const { mutate, loading, error, data } = useClaimAchievement({
    onSuccess: (achievement) => {
      console.log('Achievement claimed:', achievement);
    },
  });

  return (
    <div>
      <button 
        onClick={() => mutate({ achievementId })}
        disabled={loading}
      >
        {loading ? 'Claiming...' : 'Claim Achievement'}
      </button>
      {error && <p className="error">{error.message}</p>}
      {data && <p className="success">Achievement claimed!</p>}
    </div>
  );
}

Advanced Examples

Achievement Showcase

import { useUserAchievements, useAchievements } from '@zkscore/react';

function AchievementShowcase({ address }: { address: string }) {
  const { achievements: all } = useAchievements();
  const { achievements: earned } = useUserAchievements(address);

  const earnedIds = new Set(earned?.map(a => a.id) || []);

  return (
    <div className="showcase">
      <div className="stats">
        <h3>{earned?.length || 0} / {all?.length || 0}</h3>
        <p>Achievements Unlocked</p>
      </div>
      
      <div className="grid">
        {all?.map((achievement) => {
          const isEarned = earnedIds.has(achievement.id);
          return (
            <div 
              key={achievement.id}
              className={`achievement ${isEarned ? 'earned' : 'locked'}`}
            >
              <img 
                src={isEarned ? achievement.badge : achievement.lockedIcon}
                alt={achievement.name}
              />
              <h4>{achievement.name}</h4>
              {isEarned && <span className="check"></span>}
            </div>
          );
        })}
      </div>
    </div>
  );
}

Progress Tracker

import { useAchievementProgress } from '@zkscore/react';

function ProgressTracker({ address }: { address: string }) {
  const achievementIds = ['defi-expert', 'nft-collector', 'trading-pro'];
  
  return (
    <div className="progress-tracker">
      <h2>Your Progress</h2>
      {achievementIds.map((id) => (
        <AchievementProgressItem key={id} address={address} achievementId={id} />
      ))}
    </div>
  );
}

function AchievementProgressItem({ address, achievementId }: Props) {
  const { progress } = useAchievementProgress(address, achievementId);

  if (!progress || progress.completed) return null;

  return (
    <div className="progress-item">
      <div className="header">
        <h4>{progress.achievement.name}</h4>
        <span>{progress.percentage.toFixed(0)}%</span>
      </div>
      <progress value={progress.percentage} max={100} />
    </div>
  );
}

Auto-Claim System

import { useUserAchievements, useClaimAchievement } from '@zkscore/react';
import { useEffect } from 'react';

function AutoClaim({ address }: { address: string }) {
  const { achievements } = useUserAchievements(address);
  const { mutate: claim } = useClaimAchievement();

  useEffect(() => {
    const unclaimedAchievements = achievements?.filter(
      (a) => a.eligible && !a.claimed
    );

    unclaimedAchievements?.forEach((achievement) => {
      claim({ achievementId: achievement.id });
    });
  }, [achievements]);

  return null; // Silent auto-claim component
}

Achievement Notifications

import { useUserAchievements } from '@zkscore/react';
import { useEffect, useState } from 'react';

function AchievementNotifications({ address }: { address: string }) {
  const { achievements } = useUserAchievements(address);
  const [notification, setNotification] = useState<any>(null);
  const [prevCount, setPrevCount] = useState(0);

  useEffect(() => {
    if (achievements && achievements.length > prevCount) {
      const newAchievement = achievements[0]; // Most recent
      setNotification(newAchievement);
      setTimeout(() => setNotification(null), 5000);
    }
    setPrevCount(achievements?.length || 0);
  }, [achievements]);

  if (!notification) return null;

  return (
    <div className="achievement-notification">
      <h3>🎉 Achievement Unlocked!</h3>
      <p>{notification.name}</p>
      <img src={notification.badge} alt={notification.name} />
    </div>
  );
}

TypeScript Types

interface Achievement {
  id: string;
  name: string;
  description: string;
  icon: string;
  badge: string;
  lockedIcon: string;
  points: number;
  rarity: 'common' | 'rare' | 'epic' | 'legendary';
  category: string;
}

interface UserAchievement extends Achievement {
  earnedAt: string;
  claimed: boolean;
  eligible: boolean;
}

interface AchievementProgress {
  achievementId: string;
  achievement: Achievement;
  completed: boolean;
  percentage: number;
  requirements: Record<string, {
    name: string;
    current: number;
    required: number;
    completed: boolean;
  }>;
}

Next Steps