@umituz/react-native-gamification 1.2.1 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,91 @@
1
+ /**
2
+ * useGamification Hook
3
+ * Main hook for gamification operations
4
+ */
5
+
6
+ import { useCallback, useEffect, useMemo } from "react";
7
+ import { useGamificationStore } from "../store/gamificationStore";
8
+ import { calculateLevel } from "../utils/calculations";
9
+ import type { GamificationConfig, LevelState, Achievement } from "../types";
10
+
11
+ export interface UseGamificationReturn {
12
+ // State
13
+ points: number;
14
+ totalTasksCompleted: number;
15
+ level: LevelState;
16
+ streak: { current: number; longest: number };
17
+ achievements: Achievement[];
18
+ isLoading: boolean;
19
+ isInitialized: boolean;
20
+
21
+ // Actions
22
+ initialize: (config: GamificationConfig) => Promise<void>;
23
+ completeTask: () => void;
24
+ addPoints: (amount: number) => void;
25
+ reset: () => Promise<void>;
26
+ }
27
+
28
+ export const useGamification = (
29
+ config?: GamificationConfig
30
+ ): UseGamificationReturn => {
31
+ const store = useGamificationStore();
32
+
33
+ // Auto-initialize if config provided
34
+ useEffect(() => {
35
+ if (config && !store.isInitialized) {
36
+ store.initialize(config);
37
+ }
38
+ }, [config, store.isInitialized]);
39
+
40
+ // Calculate level from config
41
+ const level = useMemo((): LevelState => {
42
+ if (!config?.levels.length) {
43
+ return {
44
+ currentLevel: 1,
45
+ currentPoints: store.points,
46
+ pointsToNext: 50,
47
+ progress: Math.min(100, (store.points / 50) * 100),
48
+ };
49
+ }
50
+ return calculateLevel(store.points, config.levels);
51
+ }, [store.points, config?.levels]);
52
+
53
+ const completeTask = useCallback(() => {
54
+ store.completeTask();
55
+ }, [store.completeTask]);
56
+
57
+ const addPoints = useCallback(
58
+ (amount: number) => {
59
+ store.addPoints(amount);
60
+ },
61
+ [store.addPoints]
62
+ );
63
+
64
+ const initialize = useCallback(
65
+ async (cfg: GamificationConfig) => {
66
+ await store.initialize(cfg);
67
+ },
68
+ [store.initialize]
69
+ );
70
+
71
+ const reset = useCallback(async () => {
72
+ await store.reset();
73
+ }, [store.reset]);
74
+
75
+ return {
76
+ points: store.points,
77
+ totalTasksCompleted: store.totalTasksCompleted,
78
+ level,
79
+ streak: {
80
+ current: store.streak.current,
81
+ longest: store.streak.longest,
82
+ },
83
+ achievements: store.achievements,
84
+ isLoading: store.isLoading,
85
+ isInitialized: store.isInitialized,
86
+ initialize,
87
+ completeTask,
88
+ addPoints,
89
+ reset,
90
+ };
91
+ };
package/src/index.ts CHANGED
@@ -1,125 +1,49 @@
1
1
  /**
2
- * @umituz/react-native-gamification - Public API
2
+ * @umituz/react-native-gamification
3
3
  *
4
- * Comprehensive gamification system for React Native apps
5
- *
6
- * Usage:
7
- * import { useGamification, useAchievements, usePoints } from '@umituz/react-native-gamification';
4
+ * Generic gamification system for React Native apps
5
+ * All text via props - NO hardcoded strings
6
+ * Designed for 100+ apps
8
7
  */
9
8
 
10
- // =============================================================================
11
- // DOMAIN LAYER - Entities
12
- // =============================================================================
13
-
9
+ // Types
14
10
  export type {
15
- Achievement,
16
11
  AchievementDefinition,
17
- AchievementProgress,
18
- AchievementDifficulty,
19
- AchievementCategory,
20
- } from "./domain/entities/Achievement";
21
- export {
22
- createAchievement,
23
- isAchievementComplete,
24
- calculateAchievementProgress,
25
- getPointsForDifficulty,
26
- } from "./domain/entities/Achievement";
27
-
28
- export type {
29
- Point,
30
- PointBalance,
31
- PointTransaction,
32
- } from "./domain/entities/Point";
33
-
34
- export type {
35
- Level,
12
+ Achievement,
36
13
  LevelDefinition,
37
- LevelProgress,
38
- } from "./domain/entities/Level";
14
+ LevelState,
15
+ StreakState,
16
+ GamificationConfig,
17
+ GamificationState,
18
+ GamificationActions,
19
+ GamificationStore,
20
+ } from "./types";
39
21
 
40
- export type {
41
- Streak,
42
- StreakDefinition,
43
- StreakProgress,
44
- StreakMilestone,
45
- } from "./domain/entities/Streak";
46
- export {
47
- createStreakEntity,
48
- isStreakActive,
49
- isStreakBroken,
50
- updateStreakWithActivity,
51
- getStreakMilestones,
52
- getNextMilestone,
53
- getDaysUntilStreakBreak,
54
- } from "./domain/entities/Streak";
22
+ // Store
23
+ export { useGamificationStore } from "./store/gamificationStore";
55
24
 
56
- export type {
57
- Leaderboard,
58
- LeaderboardEntry,
59
- LeaderboardRanking,
60
- LeaderboardCategory,
61
- } from "./domain/entities/Leaderboard";
62
- export {
63
- createLeaderboardEntry,
64
- getRankChangeIndicator,
65
- getTopEntries,
66
- findUserRank,
67
- } from "./domain/entities/Leaderboard";
25
+ // Hooks
26
+ export { useGamification, type UseGamificationReturn } from "./hooks/useGamification";
68
27
 
69
- export type {
70
- Reward,
71
- RewardDefinition,
72
- RewardClaim,
73
- RewardRarity,
74
- } from "./domain/entities/Reward";
28
+ // Utils
75
29
  export {
76
- createReward,
77
- createPointsTransaction,
78
30
  calculateLevel,
79
- calculateNextLevelPoints,
80
- isRewardExpired,
81
- getPointsForRarity,
82
- } from "./domain/entities/Reward";
83
-
84
- export type {
85
- Progress,
86
- ProgressUpdate,
87
- ProgressMilestone,
88
- } from "./domain/entities/Progress";
89
-
90
- // =============================================================================
91
- // DOMAIN LAYER - Repository Interface
92
- // =============================================================================
93
-
94
- export type {
95
- IGamificationRepository,
96
- GamificationError,
97
- GamificationResult,
98
- } from "./domain/repositories/IGamificationRepository";
99
-
100
- // =============================================================================
101
- // INFRASTRUCTURE LAYER
102
- // =============================================================================
103
-
104
- export { storageGamificationRepository } from "./infrastructure/repositories/StorageGamificationRepository";
105
- export { useGamificationStore } from "./infrastructure/storage/GamificationStore";
106
-
107
- // =============================================================================
108
- // PRESENTATION LAYER - Hooks
109
- // =============================================================================
31
+ checkAchievementUnlock,
32
+ updateAchievementProgress,
33
+ isStreakActive,
34
+ isSameDay,
35
+ } from "./utils/calculations";
110
36
 
37
+ // Components
111
38
  export {
112
- useGamification,
113
- useGamificationInitializer,
114
- useGamificationRepository,
115
- } from "./presentation/hooks/useGamification";
116
-
117
- export { useAchievements } from "./presentation/hooks/useAchievements";
118
- export { usePoints } from "./presentation/hooks/usePoints";
119
- export { useLevel } from "./presentation/hooks/useLevel";
120
- export { useStreaks } from "./presentation/hooks/useStreaks";
121
- export { useRewards } from "./presentation/hooks/useRewards";
122
- export { useProgress } from "./presentation/hooks/useProgress";
123
-
124
-
125
-
39
+ LevelProgress,
40
+ type LevelProgressProps,
41
+ PointsBadge,
42
+ type PointsBadgeProps,
43
+ AchievementCard,
44
+ type AchievementCardProps,
45
+ AchievementToast,
46
+ type AchievementToastProps,
47
+ StreakDisplay,
48
+ type StreakDisplayProps,
49
+ } from "./components";
@@ -0,0 +1,167 @@
1
+ /**
2
+ * Gamification Store
3
+ * Zustand store with persist middleware
4
+ */
5
+
6
+ import { create } from "zustand";
7
+ import { persist, createJSONStorage } from "zustand/middleware";
8
+ import AsyncStorage from "@react-native-async-storage/async-storage";
9
+ import type {
10
+ GamificationState,
11
+ GamificationActions,
12
+ GamificationConfig,
13
+ Achievement,
14
+ } from "../types";
15
+ import {
16
+ checkAchievementUnlock,
17
+ updateAchievementProgress,
18
+ isStreakActive,
19
+ isSameDay,
20
+ } from "../utils/calculations";
21
+
22
+ const DEFAULT_STATE: GamificationState = {
23
+ points: 0,
24
+ totalTasksCompleted: 0,
25
+ achievements: [],
26
+ streak: {
27
+ current: 0,
28
+ longest: 0,
29
+ lastActivityDate: null,
30
+ },
31
+ isLoading: false,
32
+ isInitialized: false,
33
+ };
34
+
35
+ let currentConfig: GamificationConfig | null = null;
36
+
37
+ export const useGamificationStore = create<
38
+ GamificationState & GamificationActions
39
+ >()(
40
+ persist(
41
+ (set, get) => ({
42
+ ...DEFAULT_STATE,
43
+
44
+ initialize: async (config: GamificationConfig) => {
45
+ currentConfig = config;
46
+ const state = get();
47
+
48
+ // Initialize achievements from config
49
+ const achievements: Achievement[] = config.achievements.map((def) => ({
50
+ ...def,
51
+ isUnlocked: false,
52
+ progress: 0,
53
+ }));
54
+
55
+ // Merge with existing unlocked achievements
56
+ const mergedAchievements = achievements.map((ach) => {
57
+ const existing = state.achievements.find((a) => a.id === ach.id);
58
+ if (existing) {
59
+ return { ...ach, isUnlocked: existing.isUnlocked, progress: existing.progress };
60
+ }
61
+ return ach;
62
+ });
63
+
64
+ set({ achievements: mergedAchievements, isInitialized: true });
65
+ },
66
+
67
+ addPoints: (amount: number) => {
68
+ set((state) => ({ points: state.points + amount }));
69
+ },
70
+
71
+ completeTask: () => {
72
+ const state = get();
73
+ const pointsToAdd = currentConfig?.pointsPerAction ?? 15;
74
+
75
+ set({
76
+ totalTasksCompleted: state.totalTasksCompleted + 1,
77
+ points: state.points + pointsToAdd,
78
+ });
79
+
80
+ // Update streak
81
+ get().updateStreak();
82
+
83
+ // Check achievements
84
+ get().checkAchievements();
85
+ },
86
+
87
+ updateStreak: () => {
88
+ const state = get();
89
+ const now = new Date();
90
+ const lastDate = state.streak.lastActivityDate
91
+ ? new Date(state.streak.lastActivityDate)
92
+ : null;
93
+
94
+ let newStreak = state.streak.current;
95
+
96
+ if (!lastDate || !isSameDay(lastDate, now)) {
97
+ if (isStreakActive(state.streak.lastActivityDate)) {
98
+ newStreak = state.streak.current + 1;
99
+ } else {
100
+ newStreak = 1;
101
+ }
102
+ }
103
+
104
+ set({
105
+ streak: {
106
+ current: newStreak,
107
+ longest: Math.max(state.streak.longest, newStreak),
108
+ lastActivityDate: now.toISOString(),
109
+ },
110
+ });
111
+ },
112
+
113
+ checkAchievements: () => {
114
+ if (!currentConfig) return [];
115
+
116
+ const state = get();
117
+ const newlyUnlocked: Achievement[] = [];
118
+
119
+ const updatedAchievements = state.achievements.map((ach) => {
120
+ if (ach.isUnlocked) return ach;
121
+
122
+ const progress = updateAchievementProgress(
123
+ ach,
124
+ state.totalTasksCompleted,
125
+ state.streak.current
126
+ );
127
+
128
+ const shouldUnlock = checkAchievementUnlock(
129
+ ach,
130
+ state.totalTasksCompleted,
131
+ state.streak.current
132
+ );
133
+
134
+ if (shouldUnlock && !ach.isUnlocked) {
135
+ const unlocked = {
136
+ ...ach,
137
+ isUnlocked: true,
138
+ unlockedAt: new Date().toISOString(),
139
+ progress: 100,
140
+ };
141
+ newlyUnlocked.push(unlocked);
142
+ return unlocked;
143
+ }
144
+
145
+ return { ...ach, progress };
146
+ });
147
+
148
+ set({ achievements: updatedAchievements });
149
+ return newlyUnlocked;
150
+ },
151
+
152
+ reset: async () => {
153
+ set(DEFAULT_STATE);
154
+ },
155
+ }),
156
+ {
157
+ name: "gamification-storage",
158
+ storage: createJSONStorage(() => AsyncStorage),
159
+ partialize: (state) => ({
160
+ points: state.points,
161
+ totalTasksCompleted: state.totalTasksCompleted,
162
+ achievements: state.achievements,
163
+ streak: state.streak,
164
+ }),
165
+ }
166
+ )
167
+ );
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Gamification Types
3
+ * Generic types for 100+ apps - NO app-specific code
4
+ */
5
+
6
+ // Achievement Definition (provided by app)
7
+ export interface AchievementDefinition {
8
+ id: string;
9
+ threshold: number;
10
+ type: "count" | "streak" | "milestone";
11
+ }
12
+
13
+ // Achievement State (internal)
14
+ export interface Achievement extends AchievementDefinition {
15
+ isUnlocked: boolean;
16
+ unlockedAt?: string;
17
+ progress: number;
18
+ }
19
+
20
+ // Level Definition (provided by app)
21
+ export interface LevelDefinition {
22
+ level: number;
23
+ minPoints: number;
24
+ maxPoints: number;
25
+ }
26
+
27
+ // Level State (internal)
28
+ export interface LevelState {
29
+ currentLevel: number;
30
+ currentPoints: number;
31
+ pointsToNext: number;
32
+ progress: number;
33
+ }
34
+
35
+ // Streak State
36
+ export interface StreakState {
37
+ current: number;
38
+ longest: number;
39
+ lastActivityDate: string | null;
40
+ }
41
+
42
+ // Gamification Config (provided by app via props)
43
+ export interface GamificationConfig {
44
+ storageKey: string;
45
+ achievements: AchievementDefinition[];
46
+ levels: LevelDefinition[];
47
+ pointsPerAction?: number;
48
+ streakBonusMultiplier?: number;
49
+ }
50
+
51
+ // Store State
52
+ export interface GamificationState {
53
+ points: number;
54
+ totalTasksCompleted: number;
55
+ achievements: Achievement[];
56
+ streak: StreakState;
57
+ isLoading: boolean;
58
+ isInitialized: boolean;
59
+ }
60
+
61
+ // Store Actions
62
+ export interface GamificationActions {
63
+ initialize: (config: GamificationConfig) => Promise<void>;
64
+ addPoints: (amount: number) => void;
65
+ completeTask: () => void;
66
+ updateStreak: () => void;
67
+ checkAchievements: () => Achievement[];
68
+ reset: () => Promise<void>;
69
+ }
70
+
71
+ // Combined Store
72
+ export type GamificationStore = GamificationState & GamificationActions;
73
+
74
+ // UI Component Props (all text via props - NO hardcoded strings)
75
+ export interface LevelProgressProps {
76
+ level: number;
77
+ points: number;
78
+ levelTitle: string;
79
+ pointsToNext: number;
80
+ progress: number;
81
+ showPoints?: boolean;
82
+ }
83
+
84
+ export interface AchievementCardProps {
85
+ title: string;
86
+ description: string;
87
+ icon: string;
88
+ isUnlocked: boolean;
89
+ progress: number;
90
+ threshold: number;
91
+ }
92
+
93
+ export interface StreakDisplayProps {
94
+ current: number;
95
+ longest: number;
96
+ streakLabel: string;
97
+ bestLabel: string;
98
+ }
99
+
100
+ export interface PointsBadgeProps {
101
+ points: number;
102
+ label?: string;
103
+ }
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Gamification Calculations
3
+ * Pure utility functions - NO side effects
4
+ */
5
+
6
+ import type { LevelDefinition, LevelState, Achievement, AchievementDefinition } from "../types";
7
+
8
+ export const calculateLevel = (
9
+ points: number,
10
+ levels: LevelDefinition[]
11
+ ): LevelState => {
12
+ const sortedLevels = [...levels].sort((a, b) => a.minPoints - b.minPoints);
13
+
14
+ let currentLevelDef = sortedLevels[0];
15
+ let nextLevelDef: LevelDefinition | null = null;
16
+
17
+ for (let i = 0; i < sortedLevels.length; i++) {
18
+ if (points >= sortedLevels[i].minPoints) {
19
+ currentLevelDef = sortedLevels[i];
20
+ nextLevelDef = sortedLevels[i + 1] || null;
21
+ }
22
+ }
23
+
24
+ const pointsInLevel = points - currentLevelDef.minPoints;
25
+ const levelRange = nextLevelDef
26
+ ? nextLevelDef.minPoints - currentLevelDef.minPoints
27
+ : 100;
28
+ const progress = Math.min(100, (pointsInLevel / levelRange) * 100);
29
+ const pointsToNext = nextLevelDef
30
+ ? nextLevelDef.minPoints - points
31
+ : 0;
32
+
33
+ return {
34
+ currentLevel: currentLevelDef.level,
35
+ currentPoints: points,
36
+ pointsToNext,
37
+ progress,
38
+ };
39
+ };
40
+
41
+ export const checkAchievementUnlock = (
42
+ definition: AchievementDefinition,
43
+ tasksCompleted: number,
44
+ currentStreak: number
45
+ ): boolean => {
46
+ switch (definition.type) {
47
+ case "count":
48
+ return tasksCompleted >= definition.threshold;
49
+ case "streak":
50
+ return currentStreak >= definition.threshold;
51
+ case "milestone":
52
+ return tasksCompleted >= definition.threshold;
53
+ default:
54
+ return false;
55
+ }
56
+ };
57
+
58
+ export const updateAchievementProgress = (
59
+ definition: AchievementDefinition,
60
+ tasksCompleted: number,
61
+ currentStreak: number
62
+ ): number => {
63
+ const value = definition.type === "streak" ? currentStreak : tasksCompleted;
64
+ return Math.min(100, (value / definition.threshold) * 100);
65
+ };
66
+
67
+ export const isStreakActive = (lastActivityDate: string | null): boolean => {
68
+ if (!lastActivityDate) return false;
69
+
70
+ const last = new Date(lastActivityDate);
71
+ const now = new Date();
72
+ const diffDays = Math.floor(
73
+ (now.getTime() - last.getTime()) / (1000 * 60 * 60 * 24)
74
+ );
75
+
76
+ return diffDays <= 1;
77
+ };
78
+
79
+ export const isSameDay = (date1: Date, date2: Date): boolean => {
80
+ return (
81
+ date1.getFullYear() === date2.getFullYear() &&
82
+ date1.getMonth() === date2.getMonth() &&
83
+ date1.getDate() === date2.getDate()
84
+ );
85
+ };
@@ -1,98 +0,0 @@
1
- /**
2
- * Achievement Entity
3
- *
4
- * Represents a user achievement in the gamification system
5
- */
6
-
7
- export interface Achievement {
8
- id: string;
9
- userId: string;
10
- type: string; // Achievement type identifier (e.g., "first_goal", "streak_7_days")
11
- title: string;
12
- description: string;
13
- icon?: string;
14
- category?: string; // Category for grouping achievements
15
- unlocked: boolean;
16
- unlockedDate?: string;
17
- progress: number; // Current progress (0-100 or 0-requirement)
18
- requirement: number; // Required value to unlock
19
- points?: number; // Points awarded when unlocked
20
- rarity?: "common" | "rare" | "epic" | "legendary";
21
- metadata?: Record<string, any>; // Additional metadata
22
- createdDate: string;
23
- updatedDate: string;
24
- }
25
-
26
- export interface AchievementDefinition {
27
- type: string;
28
- title: string;
29
- description: string;
30
- icon?: string;
31
- category?: string;
32
- requirement: number;
33
- points?: number;
34
- rarity?: "common" | "rare" | "epic" | "legendary";
35
- metadata?: Record<string, any>;
36
- }
37
-
38
- export interface AchievementProgress {
39
- achievementId: string;
40
- userId: string;
41
- currentValue: number;
42
- requirement: number;
43
- progress: number; // Percentage (0-100)
44
- unlocked: boolean;
45
- }
46
-
47
- export type AchievementDifficulty = 'easy' | 'medium' | 'hard' | 'legendary';
48
- export type AchievementCategory = 'milestone' | 'challenge' | 'streak' | 'social' | 'special';
49
-
50
- /**
51
- * Factory function to create an achievement
52
- */
53
- export function createAchievement(
54
- props: Omit<Achievement, 'id' | 'unlocked' | 'createdDate' | 'updatedDate'>
55
- ): Achievement {
56
- const now = new Date().toISOString();
57
- return {
58
- ...props,
59
- id: `achievement-${Date.now()}-${Math.random().toString(36).substring(7)}`,
60
- unlocked: props.progress >= props.requirement,
61
- createdDate: now,
62
- updatedDate: now,
63
- };
64
- }
65
-
66
- /**
67
- * Check if achievement is complete
68
- */
69
- export function isAchievementComplete(achievement: Achievement): boolean {
70
- return achievement.progress >= achievement.requirement;
71
- }
72
-
73
- /**
74
- * Calculate achievement progress percentage
75
- */
76
- export function calculateAchievementProgress(achievement: Achievement): number {
77
- if (achievement.requirement === 0) return 0;
78
- return Math.min(100, Math.round((achievement.progress / achievement.requirement) * 100));
79
- }
80
-
81
- /**
82
- * Get points for achievement difficulty/rarity
83
- */
84
- export function getPointsForDifficulty(rarity: Achievement['rarity'] | AchievementDifficulty): number {
85
- const pointsMap: Record<string, number> = {
86
- easy: 10,
87
- medium: 25,
88
- hard: 50,
89
- legendary: 100,
90
- common: 10,
91
- rare: 25,
92
- epic: 50,
93
- };
94
- return pointsMap[rarity || 'common'] || 10;
95
- }
96
-
97
-
98
-