@umituz/react-native-gamification 1.1.0 → 1.2.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.
package/LICENSE CHANGED
@@ -20,3 +20,5 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
20
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
21
  SOFTWARE.
22
22
 
23
+
24
+
package/README.md CHANGED
@@ -274,3 +274,5 @@ MIT
274
274
 
275
275
  Ümit UZ <umit@umituz.com>
276
276
 
277
+
278
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-gamification",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "Comprehensive gamification system for React Native apps with achievements, points, levels, streaks, leaderboards, rewards, and progress tracking",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -53,3 +53,5 @@
53
53
  "LICENSE"
54
54
  ]
55
55
  }
56
+
57
+
@@ -44,3 +44,55 @@ export interface AchievementProgress {
44
44
  unlocked: boolean;
45
45
  }
46
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
+
@@ -43,3 +43,59 @@ export interface LeaderboardRanking {
43
43
  belowUsers: number; // Number of users below
44
44
  }
45
45
 
46
+ export type LeaderboardCategory = 'points' | 'achievements' | 'streaks' | 'activity';
47
+
48
+ /**
49
+ * Factory function to create leaderboard entry
50
+ */
51
+ export function createLeaderboardEntry(
52
+ props: Omit<LeaderboardEntry, 'id' | 'createdDate' | 'updatedDate' | 'change'> & { previousRank?: number }
53
+ ): LeaderboardEntry {
54
+ const now = new Date().toISOString();
55
+ const change = props.previousRank ? props.previousRank - props.rank : 0;
56
+
57
+ return {
58
+ ...props,
59
+ id: `entry-${Date.now()}-${Math.random().toString(36).substring(7)}`,
60
+ createdDate: now,
61
+ updatedDate: now,
62
+ metadata: {
63
+ ...props.metadata,
64
+ change,
65
+ },
66
+ };
67
+ }
68
+
69
+ /**
70
+ * Calculate rank change indicator
71
+ */
72
+ export function getRankChangeIndicator(change: number): '↑' | '↓' | '=' {
73
+ if (change > 0) return '↑'; // Improved (moved up)
74
+ if (change < 0) return '↓'; // Declined (moved down)
75
+ return '='; // No change
76
+ }
77
+
78
+ /**
79
+ * Get top N entries from leaderboard
80
+ */
81
+ export function getTopEntries(
82
+ leaderboard: Leaderboard,
83
+ limit: number = 10
84
+ ): LeaderboardEntry[] {
85
+ return leaderboard.entries
86
+ .sort((a, b) => a.rank - b.rank)
87
+ .slice(0, limit);
88
+ }
89
+
90
+ /**
91
+ * Find user's position in leaderboard
92
+ */
93
+ export function findUserRank(
94
+ leaderboard: Leaderboard,
95
+ userId: string
96
+ ): LeaderboardEntry | null {
97
+ return leaderboard.entries.find((entry) => entry.userId === userId) || null;
98
+ }
99
+
100
+
101
+
@@ -36,3 +36,5 @@ export interface LevelProgress {
36
36
  canLevelUp: boolean;
37
37
  }
38
38
 
39
+
40
+
@@ -36,3 +36,5 @@ export interface PointTransaction {
36
36
  createdDate: string;
37
37
  }
38
38
 
39
+
40
+
@@ -39,3 +39,5 @@ export interface ProgressMilestone {
39
39
  metadata?: Record<string, any>;
40
40
  }
41
41
 
42
+
43
+
@@ -4,6 +4,8 @@
4
4
  * Represents rewards in the gamification system
5
5
  */
6
6
 
7
+ import type { PointTransaction } from './Point';
8
+
7
9
  export interface Reward {
8
10
  id: string;
9
11
  userId: string;
@@ -46,3 +48,84 @@ export interface RewardClaim {
46
48
  metadata?: Record<string, any>;
47
49
  }
48
50
 
51
+ export type RewardRarity = 'common' | 'uncommon' | 'rare' | 'epic' | 'legendary';
52
+
53
+ /**
54
+ * Factory function to create reward
55
+ */
56
+ export function createReward(
57
+ props: Omit<Reward, 'id' | 'unlocked' | 'claimed' | 'createdDate' | 'updatedDate'>
58
+ ): Reward {
59
+ const now = new Date().toISOString();
60
+ return {
61
+ ...props,
62
+ id: `reward-${Date.now()}-${Math.random().toString(36).substring(7)}`,
63
+ unlocked: false,
64
+ claimed: false,
65
+ createdDate: now,
66
+ updatedDate: now,
67
+ };
68
+ }
69
+
70
+ /**
71
+ * Factory function to create points transaction
72
+ */
73
+ export function createPointsTransaction(
74
+ userId: string,
75
+ amount: number,
76
+ type: 'earn' | 'spend' | 'bonus',
77
+ source: string,
78
+ description: string
79
+ ): PointTransaction {
80
+ const now = new Date().toISOString();
81
+ return {
82
+ id: `transaction-${Date.now()}-${Math.random().toString(36).substring(7)}`,
83
+ userId,
84
+ amount: type === 'spend' ? -Math.abs(amount) : Math.abs(amount),
85
+ source,
86
+ description,
87
+ balance: 0, // Will be calculated by repository
88
+ createdDate: now,
89
+ };
90
+ }
91
+
92
+ /**
93
+ * Calculate user level from total experience/points
94
+ */
95
+ export function calculateLevel(totalPoints: number): number {
96
+ // Simple level formula: level = floor(sqrt(points / 100))
97
+ return Math.floor(Math.sqrt(totalPoints / 100));
98
+ }
99
+
100
+ /**
101
+ * Calculate points needed for next level
102
+ */
103
+ export function calculateNextLevelPoints(currentLevel: number): number {
104
+ const nextLevel = currentLevel + 1;
105
+ return Math.pow(nextLevel, 2) * 100;
106
+ }
107
+
108
+ /**
109
+ * Check if reward is expired
110
+ */
111
+ export function isRewardExpired(reward: Reward): boolean {
112
+ if (!reward.expiresAt) return false;
113
+ return new Date() > new Date(reward.expiresAt);
114
+ }
115
+
116
+ /**
117
+ * Get points value for rarity
118
+ */
119
+ export function getPointsForRarity(rarity: RewardRarity): number {
120
+ const pointsMap: Record<RewardRarity, number> = {
121
+ common: 50,
122
+ uncommon: 100,
123
+ rare: 250,
124
+ epic: 500,
125
+ legendary: 1000,
126
+ };
127
+ return pointsMap[rarity];
128
+ }
129
+
130
+
131
+
@@ -36,3 +36,119 @@ export interface StreakProgress {
36
36
  nextMilestone?: number; // Next milestone value
37
37
  }
38
38
 
39
+ export interface StreakMilestone {
40
+ days: number;
41
+ name: string;
42
+ reward: number; // Points awarded
43
+ achieved: boolean;
44
+ }
45
+
46
+ /**
47
+ * Factory function to create streak entity
48
+ */
49
+ export function createStreakEntity(userId: string, type: string): Streak {
50
+ const now = new Date().toISOString();
51
+ return {
52
+ id: `streak-${Date.now()}-${Math.random().toString(36).substring(7)}`,
53
+ userId,
54
+ type,
55
+ currentStreak: 0,
56
+ longestStreak: 0,
57
+ lastActivityDate: now,
58
+ isActive: false,
59
+ createdDate: now,
60
+ updatedDate: now,
61
+ };
62
+ }
63
+
64
+ /**
65
+ * Check if streak should continue (activity within last 24 hours)
66
+ */
67
+ export function isStreakActive(streak: Streak): boolean {
68
+ const now = new Date();
69
+ const lastActivity = new Date(streak.lastActivityDate);
70
+ const hoursSinceLastActivity = (now.getTime() - lastActivity.getTime()) / (1000 * 60 * 60);
71
+
72
+ return hoursSinceLastActivity <= 24;
73
+ }
74
+
75
+ /**
76
+ * Check if streak is broken (no activity for more than 48 hours)
77
+ */
78
+ export function isStreakBroken(streak: Streak): boolean {
79
+ const now = new Date();
80
+ const lastActivity = new Date(streak.lastActivityDate);
81
+ const hoursSinceLastActivity = (now.getTime() - lastActivity.getTime()) / (1000 * 60 * 60);
82
+
83
+ return hoursSinceLastActivity > 48;
84
+ }
85
+
86
+ /**
87
+ * Update streak with new activity
88
+ */
89
+ export function updateStreakWithActivity(streak: Streak): Streak {
90
+ const now = new Date().toISOString();
91
+
92
+ // Check if streak is broken
93
+ if (isStreakBroken(streak)) {
94
+ return {
95
+ ...streak,
96
+ currentStreak: 1,
97
+ longestStreak: Math.max(1, streak.longestStreak),
98
+ lastActivityDate: now,
99
+ isActive: true,
100
+ updatedDate: now,
101
+ };
102
+ }
103
+
104
+ // Continue streak
105
+ const newStreak = streak.currentStreak + 1;
106
+ const newLongestStreak = Math.max(newStreak, streak.longestStreak);
107
+
108
+ return {
109
+ ...streak,
110
+ currentStreak: newStreak,
111
+ longestStreak: newLongestStreak,
112
+ lastActivityDate: now,
113
+ isActive: true,
114
+ updatedDate: now,
115
+ };
116
+ }
117
+
118
+ /**
119
+ * Get streak milestones
120
+ */
121
+ export function getStreakMilestones(currentStreak: number): StreakMilestone[] {
122
+ const milestones: StreakMilestone[] = [
123
+ { days: 7, name: 'Week Warrior', reward: 50, achieved: currentStreak >= 7 },
124
+ { days: 14, name: 'Fortnight Fighter', reward: 100, achieved: currentStreak >= 14 },
125
+ { days: 30, name: 'Monthly Master', reward: 250, achieved: currentStreak >= 30 },
126
+ { days: 60, name: 'Consistency Champion', reward: 500, achieved: currentStreak >= 60 },
127
+ { days: 100, name: 'Century Star', reward: 1000, achieved: currentStreak >= 100 },
128
+ { days: 365, name: 'Yearly Legend', reward: 5000, achieved: currentStreak >= 365 },
129
+ ];
130
+
131
+ return milestones;
132
+ }
133
+
134
+ /**
135
+ * Get next milestone for motivation
136
+ */
137
+ export function getNextMilestone(currentStreak: number): StreakMilestone | null {
138
+ const milestones = getStreakMilestones(currentStreak);
139
+ return milestones.find((m) => !m.achieved) || null;
140
+ }
141
+
142
+ /**
143
+ * Calculate days until streak break
144
+ */
145
+ export function getDaysUntilStreakBreak(streak: Streak): number {
146
+ const now = new Date();
147
+ const lastActivity = new Date(streak.lastActivityDate);
148
+ const hoursRemaining = 48 - (now.getTime() - lastActivity.getTime()) / (1000 * 60 * 60);
149
+
150
+ return Math.max(0, Math.ceil(hoursRemaining / 24));
151
+ }
152
+
153
+
154
+
@@ -231,3 +231,5 @@ export interface IGamificationRepository {
231
231
  updateProgress(update: ProgressUpdate): Promise<GamificationResult<Progress>>;
232
232
  }
233
233
 
234
+
235
+
package/src/index.ts CHANGED
@@ -15,6 +15,14 @@ export type {
15
15
  Achievement,
16
16
  AchievementDefinition,
17
17
  AchievementProgress,
18
+ AchievementDifficulty,
19
+ AchievementCategory,
20
+ } from "./domain/entities/Achievement";
21
+ export {
22
+ createAchievement,
23
+ isAchievementComplete,
24
+ calculateAchievementProgress,
25
+ getPointsForDifficulty,
18
26
  } from "./domain/entities/Achievement";
19
27
 
20
28
  export type {
@@ -33,18 +41,44 @@ export type {
33
41
  Streak,
34
42
  StreakDefinition,
35
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,
36
54
  } from "./domain/entities/Streak";
37
55
 
38
56
  export type {
39
57
  Leaderboard,
40
58
  LeaderboardEntry,
41
59
  LeaderboardRanking,
60
+ LeaderboardCategory,
61
+ } from "./domain/entities/Leaderboard";
62
+ export {
63
+ createLeaderboardEntry,
64
+ getRankChangeIndicator,
65
+ getTopEntries,
66
+ findUserRank,
42
67
  } from "./domain/entities/Leaderboard";
43
68
 
44
69
  export type {
45
70
  Reward,
46
71
  RewardDefinition,
47
72
  RewardClaim,
73
+ RewardRarity,
74
+ } from "./domain/entities/Reward";
75
+ export {
76
+ createReward,
77
+ createPointsTransaction,
78
+ calculateLevel,
79
+ calculateNextLevelPoints,
80
+ isRewardExpired,
81
+ getPointsForRarity,
48
82
  } from "./domain/entities/Reward";
49
83
 
50
84
  export type {
@@ -87,3 +121,5 @@ export { useStreaks } from "./presentation/hooks/useStreaks";
87
121
  export { useRewards } from "./presentation/hooks/useRewards";
88
122
  export { useProgress } from "./presentation/hooks/useProgress";
89
123
 
124
+
125
+
@@ -730,3 +730,5 @@ class StorageGamificationRepository implements IGamificationRepository {
730
730
 
731
731
  export const storageGamificationRepository = new StorageGamificationRepository();
732
732
 
733
+
734
+
@@ -346,3 +346,5 @@ export const useGamificationStore = create<GamificationStore>((set, get) => ({
346
346
  },
347
347
  }));
348
348
 
349
+
350
+
@@ -33,3 +33,5 @@ export const useAchievements = () => {
33
33
  };
34
34
  };
35
35
 
36
+
37
+
@@ -89,3 +89,5 @@ export const useGamificationRepository = (repository: IGamificationRepository |
89
89
  }, [repository]);
90
90
  };
91
91
 
92
+
93
+
@@ -35,3 +35,5 @@ export const useLevel = () => {
35
35
  };
36
36
  };
37
37
 
38
+
39
+
@@ -32,3 +32,5 @@ export const usePoints = () => {
32
32
  };
33
33
  };
34
34
 
35
+
36
+
@@ -29,3 +29,5 @@ export const useProgress = () => {
29
29
  };
30
30
  };
31
31
 
32
+
33
+
@@ -39,3 +39,5 @@ export const useRewards = () => {
39
39
  };
40
40
  };
41
41
 
42
+
43
+
@@ -32,3 +32,5 @@ export const useStreaks = () => {
32
32
  };
33
33
  };
34
34
 
35
+
36
+