diff --git a/BADGE_SYSTEM_IMPLEMENTATION.md b/BADGE_SYSTEM_IMPLEMENTATION.md new file mode 100644 index 0000000..97e27d3 --- /dev/null +++ b/BADGE_SYSTEM_IMPLEMENTATION.md @@ -0,0 +1,119 @@ +# Badge System Implementation + +## Overview +Implemented a badge achievement system that rewards users based on their impact score. Users earn badges as they contribute to the platform through reports and event participation. + +## Badge Levels + +| Badge | Min Score | Max Score | Image | Description | +|-------|-----------|-----------|-------|-------------| +| Beginner | 0 | 49 | `/Beginner.png` | Starting your environmental journey | +| Intermediate | 50 | 99 | `/Intermediate.png` | Making a real difference | +| Advanced | 100 | 149 | `/Advanced.png` | Dedicated environmental champion | +| Expert | 150 | 199 | `/Expert.png` | Leading the cleanup movement | +| Master | 200+ | ∞ | `/Master.png` | Elite environmental guardian | + +## Impact Score Calculation + +The impact score is calculated based on user activities: +- **Reports submitted**: 10 points each +- **Events attended**: 20 points each +- **Events organized (completed)**: 50 points each + +Formula: `(reports × 10) + (events attended × 20) + (events organized × 50)` + +## Implementation Details + +### Files Created + +1. **`src/utils/badges.ts`** + - Badge type definitions and constants + - `getBadgeForScore()` - Returns badge for a given score + - `getNextBadgeProgress()` - Calculates progress to next badge level + +2. **`src/components/auth/BadgeDisplay.tsx`** + - Reusable badge display component + - Shows current badge with image and description + - Optional progress bar to next badge level + - Three size variants: small, medium, large + +### Files Modified + +1. **`src/pages/UserProfilePage.tsx`** + - Added badge display in statistics section (small size) + - Added dedicated badge progress section (large size with progress bar) + - Shows current badge, description, and progress to next level + +2. **`src/components/auth/index.ts`** + - Exported `BadgeDisplay` component + +## Features + +### Badge Display Component +```typescript + +``` + +**Props:** +- `impactScore` (number, required) - User's current impact score +- `showProgress` (boolean, optional) - Show progress bar to next badge +- `size` ('small' | 'medium' | 'large', optional) - Badge image size + +### Progress Tracking +- Visual progress bar showing advancement to next badge +- Points remaining to next level +- Congratulatory message for Master level achievers + +### Visual Design +- Color-coded badges with gradient backgrounds +- Responsive layout that works on all screen sizes +- Smooth animations for progress bars +- Badge images from `/public` folder + +## User Profile Integration + +The badge system is integrated into the user profile page in two locations: + +1. **Statistics Grid** - Small badge display alongside key metrics +2. **Achievement Section** - Large badge with full progress details + +## Badge Images + +Badge images are stored in the `/public` folder: +- `Beginner.png` +- `Intermediate.png` +- `Advanced.png` +- `Expert.png` +- `Master.png` + +These images are already present in the project and are referenced by the badge system. + +## Future Enhancements + +Potential improvements for the badge system: +- Badge unlock notifications +- Badge showcase on user profile +- Special badges for milestones (e.g., "First Report", "Event Organizer") +- Leaderboard showing top badge holders +- Social sharing of badge achievements +- Badge history timeline + +## Testing + +To test the badge system: +1. Navigate to user profile page +2. View current badge based on impact score +3. Check progress bar showing advancement to next level +4. Verify badge images load correctly +5. Test responsive design on different screen sizes + +## Notes + +- Badge levels are automatically calculated based on impact score +- Progress is shown as a percentage and points remaining +- Master badge holders see a special "Maximum Level Achieved" message +- The system is fully type-safe with TypeScript diff --git a/src/components/auth/BadgeDisplay.tsx b/src/components/auth/BadgeDisplay.tsx new file mode 100644 index 0000000..473e432 --- /dev/null +++ b/src/components/auth/BadgeDisplay.tsx @@ -0,0 +1,69 @@ +import { getBadgeForScore, getNextBadgeProgress } from '@/utils/badges'; + +interface BadgeDisplayProps { + impactScore: number; + showProgress?: boolean; + size?: 'small' | 'medium' | 'large'; +} + +/** + * Display user's badge based on impact score + */ +export function BadgeDisplay({ impactScore, showProgress = false, size = 'medium' }: BadgeDisplayProps) { + const { currentBadge, nextBadge, progress, pointsToNext } = getNextBadgeProgress(impactScore); + + const sizeClasses = { + small: 'h-12 w-12', + medium: 'h-20 w-20', + large: 'h-32 w-32', + }; + + return ( +
+ {/* Badge Image */} +
+ {`${currentBadge.level} +
+ + {/* Badge Info */} +
+ + {currentBadge.level} + +

{currentBadge.description}

+
+ + {/* Progress to Next Badge */} + {showProgress && nextBadge && ( +
+
+ Progress to {nextBadge.level} + {pointsToNext} points to go +
+
+
+
+
+ {currentBadge.minScore} + {nextBadge.minScore} +
+
+ )} + + {/* Master Badge - No Progress */} + {showProgress && !nextBadge && ( +
+

🏆 Maximum Level Achieved!

+

You're an environmental legend

+
+ )} +
+ ); +} diff --git a/src/components/auth/index.ts b/src/components/auth/index.ts index e22706f..1c65fa9 100644 --- a/src/components/auth/index.ts +++ b/src/components/auth/index.ts @@ -3,4 +3,5 @@ export { UserProfile } from './UserProfile'; export { SignInPage } from './SignInPage'; export { SignUpPage } from './SignUpPage'; export { NotificationSettings } from './NotificationSettings'; +export { BadgeDisplay } from './BadgeDisplay'; export type { NotificationPreferences, AreaOfInterest } from './NotificationSettings'; diff --git a/src/pages/UserProfilePage.tsx b/src/pages/UserProfilePage.tsx index 05fcef6..354329c 100644 --- a/src/pages/UserProfilePage.tsx +++ b/src/pages/UserProfilePage.tsx @@ -5,7 +5,7 @@ import { useClerk } from '@clerk/clerk-react'; import { userService, type UserProfile, type UserActivity } from '@/services/userService'; import type { Report, CleanupEvent } from '@/types'; import { MapContainer, TileLayer, Marker, Popup } from 'react-leaflet'; -import { NotificationSettings, type NotificationPreferences } from '@/components/auth'; +import { NotificationSettings, type NotificationPreferences, BadgeDisplay } from '@/components/auth'; import 'leaflet/dist/leaflet.css'; import L from 'leaflet'; @@ -288,20 +288,69 @@ export function UserProfilePage() { )}
- {/* Statistics */} -
-
-

Reports Submitted

-

{profile.reportCount}

+ {/* Statistics and Badge */} +
+ {/* Reports Submitted */} +
+
+
+ + + +
+
+

Reports Submitted

+

{profile.reportCount}

+
+
-
-

Events Attended

-

{profile.eventCount}

+ + {/* Events Attended */} +
+
+
+ + + +
+
+

Events Attended

+

{profile.eventCount}

+
+
-
-

Impact Score

-

{calculateImpactScore()}

+ + {/* Impact Score */} +
+
+
+ + + +
+
+

Impact Score

+

{calculateImpactScore()}

+
+
+ + {/* Current Badge */} +
+ +
+
+ + {/* Badge Progress Section */} +
+
+ + + +

Your Achievement Badge

+
+

Keep contributing to unlock higher badges!

+
diff --git a/src/utils/badges.ts b/src/utils/badges.ts new file mode 100644 index 0000000..b3fcff9 --- /dev/null +++ b/src/utils/badges.ts @@ -0,0 +1,117 @@ +/** + * Badge system utilities for user achievements + */ + +export type BadgeLevel = 'Beginner' | 'Intermediate' | 'Advanced' | 'Expert' | 'Master'; + +export interface Badge { + level: BadgeLevel; + minScore: number; + maxScore: number | null; + image: string; + color: string; + description: string; +} + +export const BADGES: Record = { + Beginner: { + level: 'Beginner', + minScore: 0, + maxScore: 49, + image: '/Beginner.png', + color: 'bg-gray-100 text-gray-800', + description: 'Starting your environmental journey', + }, + Intermediate: { + level: 'Intermediate', + minScore: 50, + maxScore: 99, + image: '/Intermediate.png', + color: 'bg-blue-100 text-blue-800', + description: 'Making a real difference', + }, + Advanced: { + level: 'Advanced', + minScore: 100, + maxScore: 149, + image: '/Advanced.png', + color: 'bg-purple-100 text-purple-800', + description: 'Dedicated environmental champion', + }, + Expert: { + level: 'Expert', + minScore: 150, + maxScore: 199, + image: '/Expert.png', + color: 'bg-orange-100 text-orange-800', + description: 'Leading the cleanup movement', + }, + Master: { + level: 'Master', + minScore: 200, + maxScore: null, + image: '/Master.png', + color: 'bg-yellow-100 text-yellow-800', + description: 'Elite environmental guardian', + }, +}; + +/** + * Get badge level based on impact score + */ +export function getBadgeForScore(score: number): Badge { + if (score >= 200) return BADGES.Master; + if (score >= 150) return BADGES.Expert; + if (score >= 100) return BADGES.Advanced; + if (score >= 50) return BADGES.Intermediate; + return BADGES.Beginner; +} + +/** + * Get next badge level and progress + */ +export function getNextBadgeProgress(score: number): { + currentBadge: Badge; + nextBadge: Badge | null; + progress: number; + pointsToNext: number; +} { + const currentBadge = getBadgeForScore(score); + + // If at Master level, no next badge + if (currentBadge.level === 'Master') { + return { + currentBadge, + nextBadge: null, + progress: 100, + pointsToNext: 0, + }; + } + + // Find next badge + const badgeLevels: BadgeLevel[] = ['Beginner', 'Intermediate', 'Advanced', 'Expert', 'Master']; + const currentIndex = badgeLevels.indexOf(currentBadge.level); + const nextBadgeLevel = badgeLevels[currentIndex + 1]; + if (!nextBadgeLevel) { + return { + currentBadge, + nextBadge: null, + progress: 100, + pointsToNext: 0, + }; + } + const nextBadge = BADGES[nextBadgeLevel]; + + // Calculate progress + const pointsInCurrentLevel = score - currentBadge.minScore; + const pointsNeededForNext = nextBadge.minScore - currentBadge.minScore; + const progress = Math.min(100, (pointsInCurrentLevel / pointsNeededForNext) * 100); + const pointsToNext = nextBadge.minScore - score; + + return { + currentBadge, + nextBadge, + progress, + pointsToNext, + }; +}