mirror of
https://github.com/DocNR/POWR.git
synced 2025-06-06 18:31:03 +00:00

* Add style prop to UserAvatar component for better customization * Refactor UserAvatar to use getAvatarSeed utility for consistent avatar generation * Fix React hook ordering issues in profile/overview.tsx to prevent crashes during auth state changes * Add proper state initialization and cleanup during authentication transitions * Ensure consistent fallback avatar display for unauthenticated users These changes improve stability during login/logout operations and provide better visual continuity with Robohash avatars when profile images aren't available.
9.4 KiB
9.4 KiB
Workout History API Migration Guide
Last Updated: 2025-04-02
Status: Active
Related To: History Overview, UnifiedWorkoutHistoryService
Overview
We've consolidated our workout history services and hooks to provide a more consistent and maintainable API. This guide helps developers migrate from the legacy APIs to the new unified API.
Why We Consolidated
The previous implementation had several overlapping services:
- WorkoutHistoryService - Basic service for local database operations
- EnhancedWorkoutHistoryService - Extended service with advanced features
- NostrWorkoutHistoryService - Service for Nostr integration
- useNostrWorkoutHistory Hook - Hook for fetching workouts from Nostr
This led to:
- Duplicate code and functionality
- Inconsistent APIs
- Confusion about which service to use for what purpose
- Difficulty maintaining and extending the codebase
New Architecture
The new architecture consists of two primary components:
- UnifiedWorkoutHistoryService - A comprehensive service that combines all functionality from previous services
- useWorkoutHistory - A unified hook that provides access to all workout history features
Feature Comparison
Feature | Legacy Implementation | Unified Implementation |
---|---|---|
Local Workouts | WorkoutHistoryService | UnifiedWorkoutHistoryService |
Nostr Workouts | NostrWorkoutHistoryService | UnifiedWorkoutHistoryService |
Filtering | EnhancedWorkoutHistoryService | UnifiedWorkoutHistoryService |
Calendar View | Separate implementation | Integrated in service |
Real-time Updates | Not available | Built-in with subscriptions |
Export Functionality | Not available | Built-in (CSV/JSON) |
Streak Tracking | Not available | Built-in analytics |
Service Migration
Before:
// Using WorkoutHistoryService
const workoutHistoryService = new WorkoutHistoryService(db);
const localWorkouts = await workoutHistoryService.getAllWorkouts();
// Using NostrWorkoutHistoryService
const nostrWorkoutHistoryService = new NostrWorkoutHistoryService(db);
const allWorkouts = await nostrWorkoutHistoryService.getAllWorkouts({
includeNostr: true,
isAuthenticated: true
});
After:
// Using unified UnifiedWorkoutHistoryService
const workoutHistoryService = new UnifiedWorkoutHistoryService(db);
// For local workouts only
const localWorkouts = await workoutHistoryService.getAllWorkouts({
includeNostr: false
});
// For all workouts (local + Nostr)
const allWorkouts = await workoutHistoryService.getAllWorkouts({
includeNostr: true,
isAuthenticated: true
});
Hook Migration
Before:
// Using useNostrWorkoutHistory
const { workouts, loading } = useNostrWorkoutHistory();
// Or manually creating a service in a component
const db = useSQLiteContext();
const workoutHistoryService = React.useMemo(() => new WorkoutHistoryService(db), [db]);
// Then loading workouts
const loadWorkouts = async () => {
const workouts = await workoutHistoryService.getAllWorkouts();
setWorkouts(workouts);
};
After:
// Using unified useWorkoutHistory
const {
workouts,
loading,
refresh,
getWorkoutsByDate,
publishWorkoutToNostr
} = useWorkoutHistory({
includeNostr: true,
realtime: true,
filters: { type: ['strength'] } // Optional filters
});
API Reference
UnifiedWorkoutHistoryService
export class UnifiedWorkoutHistoryService {
constructor(database: SQLiteDatabase);
// Core operations
getAllWorkouts(options?: {
limit?: number;
offset?: number;
includeNostr?: boolean;
isAuthenticated?: boolean;
}): Promise<Workout[]>;
getWorkoutDetails(workoutId: string): Promise<Workout | null>;
// Filtering and Search
filterWorkouts(filters: WorkoutFilters): Promise<Workout[]>;
searchWorkouts(query: string): Promise<Workout[]>;
getWorkoutsByExercise(exerciseId: string): Promise<Workout[]>;
// Calendar operations
getWorkoutsByDate(date: Date): Promise<Workout[]>;
getWorkoutDatesInMonth(year: number, month: number): Promise<Date[]>;
// Nostr operations
publishWorkoutToNostr(workoutId: string): Promise<string>;
importNostrWorkoutToLocal(eventId: string): Promise<string>;
subscribeToNostrWorkouts(pubkey: string, callback: (workout: Workout) => void): string;
unsubscribeFromNostrWorkouts(subId: string): void;
// Analytics
getWorkoutStreak(): Promise<{ current: number; longest: number; lastWorkoutDate: Date | null }>;
getWorkoutCount(): Promise<number>;
// Export
exportWorkoutHistory(format: 'csv' | 'json'): Promise<string>;
// Status
getWorkoutSyncStatus(workoutId: string): Promise<WorkoutSyncStatus>;
updateWorkoutNostrStatus(workoutId: string, eventId: string, relayCount: number): Promise<boolean>;
}
useWorkoutHistory
export function useWorkoutHistory(options: {
includeNostr?: boolean;
filters?: WorkoutFilters;
realtime?: boolean;
} = {}): {
workouts: Workout[];
loading: boolean;
error: Error | null;
refreshing: boolean;
refresh: () => Promise<Workout[]>;
getWorkoutsByDate: (date: Date) => Promise<Workout[]>;
getWorkoutDetails: (workoutId: string) => Promise<Workout | null>;
publishWorkoutToNostr: (workoutId: string) => Promise<string>;
importNostrWorkoutToLocal: (eventId: string) => Promise<string>;
service: UnifiedWorkoutHistoryService;
}
Migration Examples
Calendar View
// Before
const workoutHistoryService = React.useMemo(() => new WorkoutHistoryService(db), [db]);
const loadWorkouts = async () => {
const allWorkouts = await workoutHistoryService.getAllWorkouts();
setWorkouts(allWorkouts);
};
// After
const {
workouts: allWorkouts,
loading,
refresh,
getWorkoutsByDate
} = useWorkoutHistory({
includeNostr: true,
realtime: true
});
Workout History Screen
// Before
const workoutHistoryService = React.useMemo(() => new WorkoutHistoryService(db), [db]);
const loadWorkouts = async () => {
let allWorkouts = [];
if (includeNostr) {
allWorkouts = await workoutHistoryService.getAllWorkouts();
} else {
allWorkouts = await workoutHistoryService.filterWorkouts({
source: ['local']
});
}
setWorkouts(allWorkouts);
};
// After
const {
workouts: allWorkouts,
loading,
refresh
} = useWorkoutHistory({
includeNostr,
filters: includeNostr ? undefined : { source: ['local'] },
realtime: true
});
Workout Detail Screen
// Before
const workoutHistoryService = React.useMemo(() => new WorkoutHistoryService(db), [db]);
const loadWorkout = async () => {
const workoutDetails = await workoutHistoryService.getWorkoutDetails(id);
setWorkout(workoutDetails);
};
// After
const { getWorkoutDetails, publishWorkoutToNostr } = useWorkoutHistory();
const loadWorkout = async () => {
const workoutDetails = await getWorkoutDetails(id);
setWorkout(workoutDetails);
};
Key Benefits of the New API
- Simplified Interface: One service and one hook to learn and use
- Real-time Updates: Built-in support for real-time Nostr updates
- Consistent Filtering: Unified filtering across local and Nostr workouts
- Better Type Safety: Improved TypeScript types and interfaces
- Reduced Boilerplate: Less code needed in components
- Offline Support: Better handling of offline scenarios
- Enhanced Performance: Optimized queries and caching
Migration Timeline
- The legacy services (
EnhancedWorkoutHistoryService
,NostrWorkoutHistoryService
) and hooks (useNostrWorkoutHistory
) are now deprecated - These services and hooks will be removed in a future release
- All components should migrate to the new unified API as soon as possible
Troubleshooting
Common Issues
- Subscription Management
If you encounter issues with Nostr subscriptions:
// Make sure to clean up subscriptions when the component unmounts
useEffect(() => {
// Component setup...
return () => {
// Always clean up subscriptions to prevent memory leaks
if (subId) {
workoutHistoryService.unsubscribeFromNostrWorkouts(subId);
}
};
}, [workoutHistoryService]);
- Filtering Workouts
If filtering isn't working as expected:
// Use proper typing for filter properties
const filters: WorkoutFilters = {
type: ['strength'], // Explicitly typed array of strings
source: ['local', 'nostr'], // Use valid source values
dateRange: {
start: new Date('2025-01-01'),
end: new Date('2025-04-01')
}
};
- Calendar Date Issues
If calendar dates aren't showing workouts:
// Make sure to use the getWorkoutsByDate method
const workoutsForSelectedDate = await getWorkoutsByDate(selectedDate);
// If that returns empty results, you can use a fallback:
if (workoutsForSelectedDate.length === 0) {
const filteredWorkouts = allWorkouts.filter(workout =>
isSameDay(new Date(workout.startTime), selectedDate)
);
setSelectedDateWorkouts(filteredWorkouts);
}
Related Documentation
- History Overview - Overview of the History tab's features and architecture
- Workout Data Models - Details on workout data structures
- Nostr Offline Queue - Information on offline Nostr functionality