diff --git a/VEZA_COMPLETE_MVP_TODOLIST.json b/VEZA_COMPLETE_MVP_TODOLIST.json index 3103dbd52..e0462e262 100644 --- a/VEZA_COMPLETE_MVP_TODOLIST.json +++ b/VEZA_COMPLETE_MVP_TODOLIST.json @@ -9394,7 +9394,7 @@ "description": "Add types for all query parameters", "owner": "frontend", "estimated_hours": 3, - "status": "todo", + "status": "completed", "files_involved": [], "implementation_steps": [ { @@ -9415,7 +9415,8 @@ "Unit tests", "Integration tests" ], - "notes": "" + "notes": "Created comprehensive query parameter type definitions. Added types for PaginationQueryParams, SortQueryParams, SearchQueryParams, TrackSearchQueryParams, PlaylistSearchQueryParams, UserSearchQueryParams, LibraryQueryParams, MarketplaceQueryParams, TrackListQueryParams, PlaylistListQueryParams, ConversationListQueryParams, MessageListQueryParams, NotificationQueryParams, AuditLogQueryParams, AnalyticsQueryParams, FilterQueryParams, DateRangeQueryParams, ResetPasswordQueryParams, VerifyEmailQueryParams, OAuthCallbackQueryParams, ShareQueryParams, EmbedQueryParams, AdminQueryParams, SettingsQueryParams. Added helper functions: parseQueryParams, buildQueryString, convertQueryParams, parsePaginationParams, parseBooleanParam, parseNumberParam.", + "completed_at": "2025-12-25T14:46:55.872621Z" }, { "id": "FE-TYPE-010", diff --git a/apps/web/src/types/queryParams.ts b/apps/web/src/types/queryParams.ts new file mode 100644 index 000000000..1abc05249 --- /dev/null +++ b/apps/web/src/types/queryParams.ts @@ -0,0 +1,393 @@ +/** + * Query Parameter Types + * FE-TYPE-009: Add type definitions for all query parameters + * + * Comprehensive type definitions for all query parameters used throughout + * the application, ensuring type safety for URL query string handling. + */ + +/** + * Base query parameter types + */ +export type SortOrder = 'asc' | 'desc'; +export type SortDirection = 'asc' | 'desc'; + +/** + * Pagination Query Parameters + * Common query params: ?page=1&limit=20 + */ +export interface PaginationQueryParams { + page?: string; // Page number (as string from URL) + limit?: string; // Items per page (as string from URL) + cursor?: string; // Cursor for cursor-based pagination + offset?: string; // Offset for offset-based pagination +} + +/** + * Sort Query Parameters + * Common query params: ?sort_by=name&sort_order=asc + */ +export interface SortQueryParams { + sort_by?: string; // Field to sort by + sort_order?: SortOrder; // Sort direction + sort?: string; // Alternative sort field name + order?: SortOrder; // Alternative order field name +} + +/** + * Search Query Parameters + * Route: /search?q=query&type=tracks + */ +export interface SearchQueryParams { + q?: string; // Search query + query?: string; // Alternative query parameter name + type?: 'all' | 'track' | 'tracks' | 'playlist' | 'playlists' | 'user' | 'users'; + page?: string; + limit?: string; +} + +/** + * Track Search Query Parameters + * Advanced search with filters + */ +export interface TrackSearchQueryParams { + q?: string; // Search query + tags?: string; // Comma-separated tags + tag_mode?: 'all' | 'any'; // Tag matching mode + min_duration?: string; // Minimum duration in seconds + max_duration?: string; // Maximum duration in seconds + min_bpm?: string; // Minimum BPM + max_bpm?: string; // Maximum BPM + genre?: string; // Genre filter + format?: string; // Audio format (mp3, flac, etc.) + min_date?: string; // Minimum date (ISO8601) + max_date?: string; // Maximum date (ISO8601) + page?: string; + limit?: string; + sort_by?: string; + sort_order?: SortOrder; +} + +/** + * Playlist Search Query Parameters + */ +export interface PlaylistSearchQueryParams { + q?: string; // Search query + owner?: string; // Owner username or ID + is_public?: 'true' | 'false'; // Public/private filter + min_tracks?: string; // Minimum number of tracks + max_tracks?: string; // Maximum number of tracks + page?: string; + limit?: string; + sort_by?: 'name' | 'created_at' | 'updated_at' | 'track_count' | 'follower_count'; + sort_order?: SortOrder; +} + +/** + * User Search Query Parameters + */ +export interface UserSearchQueryParams { + q?: string; // Search query + role?: string; // User role filter + is_verified?: 'true' | 'false'; // Verified users only + is_active?: 'true' | 'false'; // Active users only + page?: string; + limit?: string; + sort_by?: 'username' | 'created_at' | 'last_login'; + sort_order?: SortOrder; +} + +/** + * Library Query Parameters + * Route: /library?filter=tracks&sort=recent + */ +export interface LibraryQueryParams { + filter?: 'all' | 'tracks' | 'playlists' | 'favorites' | 'recent'; + sort?: 'recent' | 'name' | 'artist' | 'date' | 'title'; + order?: SortOrder; + page?: string; + limit?: string; +} + +/** + * Marketplace Query Parameters + * Route: /marketplace?category=music&price_min=10&price_max=100 + */ +export interface MarketplaceQueryParams { + category?: string; // Product category + price_min?: string; // Minimum price + price_max?: string; // Maximum price + currency?: string; // Currency code + sort?: 'price' | 'date' | 'popularity' | 'rating'; + order?: SortOrder; + page?: string; + limit?: string; +} + +/** + * Track List Query Parameters + */ +export interface TrackListQueryParams { + artist?: string; // Filter by artist + genre?: string; // Filter by genre + year?: string; // Filter by year + is_public?: 'true' | 'false'; // Public/private filter + status?: 'uploading' | 'processing' | 'completed' | 'failed'; + page?: string; + limit?: string; + sort_by?: 'title' | 'artist' | 'created_at' | 'play_count' | 'like_count'; + sort_order?: SortOrder; +} + +/** + * Playlist List Query Parameters + */ +export interface PlaylistListQueryParams { + user_id?: string; // Filter by user + is_public?: 'true' | 'false'; // Public/private filter + min_tracks?: string; // Minimum number of tracks + max_tracks?: string; // Maximum number of tracks + page?: string; + limit?: string; + sort_by?: 'name' | 'created_at' | 'updated_at' | 'track_count'; + sort_order?: SortOrder; +} + +/** + * Conversation List Query Parameters + */ +export interface ConversationListQueryParams { + type?: 'direct' | 'group'; // Conversation type + unread_only?: 'true' | 'false'; // Show only unread + page?: string; + limit?: string; + sort_by?: 'name' | 'updated_at' | 'created_at'; + sort_order?: SortOrder; +} + +/** + * Message List Query Parameters + */ +export interface MessageListQueryParams { + conversation_id?: string; // Filter by conversation + sender_id?: string; // Filter by sender + message_type?: 'text' | 'image' | 'audio' | 'file'; + page?: string; + limit?: string; + sort_by?: 'created_at' | 'updated_at'; + sort_order?: SortOrder; +} + +/** + * Notification Query Parameters + */ +export interface NotificationQueryParams { + type?: string; // Notification type filter + read?: 'true' | 'false'; // Read/unread filter + page?: string; + limit?: string; + sort_by?: 'created_at'; + sort_order?: SortOrder; +} + +/** + * Audit Log Query Parameters + */ +export interface AuditLogQueryParams { + user_id?: string; // Filter by user + action?: string; // Filter by action + resource?: string; // Filter by resource type + start_date?: string; // Start date (ISO8601) + end_date?: string; // End date (ISO8601) + page?: string; + limit?: string; + sort_by?: 'timestamp' | 'action' | 'resource'; + sort_order?: SortOrder; +} + +/** + * Analytics Query Parameters + */ +export interface AnalyticsQueryParams { + start_date?: string; // Start date (ISO8601) + end_date?: string; // End date (ISO8601) + metric?: string; // Metric to analyze + group_by?: string; // Grouping field + interval?: 'hour' | 'day' | 'week' | 'month'; // Time interval +} + +/** + * Filter Query Parameters (generic) + */ +export interface FilterQueryParams { + filter?: string; // Filter value + filters?: string; // Multiple filters (comma-separated) + exclude?: string; // Exclude filter + include?: string; // Include filter +} + +/** + * Date Range Query Parameters + */ +export interface DateRangeQueryParams { + start_date?: string; // Start date (ISO8601) + end_date?: string; // End date (ISO8601) + date_from?: string; // Alternative start date + date_to?: string; // Alternative end date +} + +/** + * Reset Password Query Parameters + * Route: /reset-password?token=... + */ +export interface ResetPasswordQueryParams { + token?: string; // Reset token +} + +/** + * Verify Email Query Parameters + * Route: /verify-email?token=... + */ +export interface VerifyEmailQueryParams { + token?: string; // Verification token +} + +/** + * OAuth Callback Query Parameters + * Route: /auth/callback?code=...&state=... + */ +export interface OAuthCallbackQueryParams { + code?: string; // OAuth authorization code + state?: string; // OAuth state parameter + error?: string; // OAuth error code + error_description?: string; // OAuth error description +} + +/** + * Share Query Parameters + * Route: /share?token=...&type=... + */ +export interface ShareQueryParams { + token?: string; // Share token + type?: 'track' | 'playlist' | 'user'; + id?: string; // Resource ID +} + +/** + * Embed Query Parameters + * Route: /embed?track_id=...&autoplay=true + */ +export interface EmbedQueryParams { + track_id?: string; // Track ID + playlist_id?: string; // Playlist ID + autoplay?: 'true' | 'false'; // Autoplay option + controls?: 'true' | 'false'; // Show controls + theme?: 'light' | 'dark'; // Theme +} + +/** + * Admin Query Parameters + */ +export interface AdminQueryParams { + section?: string; // Admin section + action?: string; // Admin action + user_id?: string; // User ID filter + role_id?: string; // Role ID filter + page?: string; + limit?: string; +} + +/** + * Settings Query Parameters + */ +export interface SettingsQueryParams { + tab?: 'profile' | 'security' | 'notifications' | 'privacy' | 'sessions' | 'api'; + section?: string; // Settings section +} + +/** + * Helper function to parse query params from URLSearchParams + */ +export function parseQueryParams>( + searchParams: URLSearchParams, +): Partial { + const params: Partial = {}; + + for (const [key, value] of searchParams.entries()) { + params[key as keyof T] = value as T[keyof T]; + } + + return params; +} + +/** + * Helper function to build query string from params + */ +export function buildQueryString>( + params: T, +): string { + const searchParams = new URLSearchParams(); + + for (const [key, value] of Object.entries(params)) { + if (value !== null && value !== undefined && value !== '') { + searchParams.set(key, String(value)); + } + } + + return searchParams.toString(); +} + +/** + * Helper function to convert string query params to typed values + */ +export function convertQueryParams>( + params: Partial, + converters: { + [K in keyof T]?: (value: string) => T[K]; + }, +): Partial { + const converted: Partial = {}; + + for (const [key, value] of Object.entries(params)) { + if (value !== undefined) { + const converter = converters[key as keyof T]; + if (converter) { + converted[key as keyof T] = converter(value); + } else { + converted[key as keyof T] = value as T[keyof T]; + } + } + } + + return converted; +} + +/** + * Helper to parse pagination params + */ +export function parsePaginationParams( + params: PaginationQueryParams, +): { page: number; limit: number } { + return { + page: params.page ? parseInt(params.page, 10) : 1, + limit: params.limit ? parseInt(params.limit, 10) : 20, + }; +} + +/** + * Helper to parse boolean query params + */ +export function parseBooleanParam(value: string | undefined): boolean | undefined { + if (value === undefined) return undefined; + return value === 'true' || value === '1'; +} + +/** + * Helper to parse number query params + */ +export function parseNumberParam(value: string | undefined): number | undefined { + if (value === undefined) return undefined; + const parsed = parseInt(value, 10); + return isNaN(parsed) ? undefined : parsed; +} +