@umituz/react-native-settings 4.21.11 → 4.21.12

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.
Files changed (46) hide show
  1. package/package.json +11 -2
  2. package/src/domains/gamification/components/GamificationScreenWrapper.tsx +42 -87
  3. package/src/domains/gamification/components/index.ts +1 -0
  4. package/src/domains/gamification/index.ts +3 -11
  5. package/src/index.ts +3 -0
  6. package/src/notifications/domains/quietHours/infrastructure/hooks/useQuietHoursActions.ts +52 -0
  7. package/src/notifications/domains/quietHours/presentation/components/QuietHoursCard.tsx +112 -0
  8. package/src/notifications/domains/reminders/infrastructure/config/reminderPresets.ts +120 -0
  9. package/src/notifications/domains/reminders/infrastructure/hooks/useReminderActions.ts +106 -0
  10. package/src/notifications/domains/reminders/infrastructure/storage/RemindersStore.ts +148 -0
  11. package/src/notifications/domains/reminders/presentation/components/FormButton.tsx +66 -0
  12. package/src/notifications/domains/reminders/presentation/components/FrequencySelector.tsx +72 -0
  13. package/src/notifications/domains/reminders/presentation/components/ReminderForm.tsx +169 -0
  14. package/src/notifications/domains/reminders/presentation/components/ReminderItem.tsx +130 -0
  15. package/src/notifications/domains/reminders/presentation/components/TimePresetSelector.tsx +100 -0
  16. package/src/notifications/domains/reminders/presentation/components/WeekdaySelector.tsx +61 -0
  17. package/src/notifications/domains/reminders/presentation/screens/ReminderListScreen.tsx +131 -0
  18. package/src/notifications/index.ts +139 -0
  19. package/src/notifications/infrastructure/config/notificationsConfig.ts +98 -0
  20. package/src/notifications/infrastructure/hooks/useNotificationSettings.ts +37 -0
  21. package/src/notifications/infrastructure/services/NotificationBadgeManager.ts +28 -0
  22. package/src/notifications/infrastructure/services/NotificationManager.ts +138 -0
  23. package/src/notifications/infrastructure/services/NotificationPermissions.ts +80 -0
  24. package/src/notifications/infrastructure/services/NotificationScheduler.ts +77 -0
  25. package/src/notifications/infrastructure/services/NotificationService.ts +50 -0
  26. package/src/notifications/infrastructure/services/types.ts +176 -0
  27. package/src/notifications/infrastructure/storage/NotificationsStore.ts +45 -0
  28. package/src/notifications/infrastructure/utils/dev.ts +25 -0
  29. package/src/notifications/infrastructure/utils/idGenerator.ts +14 -0
  30. package/src/notifications/infrastructure/utils/triggerBuilder.ts +45 -0
  31. package/src/notifications/presentation/components/NotificationsSection.tsx +84 -0
  32. package/src/notifications/presentation/components/RemindersNavRow.styles.ts +38 -0
  33. package/src/notifications/presentation/components/RemindersNavRow.tsx +51 -0
  34. package/src/notifications/presentation/components/SettingRow.tsx +86 -0
  35. package/src/notifications/presentation/hooks/useNotificationSettingsUI.ts +52 -0
  36. package/src/notifications/presentation/hooks/useTimePicker.ts +71 -0
  37. package/src/notifications/presentation/screens/NotificationSettingsScreen.styles.ts +30 -0
  38. package/src/notifications/presentation/screens/NotificationSettingsScreen.tsx +131 -0
  39. package/src/notifications/presentation/screens/NotificationsScreen.tsx +107 -0
  40. package/src/presentation/navigation/SettingsStackNavigator.tsx +21 -11
  41. package/src/presentation/screens/components/sections/FeatureSettingsSection.tsx +1 -1
  42. package/src/domains/gamification/README.md +0 -343
  43. package/src/domains/gamification/components/GamificationSettingsItem.tsx +0 -33
  44. package/src/domains/gamification/examples/gamification.config.example.ts +0 -70
  45. package/src/domains/gamification/examples/localization.example.json +0 -71
  46. package/src/domains/gamification/types/settings.ts +0 -28
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-settings",
3
- "version": "4.21.11",
3
+ "version": "4.21.12",
4
4
  "description": "Complete settings hub for React Native apps - consolidated package with settings, about, legal, appearance, feedback, FAQs, rating, and gamification",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -38,7 +38,6 @@
38
38
  "@umituz/react-native-auth": "latest",
39
39
  "@umituz/react-native-design-system": "latest",
40
40
  "@umituz/react-native-localization": "latest",
41
- "@umituz/react-native-notifications": "latest",
42
41
  "@umituz/react-native-storage": "latest",
43
42
  "@umituz/react-native-tanstack": "latest",
44
43
  "firebase": "^12.7.0"
@@ -48,6 +47,11 @@
48
47
  "@react-navigation/native": ">=6.0.0",
49
48
  "@react-navigation/stack": ">=6.0.0",
50
49
  "@tanstack/react-query": ">=5.0.0",
50
+ "expo-notifications": ">=0.28.0",
51
+ "expo-device": ">=6.0.0",
52
+ "expo-haptics": ">=15.0.0",
53
+ "@react-native-community/datetimepicker": ">=8.0.0",
54
+ "@umituz/react-native-haptics": "latest",
51
55
  "react": ">=19.0.0",
52
56
  "react-native": ">=0.81.0",
53
57
  "react-native-safe-area-context": ">=4.0.0"
@@ -58,6 +62,11 @@
58
62
  "@react-navigation/stack": "^7.6.13",
59
63
  "@tanstack/react-query": "^5.0.0",
60
64
  "@types/react": "~19.1.10",
65
+ "expo-notifications": "~0.27.6",
66
+ "expo-device": "~7.0.0",
67
+ "expo-haptics": "~14.0.0",
68
+ "@react-native-community/datetimepicker": "^8.2.0",
69
+ "@umituz/react-native-haptics": "latest",
61
70
  "@typescript-eslint/eslint-plugin": "^7.18.0",
62
71
  "@typescript-eslint/parser": "^7.18.0",
63
72
  "eslint": "^8.57.0",
@@ -1,91 +1,46 @@
1
- /**
2
- * Gamification Screen Component
3
- * Wrapper for gamification screen
4
- */
1
+ import React from 'react';
2
+ import { useGamification } from '../hooks/useGamification';
3
+ import { GamificationScreen } from './GamificationScreen';
4
+ import type { GamificationConfig } from '../types';
5
5
 
6
- import React, { useMemo } from "react";
7
- import { GamificationScreen as BaseGamificationScreen } from "./GamificationScreen";
8
- import { useGamification } from "../hooks/useGamification";
9
- import { useLocalization } from "@umituz/react-native-localization";
10
- import type { GamificationSettingsConfig } from "../types/settings";
11
-
12
- export interface GamificationScreenWrapperProps {
13
- config: GamificationSettingsConfig;
6
+ interface GamificationScreenWrapperProps {
7
+ config: GamificationConfig;
14
8
  }
15
9
 
16
- /**
17
- * Gamification Screen for Settings
18
- * Displays achievements, levels, streaks, and stats
19
- */
20
- export const GamificationScreenWrapper: React.FC<GamificationScreenWrapperProps> = ({
21
- config,
22
- }) => {
23
- const { t } = useLocalization();
24
- const gamification = useGamification(config.config);
25
-
26
- const screenProps = useMemo(() => {
27
- // Map achievements to screen format
28
- const achievements = gamification.achievements.map((achievement) => ({
29
- id: achievement.id,
30
- title: t(`gamification.achievements.${achievement.id}.title`),
31
- description: t(`gamification.achievements.${achievement.id}.description`),
32
- icon: "trophy",
33
- isUnlocked: achievement.isUnlocked,
34
- progress: achievement.progress,
35
- threshold: achievement.threshold,
36
- }));
37
-
38
- // Map stats
39
- const stats = [
40
- {
41
- icon: "star",
42
- value: gamification.points,
43
- label: t("gamification.stats.totalPoints"),
44
- },
45
- {
46
- icon: "check-circle",
47
- value: gamification.totalTasksCompleted,
48
- label: t("gamification.stats.tasksCompleted"),
49
- },
50
- {
51
- icon: "award",
52
- value: gamification.achievements.filter((a) => a.isUnlocked).length,
53
- label: t("gamification.stats.achievementsUnlocked"),
54
- },
55
- ];
56
-
57
- // Level props
58
- const levelProps = {
59
- level: gamification.level.currentLevel,
60
- points: gamification.level.currentPoints,
61
- levelTitle: t("gamification.level.title", {
62
- level: gamification.level.currentLevel,
63
- }),
64
- pointsToNext: gamification.level.pointsToNext,
65
- progress: gamification.level.progress,
66
- showPoints: true,
67
- };
68
-
69
- // Streak props
70
- const streakProps = gamification.streak.current > 0
71
- ? {
72
- current: gamification.streak.current,
73
- longest: gamification.streak.longest,
74
- currentLabel: t("gamification.streak.current"),
75
- bestLabel: t("gamification.streak.best"),
76
- daysLabel: t("gamification.streak.days"),
77
- }
78
- : undefined;
79
-
80
- return {
81
- ...config.screenProps,
82
- levelProps,
83
- stats,
84
- achievements,
85
- streakProps,
86
- emptyAchievementsText: t("gamification.achievements.empty"),
87
- };
88
- }, [gamification, config.screenProps, t]);
89
-
90
- return <BaseGamificationScreen {...screenProps} />;
10
+ export const GamificationScreenWrapper: React.FC<GamificationScreenWrapperProps> = ({ config }) => {
11
+ const {
12
+ points,
13
+ totalTasksCompleted,
14
+ level,
15
+ streak,
16
+ achievements,
17
+ } = useGamification(config);
18
+
19
+ return (
20
+ <GamificationScreen
21
+ title={config.translations.title}
22
+ statsTitle={config.translations.statsTitle}
23
+ achievementsTitle={config.translations.achievementsTitle}
24
+ streakTitle={config.translations.streakTitle}
25
+ levelProps={{
26
+ level: level.currentLevel,
27
+ currentPoints: level.currentPoints,
28
+ pointsToNext: level.pointsToNext,
29
+ progress: level.progress,
30
+ levelTitle: config.translations.levelTitle,
31
+ }}
32
+ streakProps={{
33
+ streak: streak.current,
34
+ longestStreak: streak.longest,
35
+ title: config.translations.streakTitle,
36
+ }}
37
+ stats={{
38
+ points,
39
+ tasksCompleted: totalTasksCompleted,
40
+ streak: streak.current,
41
+ }}
42
+ achievements={achievements}
43
+ emptyAchievementsText={config.translations.emptyAchievements}
44
+ />
45
+ );
91
46
  };
@@ -11,3 +11,4 @@ export { StreakDisplay, type StreakDisplayProps } from "./StreakDisplay";
11
11
  export { StatsCard, type StatsCardProps } from "./StatsCard";
12
12
  export { AchievementItem, type AchievementItemProps } from "./AchievementItem";
13
13
  export { GamificationScreen, type GamificationScreenProps } from "./GamificationScreen/index";
14
+ export { GamificationScreenWrapper } from "./GamificationScreenWrapper";
@@ -1,5 +1,6 @@
1
1
  /**
2
- * @umituz/react-native-gamification
2
+ * Gamification Domain
3
+ * Part of @umituz/react-native-settings
3
4
  *
4
5
  * Generic gamification system for React Native apps
5
6
  * All text via props - NO hardcoded strings
@@ -19,12 +20,6 @@ export type {
19
20
  GamificationStore,
20
21
  } from "./types";
21
22
 
22
- // Settings Integration Types
23
- export type {
24
- GamificationSettingsConfig,
25
- GamificationMenuConfig,
26
- } from "./types/settings";
27
-
28
23
  // Store
29
24
  export { useGamificationStore } from "./store/gamificationStore";
30
25
 
@@ -58,8 +53,5 @@ export {
58
53
  type AchievementItemProps,
59
54
  GamificationScreen,
60
55
  type GamificationScreenProps,
56
+ GamificationScreenWrapper,
61
57
  } from "./components";
62
-
63
- // Settings Integration Components
64
- export { GamificationScreenWrapper } from "./components/GamificationScreenWrapper";
65
- export { GamificationSettingsItem } from "./components/GamificationSettingsItem";
package/src/index.ts CHANGED
@@ -128,6 +128,9 @@ export * from "./domains/gamification";
128
128
  // PRESENTATION LAYER - Config Creator Utilities
129
129
  // =============================================================================
130
130
 
131
+ // Notifications Domain
132
+ export * from "./notifications";
133
+
131
134
  export {
132
135
  createAppearanceConfig,
133
136
  createLanguageConfig,
@@ -0,0 +1,52 @@
1
+ /**
2
+ * useQuietHoursActions Hook
3
+ * Manages quiet hours configuration
4
+ */
5
+
6
+ import { useCallback } from 'react';
7
+ import { usePreferencesStore, useQuietHours } from '../../../reminders/infrastructure/storage/RemindersStore';
8
+ import type { QuietHoursConfig } from '../../../../infrastructure/services/types';
9
+
10
+ export const useQuietHoursActions = () => {
11
+ const quietHours = useQuietHours();
12
+ const { updateQuietHours } = usePreferencesStore();
13
+
14
+ const setQuietHoursEnabled = useCallback(async (enabled: boolean): Promise<void> => {
15
+ await updateQuietHours({ ...quietHours, enabled });
16
+ }, [quietHours, updateQuietHours]);
17
+
18
+ const setStartTime = useCallback(async (hour: number, minute: number): Promise<void> => {
19
+ await updateQuietHours({ ...quietHours, startHour: hour, startMinute: minute });
20
+ }, [quietHours, updateQuietHours]);
21
+
22
+ const setEndTime = useCallback(async (hour: number, minute: number): Promise<void> => {
23
+ await updateQuietHours({ ...quietHours, endHour: hour, endMinute: minute });
24
+ }, [quietHours, updateQuietHours]);
25
+
26
+ const setQuietHours = useCallback(async (config: QuietHoursConfig): Promise<void> => {
27
+ await updateQuietHours(config);
28
+ }, [updateQuietHours]);
29
+
30
+ const isInQuietHours = useCallback((): boolean => {
31
+ if (!quietHours.enabled) return false;
32
+
33
+ const now = new Date();
34
+ const currentMinutes = now.getHours() * 60 + now.getMinutes();
35
+ const startMinutes = quietHours.startHour * 60 + quietHours.startMinute;
36
+ const endMinutes = quietHours.endHour * 60 + quietHours.endMinute;
37
+
38
+ if (startMinutes <= endMinutes) {
39
+ return currentMinutes >= startMinutes && currentMinutes < endMinutes;
40
+ }
41
+ return currentMinutes >= startMinutes || currentMinutes < endMinutes;
42
+ }, [quietHours]);
43
+
44
+ return {
45
+ quietHours,
46
+ setQuietHoursEnabled,
47
+ setStartTime,
48
+ setEndTime,
49
+ setQuietHours,
50
+ isInQuietHours,
51
+ };
52
+ };
@@ -0,0 +1,112 @@
1
+ /**
2
+ * QuietHoursCard Component
3
+ * Displays and manages quiet hours settings
4
+ */
5
+
6
+ import React, { useMemo } from 'react';
7
+ import { View, TouchableOpacity, StyleSheet } from 'react-native';
8
+ import { AtomicText, AtomicIcon, AtomicCard } from '@umituz/react-native-design-system';
9
+ import { Switch } from 'react-native';
10
+ import { useAppDesignTokens } from '@umituz/react-native-design-system';
11
+ import { SettingRow } from '../../../../presentation/components/SettingRow';
12
+ import type { QuietHoursConfig, QuietHoursTranslations } from '../../../../infrastructure/services/types';
13
+
14
+ export interface QuietHoursCardProps {
15
+ config: QuietHoursConfig;
16
+ translations: QuietHoursTranslations;
17
+ onToggle: (enabled: boolean) => void;
18
+ onStartTimePress: () => void;
19
+ onEndTimePress: () => void;
20
+ }
21
+
22
+ const formatTime = (hour: number, minute: number): string => {
23
+ return `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`;
24
+ };
25
+
26
+ export const QuietHoursCard: React.FC<QuietHoursCardProps> = ({
27
+ config,
28
+ translations,
29
+ onToggle,
30
+ onStartTimePress,
31
+ onEndTimePress,
32
+ }) => {
33
+ const tokens = useAppDesignTokens();
34
+ const styles = useMemo(() => createStyles(tokens), [tokens]);
35
+
36
+ return (
37
+ <AtomicCard style={styles.card}>
38
+ <View style={styles.header}>
39
+ <View style={styles.iconContainer}>
40
+ <AtomicIcon name="moon" size="md" color="primary" />
41
+ </View>
42
+ <View style={styles.headerText}>
43
+ <AtomicText type="bodyLarge">{translations.title}</AtomicText>
44
+ <AtomicText type="bodySmall" style={styles.description}>{translations.description}</AtomicText>
45
+ </View>
46
+ <Switch
47
+ value={config.enabled}
48
+ onValueChange={onToggle}
49
+ trackColor={{ false: tokens.colors.surfaceSecondary, true: tokens.colors.primary }}
50
+ thumbColor={tokens.colors.surface}
51
+ />
52
+ </View>
53
+
54
+ {config.enabled && (
55
+ <View style={styles.timeContainer}>
56
+ <TouchableOpacity style={styles.timeButton} onPress={onStartTimePress} activeOpacity={0.7}>
57
+ <AtomicText type="bodySmall" style={styles.timeLabel}>{translations.startTimeLabel}</AtomicText>
58
+ <AtomicText type="bodyLarge" style={styles.timeValue}>
59
+ {formatTime(config.startHour, config.startMinute)}
60
+ </AtomicText>
61
+ </TouchableOpacity>
62
+
63
+ <View style={styles.timeSeparator}>
64
+ <AtomicIcon name="arrow-forward" size="sm" color="secondary" />
65
+ </View>
66
+
67
+ <TouchableOpacity style={styles.timeButton} onPress={onEndTimePress} activeOpacity={0.7}>
68
+ <AtomicText type="bodySmall" style={styles.timeLabel}>{translations.endTimeLabel}</AtomicText>
69
+ <AtomicText type="bodyLarge" style={styles.timeValue}>
70
+ {formatTime(config.endHour, config.endMinute)}
71
+ </AtomicText>
72
+ </TouchableOpacity>
73
+ </View>
74
+ )}
75
+ </AtomicCard>
76
+ );
77
+ };
78
+
79
+ const createStyles = (tokens: ReturnType<typeof useAppDesignTokens>) =>
80
+ StyleSheet.create({
81
+ card: { padding: 16, backgroundColor: tokens.colors.surface },
82
+ header: { flexDirection: 'row', alignItems: 'center' },
83
+ iconContainer: {
84
+ width: 48,
85
+ height: 48,
86
+ borderRadius: 24,
87
+ backgroundColor: tokens.colors.surfaceSecondary,
88
+ justifyContent: 'center',
89
+ alignItems: 'center',
90
+ marginRight: 12,
91
+ },
92
+ headerText: { flex: 1, marginRight: 12 },
93
+ description: { color: tokens.colors.textSecondary, marginTop: 2 },
94
+ timeContainer: {
95
+ flexDirection: 'row',
96
+ alignItems: 'center',
97
+ marginTop: 16,
98
+ paddingTop: 16,
99
+ borderTopWidth: 1,
100
+ borderTopColor: tokens.colors.surfaceSecondary,
101
+ },
102
+ timeButton: {
103
+ flex: 1,
104
+ backgroundColor: tokens.colors.surfaceSecondary,
105
+ borderRadius: 8,
106
+ padding: 12,
107
+ alignItems: 'center',
108
+ },
109
+ timeLabel: { color: tokens.colors.textSecondary, marginBottom: 4 },
110
+ timeValue: { color: tokens.colors.textPrimary, fontWeight: '600' },
111
+ timeSeparator: { paddingHorizontal: 12 },
112
+ });
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Reminder Presets Configuration
3
+ * Default time presets and frequency options for reminders
4
+ */
5
+
6
+ import type { TimePreset, ReminderFrequency } from '../../../../infrastructure/services/types';
7
+
8
+ // ============================================================================
9
+ // DEFAULT TIME PRESETS
10
+ // ============================================================================
11
+
12
+ export const DEFAULT_TIME_PRESETS: TimePreset[] = [
13
+ {
14
+ id: 'morning',
15
+ hour: 8,
16
+ minute: 0,
17
+ labelKey: 'notifications.presets.morning',
18
+ iconName: 'sunny',
19
+ },
20
+ {
21
+ id: 'noon',
22
+ hour: 12,
23
+ minute: 0,
24
+ labelKey: 'notifications.presets.noon',
25
+ iconName: 'sunny',
26
+ },
27
+ {
28
+ id: 'afternoon',
29
+ hour: 15,
30
+ minute: 0,
31
+ labelKey: 'notifications.presets.afternoon',
32
+ iconName: 'partly-sunny',
33
+ },
34
+ {
35
+ id: 'evening',
36
+ hour: 18,
37
+ minute: 0,
38
+ labelKey: 'notifications.presets.evening',
39
+ iconName: 'partly-sunny',
40
+ },
41
+ {
42
+ id: 'night',
43
+ hour: 21,
44
+ minute: 0,
45
+ labelKey: 'notifications.presets.night',
46
+ iconName: 'moon',
47
+ },
48
+ ];
49
+
50
+ // ============================================================================
51
+ // FREQUENCY OPTIONS
52
+ // ============================================================================
53
+
54
+ export interface FrequencyOption {
55
+ id: ReminderFrequency;
56
+ labelKey: string;
57
+ iconName: string;
58
+ }
59
+
60
+ export const FREQUENCY_OPTIONS: FrequencyOption[] = [
61
+ {
62
+ id: 'once',
63
+ labelKey: 'notifications.frequency.once',
64
+ iconName: 'calendar',
65
+ },
66
+ {
67
+ id: 'daily',
68
+ labelKey: 'notifications.frequency.daily',
69
+ iconName: 'repeat',
70
+ },
71
+ {
72
+ id: 'weekly',
73
+ labelKey: 'notifications.frequency.weekly',
74
+ iconName: 'calendar',
75
+ },
76
+ {
77
+ id: 'monthly',
78
+ labelKey: 'notifications.frequency.monthly',
79
+ iconName: 'calendar',
80
+ },
81
+ ];
82
+
83
+ // ============================================================================
84
+ // WEEKDAY OPTIONS
85
+ // ============================================================================
86
+
87
+ export interface WeekdayOption {
88
+ id: number;
89
+ labelKey: string;
90
+ shortLabelKey: string;
91
+ }
92
+
93
+ export const WEEKDAY_OPTIONS: WeekdayOption[] = [
94
+ { id: 1, labelKey: 'notifications.weekdays.sunday', shortLabelKey: 'notifications.weekdays.sun' },
95
+ { id: 2, labelKey: 'notifications.weekdays.monday', shortLabelKey: 'notifications.weekdays.mon' },
96
+ { id: 3, labelKey: 'notifications.weekdays.tuesday', shortLabelKey: 'notifications.weekdays.tue' },
97
+ { id: 4, labelKey: 'notifications.weekdays.wednesday', shortLabelKey: 'notifications.weekdays.wed' },
98
+ { id: 5, labelKey: 'notifications.weekdays.thursday', shortLabelKey: 'notifications.weekdays.thu' },
99
+ { id: 6, labelKey: 'notifications.weekdays.friday', shortLabelKey: 'notifications.weekdays.fri' },
100
+ { id: 7, labelKey: 'notifications.weekdays.saturday', shortLabelKey: 'notifications.weekdays.sat' },
101
+ ];
102
+
103
+ // ============================================================================
104
+ // HELPER FUNCTIONS
105
+ // ============================================================================
106
+
107
+ export const getTimePresetById = (id: string): TimePreset | undefined => {
108
+ return DEFAULT_TIME_PRESETS.find(preset => preset.id === id);
109
+ };
110
+
111
+ export const formatTime = (hour: number, minute: number): string => {
112
+ const h = hour.toString().padStart(2, '0');
113
+ const m = minute.toString().padStart(2, '0');
114
+ return `${h}:${m}`;
115
+ };
116
+
117
+ export const parseTime = (timeString: string): { hour: number; minute: number } => {
118
+ const [hour, minute] = timeString.split(':').map(Number);
119
+ return { hour: hour || 0, minute: minute || 0 };
120
+ };
@@ -0,0 +1,106 @@
1
+ /**
2
+ * useReminderActions Hook
3
+ * Handles reminder CRUD operations with notification scheduling
4
+ */
5
+
6
+ import { useCallback } from 'react';
7
+ import { useRemindersStore } from '../storage/RemindersStore';
8
+ import { NotificationScheduler } from '../../../../infrastructure/services/NotificationScheduler';
9
+ import { generateReminderId } from '../../../../infrastructure/utils/idGenerator';
10
+ import { buildTrigger } from '../../../../infrastructure/utils/triggerBuilder';
11
+ import type { Reminder, CreateReminderInput, UpdateReminderInput } from '../../../../infrastructure/services/types';
12
+
13
+ const scheduler = new NotificationScheduler();
14
+
15
+ export const useReminderActions = () => {
16
+ const { addReminder, updateReminder, deleteReminder, toggleReminder } = useRemindersStore();
17
+
18
+ const createReminder = useCallback(async (input: CreateReminderInput): Promise<Reminder> => {
19
+ const now = new Date().toISOString();
20
+ const reminder: Reminder = {
21
+ id: generateReminderId(),
22
+ ...input,
23
+ enabled: true,
24
+ createdAt: now,
25
+ updatedAt: now,
26
+ };
27
+
28
+ const trigger = buildTrigger(reminder);
29
+ const notificationId = await scheduler.scheduleNotification({
30
+ title: reminder.title,
31
+ body: reminder.body,
32
+ trigger,
33
+ data: { reminderId: reminder.id },
34
+ });
35
+
36
+ reminder.notificationId = notificationId;
37
+ await addReminder(reminder);
38
+
39
+ return reminder;
40
+ }, [addReminder]);
41
+
42
+ const editReminder = useCallback(async (id: string, input: UpdateReminderInput): Promise<void> => {
43
+ const { reminders } = useRemindersStore.getState();
44
+ const existing = reminders.find(r => r.id === id);
45
+ if (!existing) return;
46
+
47
+ if (existing.notificationId) {
48
+ await scheduler.cancelNotification(existing.notificationId);
49
+ }
50
+
51
+ const updated: Reminder = { ...existing, ...input, updatedAt: new Date().toISOString() };
52
+
53
+ if (updated.enabled) {
54
+ const trigger = buildTrigger(updated);
55
+ const notificationId = await scheduler.scheduleNotification({
56
+ title: updated.title,
57
+ body: updated.body,
58
+ trigger,
59
+ data: { reminderId: updated.id },
60
+ });
61
+ updated.notificationId = notificationId;
62
+ } else {
63
+ updated.notificationId = undefined;
64
+ }
65
+
66
+ await updateReminder(id, updated);
67
+ }, [updateReminder]);
68
+
69
+ const removeReminder = useCallback(async (id: string): Promise<void> => {
70
+ const { reminders } = useRemindersStore.getState();
71
+ const reminder = reminders.find(r => r.id === id);
72
+
73
+ if (reminder?.notificationId) {
74
+ await scheduler.cancelNotification(reminder.notificationId);
75
+ }
76
+
77
+ await deleteReminder(id);
78
+ }, [deleteReminder]);
79
+
80
+ const toggleReminderEnabled = useCallback(async (id: string): Promise<void> => {
81
+ const { reminders } = useRemindersStore.getState();
82
+ const reminder = reminders.find(r => r.id === id);
83
+ if (!reminder) return;
84
+
85
+ if (reminder.enabled && reminder.notificationId) {
86
+ await scheduler.cancelNotification(reminder.notificationId);
87
+ await updateReminder(id, { enabled: false, notificationId: undefined });
88
+ } else if (!reminder.enabled) {
89
+ const trigger = buildTrigger(reminder);
90
+ const notificationId = await scheduler.scheduleNotification({
91
+ title: reminder.title,
92
+ body: reminder.body,
93
+ trigger,
94
+ data: { reminderId: reminder.id },
95
+ });
96
+ await updateReminder(id, { enabled: true, notificationId });
97
+ }
98
+ }, [updateReminder]);
99
+
100
+ return {
101
+ createReminder,
102
+ editReminder,
103
+ removeReminder,
104
+ toggleReminderEnabled,
105
+ };
106
+ };