🎨 **True Light/Dark Mode** - Implemented proper light mode with inverted color scheme - Smooth theme transitions (0.3s ease) - Light mode colors: white backgrounds, dark text, vibrant accents - System theme detection with proper class application 🌈 **Enhanced Theme System** - 4 color themes work in both light and dark modes - Cyber (cyan/magenta), Ocean (blue/teal), Forest (green/lime), Sunset (orange/purple) - Theme-specific glassmorphism effects - Proper contrast in light mode ✨ **Premium Animations** - Float, glow-pulse, slide-in, scale-in, rotate-in animations - Smooth page transitions - Hover effects with depth (lift, glow, scale) - Micro-interactions on all interactive elements 🎯 **Visual Polish** - Enhanced glassmorphism for light/dark modes - Custom scrollbar with theme colors - Beautiful text selection - Focus indicators for accessibility - Premium utility classes 🔧 **Technical Improvements** - Updated UIStore to properly apply light/dark classes - Added data-theme attribute for CSS targeting - Smooth scroll behavior - Optimized transitions The app is now a visual masterpiece with perfect light/dark mode support!
121 lines
3.6 KiB
TypeScript
121 lines
3.6 KiB
TypeScript
import { apiClient } from '@/services/api/client';
|
|
import { Track, PaginatedResponse, TrackDTO } from '@/types/api';
|
|
import { trackSchema } from '@/schemas/apiSchemas';
|
|
import { uploadService, UploadMetadata } from './uploadService';
|
|
|
|
const env = {
|
|
API_URL: import.meta.env.VITE_API_URL || '',
|
|
};
|
|
|
|
const mapTrackDTO = (dto: TrackDTO): Track => ({
|
|
id: dto.id,
|
|
title: dto.title,
|
|
artist: typeof dto.artist === 'object' ? (dto.artist as any).name : dto.artist,
|
|
album: dto.album || 'Unknown',
|
|
duration: dto.duration_formatted || '0:00',
|
|
durationSec: dto.duration || 0,
|
|
genre: dto.genre || 'None',
|
|
year: dto.year || new Date().getFullYear(),
|
|
coverUrl: dto.cover_art_path ? `${env.API_URL}${dto.cover_art_path}` : undefined,
|
|
cover_art_path: dto.cover_art_path,
|
|
filePath: dto.file_path,
|
|
file_path: dto.file_path,
|
|
file_size: dto.file_size || 0,
|
|
format: dto.format || 'mp3',
|
|
bitrate: dto.bitrate || 0,
|
|
sample_rate: dto.sample_rate || 0,
|
|
play_count: dto.play_count || 0,
|
|
like_count: dto.like_count || 0,
|
|
plays: dto.play_count || 0,
|
|
likes: dto.like_count || 0,
|
|
created_at: dto.created_at,
|
|
updated_at: dto.updated_at,
|
|
creator_id: dto.creator_id,
|
|
is_public: dto.is_public ?? true,
|
|
status: dto.status as any,
|
|
stream_status: (dto.stream_status as any) || 'pending',
|
|
stream_manifest_url: dto.stream_manifest_url,
|
|
} as Track);
|
|
|
|
export const trackService = {
|
|
list: async (params?: {
|
|
user_id?: string;
|
|
artist?: string;
|
|
genre?: string;
|
|
page?: number;
|
|
limit?: number;
|
|
sort_by?: string;
|
|
order?: 'asc' | 'desc';
|
|
query?: string;
|
|
search?: string;
|
|
}) => {
|
|
const response = await apiClient.get<PaginatedResponse<TrackDTO>>('/tracks', {
|
|
params: {
|
|
...params,
|
|
query: params?.query || params?.search
|
|
}
|
|
});
|
|
const data = response.data;
|
|
const items = data.items || [];
|
|
return {
|
|
tracks: items.map(mapTrackDTO),
|
|
pagination: {
|
|
total: data.total,
|
|
page: data.page,
|
|
limit: data.limit,
|
|
total_pages: data.total_pages,
|
|
has_next: data.has_next,
|
|
has_prev: data.has_prev
|
|
}
|
|
};
|
|
},
|
|
|
|
search: async (query: string) => {
|
|
const response = await apiClient.get<TrackDTO[]>('/tracks/search', { params: { query } });
|
|
return { tracks: response.data.map(mapTrackDTO) };
|
|
},
|
|
|
|
get: async (id: string) => {
|
|
const response = await apiClient.get<TrackDTO>(`/tracks/${id}`, {
|
|
validateSchema: trackSchema
|
|
} as any);
|
|
return { track: mapTrackDTO(response.data) };
|
|
},
|
|
|
|
update: async (id: string, data: Partial<Track>) => {
|
|
const payload = {
|
|
title: data.title,
|
|
artist: data.artist,
|
|
album: data.album,
|
|
genre: data.genre,
|
|
is_public: data.is_public
|
|
};
|
|
const response = await apiClient.put<TrackDTO>(`/tracks/${id}`, payload, {
|
|
validateSchema: trackSchema
|
|
} as any);
|
|
return { track: mapTrackDTO(response.data) };
|
|
},
|
|
|
|
delete: async (id: string) => apiClient.delete(`/tracks/${id}`),
|
|
|
|
like: async (id: string) => apiClient.post(`/tracks/${id}/like`),
|
|
|
|
unlike: async (id: string) => apiClient.delete(`/tracks/${id}/like`),
|
|
|
|
recordPlay: async (id: string) => apiClient.post(`/tracks/${id}/play`),
|
|
|
|
download: async (id: string) => {
|
|
const response = await apiClient.get(`/tracks/${id}/download`, {
|
|
responseType: 'blob'
|
|
});
|
|
return response.data;
|
|
},
|
|
|
|
upload: async (file: File, metadata: UploadMetadata, onProgress?: (progress: number) => void) => {
|
|
return await uploadService.uploadTrack(file, metadata, onProgress);
|
|
},
|
|
|
|
getStatus: async (trackId: string) => {
|
|
return await uploadService.getTrackStatus(trackId);
|
|
}
|
|
};
|