progresspulse-pwa
Version:
A modern PWA for tracking progress and achieving goals with iPhone-style design
166 lines (145 loc) • 5.13 kB
text/typescript
import { create } from 'zustand';
import { db } from '@/lib/database';
import { Goal, Progress, Analytics } from '@/types';
import { toast } from 'sonner';
interface ProgressStore {
goals: Goal[];
recentProgress: Progress[];
analytics: Analytics | null;
isLoading: boolean;
// Actions
loadGoals: (userId: string) => Promise<void>;
createGoal: (goalData: Omit<Goal, 'id' | 'createdAt' | 'updatedAt' | 'currentValue'>) => Promise<void>;
updateGoal: (goalId: string, updates: Partial<Goal>) => Promise<void>;
deleteGoal: (goalId: string) => Promise<void>;
addProgress: (progressData: Omit<Progress, 'id' | 'createdAt'>) => Promise<void>;
loadRecentProgress: (userId: string) => Promise<void>;
loadAnalytics: (userId: string) => Promise<void>;
syncData: (userId: string) => Promise<void>;
}
export const useProgressStore = create<ProgressStore>((set, get) => ({
goals: [],
recentProgress: [],
analytics: null,
isLoading: false,
loadGoals: async (userId: string) => {
set({ isLoading: true });
try {
const goals = await db.getGoalsByUser(userId);
set({ goals });
} catch (error) {
console.error('Failed to load goals:', error);
toast.error('Failed to load goals');
} finally {
set({ isLoading: false });
}
},
createGoal: async (goalData) => {
try {
const newGoalData = {
...goalData,
currentValue: 0,
};
const goalId = await db.createGoal(newGoalData);
const createdGoal = await db.goals.get(goalId);
if (createdGoal) {
set(state => ({
goals: [...state.goals, createdGoal]
}));
toast.success('Goal created successfully!');
}
} catch (error) {
console.error('Failed to create goal:', error);
toast.error('Failed to create goal');
}
},
updateGoal: async (goalId: string, updates: Partial<Goal>) => {
try {
const originalGoal = get().goals.find(g => g.id === goalId);
await db.updateGoal(goalId, updates);
const updatedGoal = await db.goals.get(goalId);
if (updatedGoal) {
set(state => ({
goals: state.goals.map(goal =>
goal.id === goalId ? updatedGoal : goal
)
}));
// Send notification if goal was marked as completed
if (originalGoal && originalGoal.status !== 'completed' && updates.status === 'completed') {
const { notificationService } = await import('@/lib/notifications');
await notificationService.sendGoalCompletedNotification(updatedGoal.userId, updatedGoal.title);
}
toast.success('Goal updated successfully!');
}
} catch (error) {
console.error('Failed to update goal:', error);
toast.error('Failed to update goal');
}
},
deleteGoal: async (goalId: string) => {
try {
await db.deleteGoal(goalId);
set(state => ({
goals: state.goals.filter(goal => goal.id !== goalId)
}));
toast.success('Goal deleted successfully!');
} catch (error) {
console.error('Failed to delete goal:', error);
toast.error('Failed to delete goal');
}
},
addProgress: async (progressData) => {
try {
await db.addProgress(progressData);
// Show progress celebration notification
const { notificationService } = await import('@/lib/notifications');
const goal = get().goals.find(g => g.id === progressData.goalId);
if (goal) {
await notificationService.showProgressCelebration(goal.title, progressData.value);
// Check if goal is completed
if (goal.targetValue && (goal.currentValue + progressData.value) >= goal.targetValue) {
await notificationService.showGoalCompleted(goal.title);
}
}
// Refresh goals to get updated current values
const { loadGoals, loadRecentProgress, loadAnalytics } = get();
await Promise.all([
loadGoals(progressData.userId),
loadRecentProgress(progressData.userId),
loadAnalytics(progressData.userId)
]);
toast.success('Progress updated!');
} catch (error) {
console.error('Failed to add progress:', error);
toast.error('Failed to update progress');
}
},
loadRecentProgress: async (userId: string) => {
try {
const recentProgress = await db.getRecentProgress(userId, 20);
set({ recentProgress });
} catch (error) {
console.error('Failed to load recent progress:', error);
}
},
loadAnalytics: async (userId: string) => {
try {
const analytics = await db.getAnalytics(userId);
set({ analytics });
} catch (error) {
console.error('Failed to load analytics:', error);
}
},
syncData: async (userId: string) => {
const { loadGoals, loadRecentProgress, loadAnalytics } = get();
try {
await Promise.all([
loadGoals(userId),
loadRecentProgress(userId),
loadAnalytics(userId)
]);
} catch (error) {
console.error('Failed to sync data:', error);
}
}
}));