@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.
@@ -1,363 +0,0 @@
1
- /**
2
- * Gamification Store - Unified State Management
3
- *
4
- * Uses Zustand for state management with repository pattern
5
- * App-specific implementations can provide custom repository
6
- */
7
-
8
- import { create } from "zustand";
9
- import type { IGamificationRepository } from "../../domain/repositories/IGamificationRepository";
10
- import { storageGamificationRepository } from "../repositories/StorageGamificationRepository";
11
- import type { Achievement } from "../../domain/entities/Achievement";
12
- import type { PointBalance, PointTransaction } from "../../domain/entities/Point";
13
- import type { Level, LevelProgress } from "../../domain/entities/Level";
14
- import type { Streak } from "../../domain/entities/Streak";
15
- import type { Leaderboard, LeaderboardEntry, LeaderboardRanking } from "../../domain/entities/Leaderboard";
16
- import type { Reward, RewardClaim } from "../../domain/entities/Reward";
17
- import type { Progress, ProgressUpdate } from "../../domain/entities/Progress";
18
-
19
- interface GamificationStore {
20
- // Repository
21
- repository: IGamificationRepository;
22
-
23
- // State
24
- userId: string | null;
25
- achievements: Achievement[];
26
- pointBalance: PointBalance | null;
27
- pointTransactions: PointTransaction[];
28
- level: Level | null;
29
- streaks: Streak[];
30
- rewards: Reward[];
31
- progress: Progress[];
32
-
33
- // Loading states
34
- isLoading: boolean;
35
- isInitialized: boolean;
36
-
37
- // Actions - Initialization
38
- setUserId: (userId: string) => void;
39
- setRepository: (repository: IGamificationRepository) => void;
40
- initialize: (userId: string) => Promise<void>;
41
-
42
- // Actions - Achievements
43
- loadAchievements: () => Promise<void>;
44
- updateAchievementProgress: (achievementId: string, progressIncrement: number) => Promise<void>;
45
- unlockAchievement: (achievementId: string) => Promise<void>;
46
-
47
- // Actions - Points
48
- loadPointBalance: () => Promise<void>;
49
- loadPointTransactions: (limit?: number) => Promise<void>;
50
- addPoints: (
51
- amount: number,
52
- source: string,
53
- sourceId?: string,
54
- category?: string,
55
- description?: string,
56
- ) => Promise<void>;
57
- deductPoints: (
58
- amount: number,
59
- source: string,
60
- sourceId?: string,
61
- description?: string,
62
- ) => Promise<void>;
63
-
64
- // Actions - Levels
65
- loadLevel: () => Promise<void>;
66
- addExperience: (amount: number, source?: string) => Promise<void>;
67
-
68
- // Actions - Streaks
69
- loadStreaks: () => Promise<void>;
70
- updateStreakActivity: (type: string, activityDate: string) => Promise<void>;
71
-
72
- // Actions - Rewards
73
- loadRewards: () => Promise<void>;
74
- claimReward: (rewardId: string) => Promise<RewardClaim | null>;
75
-
76
- // Actions - Progress
77
- loadProgress: (metric?: string) => Promise<void>;
78
- updateProgress: (update: ProgressUpdate) => Promise<void>;
79
- }
80
-
81
- export const useGamificationStore = create<GamificationStore>((set, get) => ({
82
- // Repository
83
- repository: storageGamificationRepository,
84
-
85
- // State
86
- userId: null,
87
- achievements: [],
88
- pointBalance: null,
89
- pointTransactions: [],
90
- level: null,
91
- streaks: [],
92
- rewards: [],
93
- progress: [],
94
-
95
- // Loading states
96
- isLoading: false,
97
- isInitialized: false,
98
-
99
- // Actions - Initialization
100
- setUserId: (userId: string) => {
101
- set({ userId });
102
- },
103
-
104
- setRepository: (repository: IGamificationRepository) => {
105
- set({ repository });
106
- },
107
-
108
- initialize: async (userId: string) => {
109
- set({ isLoading: true, userId });
110
- const store = get();
111
- try {
112
- await Promise.all([
113
- store.loadAchievements(),
114
- store.loadPointBalance(),
115
- store.loadLevel(),
116
- store.loadStreaks(),
117
- store.loadRewards(),
118
- store.loadProgress(),
119
- ]);
120
- set({ isLoading: false, isInitialized: true });
121
- } catch (error) {
122
- /* eslint-disable-next-line no-console */
123
- if (__DEV__) console.error("Failed to initialize gamification:", error);
124
- set({ isLoading: false, isInitialized: true });
125
- }
126
- },
127
-
128
- // Actions - Achievements
129
- loadAchievements: async () => {
130
- const { userId, repository } = get();
131
- if (!userId) return;
132
- set({ isLoading: true });
133
- const result = await repository.loadAchievements(userId);
134
- if (result.success) {
135
- set({ achievements: result.data, isLoading: false });
136
- } else {
137
- /* eslint-disable-next-line no-console */
138
- if (__DEV__) console.error("Failed to load achievements:", result.error);
139
- set({ isLoading: false });
140
- }
141
- },
142
-
143
- updateAchievementProgress: async (achievementId: string, progressIncrement: number) => {
144
- const { userId, repository } = get();
145
- if (!userId) return;
146
-
147
- // Find existing achievement to get current progress
148
- const existingAchievement = get().achievements.find((a) => a.id === achievementId);
149
- if (!existingAchievement) {
150
- /* eslint-disable-next-line no-console */
151
- if (__DEV__) console.warn(`Achievement ${achievementId} not found, cannot update progress`);
152
- return;
153
- }
154
-
155
- // Calculate new progress (add increment to current progress)
156
- const currentProgress = existingAchievement.progress || 0;
157
- const newProgress = Math.min(currentProgress + progressIncrement, existingAchievement.requirement);
158
-
159
- const result = await repository.updateAchievementProgress(userId, achievementId, newProgress);
160
- if (result.success) {
161
- const achievements = get().achievements.map((a) =>
162
- a.id === achievementId ? result.data : a,
163
- );
164
- set({ achievements });
165
- // Check if achievement should be unlocked
166
- if (newProgress >= result.data.requirement && !result.data.unlocked) {
167
- await get().unlockAchievement(achievementId);
168
- }
169
- }
170
- },
171
-
172
- unlockAchievement: async (achievementId: string) => {
173
- const { userId, repository } = get();
174
- if (!userId) return;
175
- const result = await repository.unlockAchievement(userId, achievementId);
176
- if (result.success) {
177
- const achievements = get().achievements.map((a) =>
178
- a.id === achievementId ? result.data : a,
179
- );
180
- set({ achievements });
181
- // Award points if achievement has points
182
- if (result.data.points) {
183
- await get().addPoints(
184
- result.data.points,
185
- "achievement",
186
- achievementId,
187
- "achievement",
188
- `Unlocked achievement: ${result.data.title}`,
189
- );
190
- }
191
- }
192
- },
193
-
194
- // Actions - Points
195
- loadPointBalance: async () => {
196
- const { userId, repository } = get();
197
- if (!userId) return;
198
- const result = await repository.loadPointBalance(userId);
199
- if (result.success) {
200
- set({ pointBalance: result.data });
201
- }
202
- },
203
-
204
- loadPointTransactions: async (limit?: number) => {
205
- const { userId, repository } = get();
206
- if (!userId) return;
207
- const result = await repository.loadPointTransactions(userId, limit);
208
- if (result.success) {
209
- set({ pointTransactions: result.data });
210
- }
211
- },
212
-
213
- addPoints: async (
214
- amount: number,
215
- source: string,
216
- sourceId?: string,
217
- category?: string,
218
- description?: string,
219
- ) => {
220
- const { userId, repository } = get();
221
- if (!userId) return;
222
- const result = await repository.addPoints(userId, amount, source, sourceId, category, description);
223
- if (result.success) {
224
- await get().loadPointBalance();
225
- await get().loadPointTransactions(10);
226
- }
227
- },
228
-
229
- deductPoints: async (
230
- amount: number,
231
- source: string,
232
- sourceId?: string,
233
- description?: string,
234
- ) => {
235
- const { userId, repository } = get();
236
- if (!userId) return;
237
- const result = await repository.deductPoints(userId, amount, source, sourceId, description);
238
- if (result.success) {
239
- await get().loadPointBalance();
240
- await get().loadPointTransactions(10);
241
- }
242
- },
243
-
244
- // Actions - Levels
245
- loadLevel: async () => {
246
- const { userId, repository } = get();
247
- if (!userId) return;
248
- const result = await repository.loadLevel(userId);
249
- if (result.success) {
250
- set({ level: result.data });
251
- }
252
- },
253
-
254
- addExperience: async (amount: number, source?: string) => {
255
- const { userId, repository } = get();
256
- if (!userId) return;
257
- const result = await repository.addExperience(userId, amount, source);
258
- if (result.success) {
259
- await get().loadLevel();
260
- // Check for level up
261
- if (result.data.canLevelUp) {
262
- // Award points for level up (optional)
263
- await get().addPoints(
264
- result.data.currentLevel * 10,
265
- "level_up",
266
- undefined,
267
- "level",
268
- `Leveled up to level ${result.data.currentLevel}`,
269
- );
270
- }
271
- }
272
- },
273
-
274
- // Actions - Streaks
275
- loadStreaks: async () => {
276
- const { userId, repository } = get();
277
- if (!userId) return;
278
- const result = await repository.loadStreaks(userId);
279
- if (result.success) {
280
- set({ streaks: result.data });
281
- }
282
- },
283
-
284
- updateStreakActivity: async (type: string, activityDate: string) => {
285
- const { userId, repository } = get();
286
- if (!userId) return;
287
- const result = await repository.updateStreakActivity(userId, type, activityDate);
288
- if (result.success) {
289
- const streaks = get().streaks.map((s) => (s.id === result.data.id ? result.data : s));
290
- const existingIndex = streaks.findIndex((s) => s.id === result.data.id);
291
- if (existingIndex === -1) {
292
- streaks.push(result.data);
293
- }
294
- set({ streaks });
295
- }
296
- },
297
-
298
- // Actions - Rewards
299
- loadRewards: async () => {
300
- const { userId, repository } = get();
301
- if (!userId) return;
302
- const result = await repository.loadRewards(userId);
303
- if (result.success) {
304
- set({ rewards: result.data });
305
- }
306
- },
307
-
308
- claimReward: async (rewardId: string): Promise<RewardClaim | null> => {
309
- const { userId, repository } = get();
310
- if (!userId) return null;
311
- const reward = get().rewards.find((r) => r.id === rewardId);
312
- if (reward?.pointsCost) {
313
- const balance = get().pointBalance;
314
- if (!balance || balance.total < reward.pointsCost) {
315
- return null;
316
- }
317
- await get().deductPoints(
318
- reward.pointsCost,
319
- "reward_claim",
320
- rewardId,
321
- `Claimed reward: ${reward.title}`,
322
- );
323
- }
324
- const result = await repository.claimReward(userId, rewardId);
325
- if (result.success) {
326
- await get().loadRewards();
327
- return result.data;
328
- }
329
- return null;
330
- },
331
-
332
- // Actions - Progress
333
- loadProgress: async (metric?: string) => {
334
- const { userId, repository } = get();
335
- if (!userId) return;
336
- const result = await repository.loadProgress(userId, metric);
337
- if (result.success) {
338
- if (metric) {
339
- const existing = get().progress.filter((p) => p.metric !== metric);
340
- set({ progress: [...existing, ...result.data] });
341
- } else {
342
- set({ progress: result.data });
343
- }
344
- }
345
- },
346
-
347
- updateProgress: async (update: ProgressUpdate) => {
348
- const { userId, repository } = get();
349
- if (!userId) return;
350
- const result = await repository.updateProgress(update);
351
- if (result.success) {
352
- const progress = get().progress.map((p) => (p.id === result.data.id ? result.data : p));
353
- const existingIndex = progress.findIndex((p) => p.id === result.data.id);
354
- if (existingIndex === -1) {
355
- progress.push(result.data);
356
- }
357
- set({ progress });
358
- }
359
- },
360
- }));
361
-
362
-
363
-
@@ -1,37 +0,0 @@
1
- /**
2
- * useAchievements Hook
3
- *
4
- * Hook for achievement operations
5
- */
6
-
7
- import { useGamificationStore } from "../../infrastructure/storage/GamificationStore";
8
- import type { Achievement } from "../../domain/entities/Achievement";
9
-
10
- /**
11
- * Hook for achievement operations
12
- */
13
- export const useAchievements = () => {
14
- const store = useGamificationStore();
15
-
16
- return {
17
- achievements: store.achievements,
18
- loadAchievements: store.loadAchievements,
19
- updateAchievementProgress: store.updateAchievementProgress,
20
- unlockAchievement: store.unlockAchievement,
21
- getAchievementById: (id: string): Achievement | undefined => {
22
- return store.achievements.find((a) => a.id === id);
23
- },
24
- getAchievementsByCategory: (category: string): Achievement[] => {
25
- return store.achievements.filter((a) => a.category === category);
26
- },
27
- getUnlockedAchievements: (): Achievement[] => {
28
- return store.achievements.filter((a) => a.unlocked);
29
- },
30
- getLockedAchievements: (): Achievement[] => {
31
- return store.achievements.filter((a) => !a.unlocked);
32
- },
33
- };
34
- };
35
-
36
-
37
-
@@ -1,93 +0,0 @@
1
- /**
2
- * useGamification Hook
3
- *
4
- * Main hook for accessing gamification store
5
- */
6
-
7
- import { useEffect } from "react";
8
- import { useGamificationStore } from "../../infrastructure/storage/GamificationStore";
9
- import type { IGamificationRepository } from "../../domain/repositories/IGamificationRepository";
10
-
11
- /**
12
- * Main hook for gamification operations
13
- */
14
- export const useGamification = () => {
15
- const store = useGamificationStore();
16
-
17
- return {
18
- // State
19
- userId: store.userId,
20
- achievements: store.achievements,
21
- pointBalance: store.pointBalance,
22
- pointTransactions: store.pointTransactions,
23
- level: store.level,
24
- streaks: store.streaks,
25
- rewards: store.rewards,
26
- progress: store.progress,
27
- isLoading: store.isLoading,
28
- isInitialized: store.isInitialized,
29
-
30
- // Actions - Initialization
31
- setUserId: store.setUserId,
32
- setRepository: store.setRepository,
33
- initialize: store.initialize,
34
-
35
- // Actions - Achievements
36
- loadAchievements: store.loadAchievements,
37
- updateAchievementProgress: store.updateAchievementProgress,
38
- unlockAchievement: store.unlockAchievement,
39
-
40
- // Actions - Points
41
- loadPointBalance: store.loadPointBalance,
42
- loadPointTransactions: store.loadPointTransactions,
43
- addPoints: store.addPoints,
44
- deductPoints: store.deductPoints,
45
-
46
- // Actions - Levels
47
- loadLevel: store.loadLevel,
48
- addExperience: store.addExperience,
49
-
50
- // Actions - Streaks
51
- loadStreaks: store.loadStreaks,
52
- updateStreakActivity: store.updateStreakActivity,
53
-
54
- // Actions - Rewards
55
- loadRewards: store.loadRewards,
56
- claimReward: store.claimReward,
57
-
58
- // Actions - Progress
59
- loadProgress: store.loadProgress,
60
- updateProgress: store.updateProgress,
61
- };
62
- };
63
-
64
- /**
65
- * Hook to initialize gamification on mount
66
- */
67
- export const useGamificationInitializer = (userId: string | null) => {
68
- const { initialize, isInitialized } = useGamificationStore();
69
-
70
- useEffect(() => {
71
- if (userId && !isInitialized) {
72
- initialize(userId);
73
- }
74
- // eslint-disable-next-line react-hooks/exhaustive-deps
75
- }, [userId, isInitialized]);
76
- };
77
-
78
- /**
79
- * Hook to set custom repository
80
- */
81
- export const useGamificationRepository = (repository: IGamificationRepository | null) => {
82
- const { setRepository } = useGamificationStore();
83
-
84
- useEffect(() => {
85
- if (repository) {
86
- setRepository(repository);
87
- }
88
- // eslint-disable-next-line react-hooks/exhaustive-deps
89
- }, [repository]);
90
- };
91
-
92
-
93
-
@@ -1,39 +0,0 @@
1
- /**
2
- * useLevel Hook
3
- *
4
- * Hook for level operations
5
- */
6
-
7
- import { useGamificationStore } from "../../infrastructure/storage/GamificationStore";
8
- import type { Level, LevelProgress } from "../../domain/entities/Level";
9
-
10
- /**
11
- * Hook for level operations
12
- */
13
- export const useLevel = () => {
14
- const store = useGamificationStore();
15
-
16
- return {
17
- level: store.level,
18
- loadLevel: store.loadLevel,
19
- addExperience: store.addExperience,
20
- getCurrentLevel: (): number => {
21
- return store.level?.currentLevel || 1;
22
- },
23
- getCurrentExperience: (): number => {
24
- return store.level?.currentExperience || 0;
25
- },
26
- getTotalExperience: (): number => {
27
- return store.level?.totalExperience || 0;
28
- },
29
- getExperienceToNextLevel: (): number => {
30
- return store.level?.experienceToNextLevel || 100;
31
- },
32
- getLevelProgress: (): number => {
33
- return store.level?.levelProgress || 0;
34
- },
35
- };
36
- };
37
-
38
-
39
-
@@ -1,36 +0,0 @@
1
- /**
2
- * usePoints Hook
3
- *
4
- * Hook for points operations
5
- */
6
-
7
- import { useGamificationStore } from "../../infrastructure/storage/GamificationStore";
8
- import type { PointBalance, PointTransaction } from "../../domain/entities/Point";
9
-
10
- /**
11
- * Hook for points operations
12
- */
13
- export const usePoints = () => {
14
- const store = useGamificationStore();
15
-
16
- return {
17
- pointBalance: store.pointBalance,
18
- pointTransactions: store.pointTransactions,
19
- loadPointBalance: store.loadPointBalance,
20
- loadPointTransactions: store.loadPointTransactions,
21
- addPoints: store.addPoints,
22
- deductPoints: store.deductPoints,
23
- getTotalPoints: (): number => {
24
- return store.pointBalance?.total || 0;
25
- },
26
- getPointsByCategory: (category: string): number => {
27
- return store.pointBalance?.byCategory[category] || 0;
28
- },
29
- getRecentTransactions: (limit: number = 10): PointTransaction[] => {
30
- return store.pointTransactions.slice(0, limit);
31
- },
32
- };
33
- };
34
-
35
-
36
-
@@ -1,33 +0,0 @@
1
- /**
2
- * useProgress Hook
3
- *
4
- * Hook for progress tracking operations
5
- */
6
-
7
- import { useGamificationStore } from "../../infrastructure/storage/GamificationStore";
8
- import type { Progress } from "../../domain/entities/Progress";
9
-
10
- /**
11
- * Hook for progress tracking operations
12
- */
13
- export const useProgress = () => {
14
- const store = useGamificationStore();
15
-
16
- return {
17
- progress: store.progress,
18
- loadProgress: store.loadProgress,
19
- updateProgress: store.updateProgress,
20
- getProgressByMetric: (metric: string): Progress | undefined => {
21
- return store.progress.find((p) => p.metric === metric);
22
- },
23
- getProgressByCategory: (category: string): Progress[] => {
24
- return store.progress.filter((p) => p.category === category);
25
- },
26
- getProgressByPeriod: (period: "daily" | "weekly" | "monthly" | "all-time"): Progress[] => {
27
- return store.progress.filter((p) => p.period === period);
28
- },
29
- };
30
- };
31
-
32
-
33
-
@@ -1,43 +0,0 @@
1
- /**
2
- * useRewards Hook
3
- *
4
- * Hook for reward operations
5
- */
6
-
7
- import { useGamificationStore } from "../../infrastructure/storage/GamificationStore";
8
- import type { Reward } from "../../domain/entities/Reward";
9
-
10
- /**
11
- * Hook for reward operations
12
- */
13
- export const useRewards = () => {
14
- const store = useGamificationStore();
15
-
16
- return {
17
- rewards: store.rewards,
18
- loadRewards: store.loadRewards,
19
- claimReward: store.claimReward,
20
- getRewardById: (id: string): Reward | undefined => {
21
- return store.rewards.find((r) => r.id === id);
22
- },
23
- getUnlockedRewards: (): Reward[] => {
24
- return store.rewards.filter((r) => r.unlocked && !r.claimed);
25
- },
26
- getClaimedRewards: (): Reward[] => {
27
- return store.rewards.filter((r) => r.claimed);
28
- },
29
- getAvailableRewards: (): Reward[] => {
30
- const balance = store.pointBalance?.total || 0;
31
- const level = store.level?.currentLevel || 1;
32
- return store.rewards.filter(
33
- (r) =>
34
- !r.claimed &&
35
- (!r.pointsCost || r.pointsCost <= balance) &&
36
- (!r.levelRequired || r.levelRequired <= level),
37
- );
38
- },
39
- };
40
- };
41
-
42
-
43
-
@@ -1,36 +0,0 @@
1
- /**
2
- * useStreaks Hook
3
- *
4
- * Hook for streak operations
5
- */
6
-
7
- import { useGamificationStore } from "../../infrastructure/storage/GamificationStore";
8
- import type { Streak } from "../../domain/entities/Streak";
9
-
10
- /**
11
- * Hook for streak operations
12
- */
13
- export const useStreaks = () => {
14
- const store = useGamificationStore();
15
-
16
- return {
17
- streaks: store.streaks,
18
- loadStreaks: store.loadStreaks,
19
- updateStreakActivity: store.updateStreakActivity,
20
- getStreakByType: (type: string): Streak | undefined => {
21
- return store.streaks.find((s) => s.type === type);
22
- },
23
- getActiveStreaks: (): Streak[] => {
24
- return store.streaks.filter((s) => s.isActive);
25
- },
26
- getLongestStreak: (type?: string): number => {
27
- const streaks = type
28
- ? store.streaks.filter((s) => s.type === type)
29
- : store.streaks;
30
- return Math.max(...streaks.map((s) => s.longestStreak), 0);
31
- },
32
- };
33
- };
34
-
35
-
36
-