From 96a29f25dc6f71a896e379499c5859627314ec12 Mon Sep 17 00:00:00 2001 From: angel Date: Fri, 20 Jun 2025 02:33:58 +0000 Subject: [PATCH 1/5] feat: remove unused files and dependencies, streamline project structure --- .github/workflows/jest.yml | 34 ------- MIGRATION_COMPLETE.md | 145 ---------------------------- app/components/FullScreenPlayer.tsx | 15 ++- app/layout.tsx | 4 - jest.config.js | 7 -- package.json | 6 +- pnpm-lock.yaml | 68 ------------- theme.md | 121 ----------------------- 8 files changed, 7 insertions(+), 393 deletions(-) delete mode 100644 .github/workflows/jest.yml delete mode 100644 MIGRATION_COMPLETE.md delete mode 100644 jest.config.js delete mode 100644 theme.md diff --git a/.github/workflows/jest.yml b/.github/workflows/jest.yml deleted file mode 100644 index 09682dd..0000000 --- a/.github/workflows/jest.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: Run Jest Tests - -on: - push: - branches: - - main - paths: - - 'apps/data/albums.ts' - - 'apps/data/artists.ts' - pull_request: - branches: - - main - paths: - - 'apps/data/albums.ts' - - 'apps/data/artists.ts' - -jobs: - test: - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@v2 - - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - node-version: '22' - - - name: Install dependencies - run: npm install - - - name: Run Jest tests - run: npm test \ No newline at end of file diff --git a/MIGRATION_COMPLETE.md b/MIGRATION_COMPLETE.md deleted file mode 100644 index 4ce2b2e..0000000 --- a/MIGRATION_COMPLETE.md +++ /dev/null @@ -1,145 +0,0 @@ -# Migration Summary: Firebase โ†’ Navidrome/Subsonic - -## โœ… Completed Migration Tasks - -### ๐Ÿ—‘๏ธ Removed Legacy Systems -- [x] **Firebase Dependencies**: Removed firebase, react-firebase-hooks packages -- [x] **Static Data Files**: Moved `app/data/` (albums.ts, artists.ts, playlists.ts) to backup -- [x] **Firebase Config**: Moved `app/firebase/` directory to backup -- [x] **Authentication System**: Removed Firebase Auth integration -- [x] **Database Connections**: Removed Firestore database calls - -### ๐Ÿš€ Implemented Navidrome Integration -- [x] **Navidrome API Client** (`lib/navidrome.ts`) - - Subsonic API authentication with token-based security - - All major endpoints: ping, getArtists, getAlbums, getAlbum, search3, etc. - - Stream URL generation for audio playback - - Cover art URL generation with size parameters - - Star/unstar functionality for favorites - - Scrobbling support for play tracking - -- [x] **React Context Provider** (`app/components/NavidromeContext.tsx`) - - Global state management for music library data - - Loading states for UI feedback - - Error handling and connection testing - - Data fetching with automatic refresh - - CRUD operations for playlists - -### ๐ŸŽต Updated Audio System -- [x] **AudioPlayerContext** - Completely rewritten for Navidrome - - Real audio streaming instead of static file URLs - - Queue management with Navidrome song objects - - Automatic scrobbling when tracks play - - Track conversion from Navidrome Song to playable Track format - -- [x] **AudioPlayer Component** - - Updated to handle Navidrome streaming URLs - - Dynamic cover art from Navidrome getCoverArt API - - Proper track metadata display (artist, album, duration) - -### ๐ŸŽจ Updated UI Components -- [x] **AlbumArtwork Component** - - Uses Navidrome Album interface - - Dynamic cover art with getCoverArt API - - Context menu integration with Navidrome playlists - - Proper album metadata display (year, genre, song count) - -- [x] **ArtistIcon Component** - - Uses Navidrome Artist interface - - Artist cover art support - - Album count display - - Star/unstar functionality in context menu - -### ๐Ÿ“„ Updated Pages -- [x] **Main Page** (`app/page.tsx`) - - Uses NavidromeContext for album data - - Loading states with skeleton UI - - Error handling for connection issues - - Recent and newest album sections - -- [x] **Album Detail Page** (`app/album/[id]/page.tsx`) - - Fetches album and songs from Navidrome - - Real-time song playback with streaming - - Star/unstar album functionality - - Proper track listing with metadata - -- [x] **Artist Page** (`app/artist/[artist]/page.tsx`) - - Artist details from Navidrome API - - Dynamic album grid for artist - - Star/unstar artist functionality - - Modern gradient header design - -- [x] **Library Pages** - - `app/library/albums/page.tsx` - Shows all albums in grid layout - - `app/library/artists/page.tsx` - Shows all artists in grid layout - - `app/library/playlists/page.tsx` - Playlist management with CRUD operations - -### ๐Ÿ”ง Configuration & Documentation -- [x] **Environment Configuration** - - `.env.example` with Navidrome connection settings - - Removed Firebase environment variables from package.json - -- [x] **Documentation** - - `NAVIDROME_MIGRATION.md` - Detailed migration guide - - Updated `README.md` with new setup instructions - - Feature documentation and troubleshooting - -- [x] **Type Safety** - - TypeScript interfaces matching Subsonic API responses - - Proper error handling throughout the application - - Type-safe component props and context values - -### ๐Ÿงช Testing -- [x] **Test Suite** (`__tests__/navidrome.test.ts`) - - API client functionality tests - - TypeScript interface validation - - URL generation testing - - Configuration validation - -## ๐ŸŽฏ Key Benefits Achieved - -### **Real Music Streaming** -- Replaced static MP3 URLs with dynamic Navidrome streaming -- Support for multiple audio formats and bitrates -- Proper audio metadata from music files - -### **Dynamic Library** -- No more manual JSON file management -- Auto-discovery of new music added to Navidrome -- Real-time library updates - -### **Enhanced Features** -- Scrobbling for play tracking -- Star/favorite functionality -- Playlist management (create, edit, delete) -- Search across entire music library -- High-quality album artwork - -### **Better Architecture** -- Removed Firebase dependency completely -- Self-hosted music solution -- Standards-based Subsonic API integration -- Type-safe development with proper interfaces - -## ๐Ÿ”„ Migration Path - -1. **Backup**: Old Firebase and static data moved to `-old` directories -2. **Dependencies**: Firebase packages removed, crypto built-in used -3. **Environment**: New `.env.local` needed with Navidrome credentials -4. **Data Flow**: `Static JSON โ†’ Firebase โ†’ Navidrome API` -5. **Authentication**: `Firebase Auth โ†’ Navidrome Server Authentication` -6. **Streaming**: `Static Files โ†’ Navidrome Stream API` - -## ๐Ÿšฆ Ready for Production - -The application is now fully migrated and ready for use with any Navidrome server. All core functionality has been preserved and enhanced: - -- โœ… Browse music library (albums, artists, songs) -- โœ… Audio playback with queue management -- โœ… Search functionality -- โœ… Playlist management -- โœ… Favorites/starring -- โœ… Responsive design -- โœ… Error handling and loading states - -**Next Steps**: Set up Navidrome server and configure connection in `.env.local` diff --git a/app/components/FullScreenPlayer.tsx b/app/components/FullScreenPlayer.tsx index d2c4c10..b9bbac8 100644 --- a/app/components/FullScreenPlayer.tsx +++ b/app/components/FullScreenPlayer.tsx @@ -5,6 +5,7 @@ import Image from 'next/image'; import { useAudioPlayer } from '@/app/components/AudioPlayerContext'; import { Progress } from '@/components/ui/progress'; import { lrcLibClient } from '@/lib/lrclib'; +import Link from 'next/link'; import { FaPlay, FaPause, @@ -333,19 +334,15 @@ export const FullScreenPlayer: React.FC = ({ isOpen, onCl

{currentTrack.name}

-

{currentTrack.artist}

+ + {currentTrack.artist} + {/* Progress */}
-
-
+
+
{formatTime(currentTime)} diff --git a/app/layout.tsx b/app/layout.tsx index 07e831b..6c27334 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,6 +1,4 @@ -import { SpeedInsights } from "@vercel/speed-insights/next"; import React from 'react'; -import { Analytics } from "@vercel/analytics/react"; import localFont from "next/font/local"; import "./globals.css"; import { AudioPlayerProvider } from "./components/AudioPlayerContext"; @@ -81,8 +79,6 @@ export default function Layout({ children }: LayoutProps) { - - {children} diff --git a/jest.config.js b/jest.config.js deleted file mode 100644 index a714176..0000000 --- a/jest.config.js +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = { - preset: 'ts-jest', - testEnvironment: 'node', - moduleNameMapper: { - '^@/(.*)$': '/$1', - }, -}; \ No newline at end of file diff --git a/package.json b/package.json index a631d8b..9cdc9d8 100644 --- a/package.json +++ b/package.json @@ -6,8 +6,7 @@ "dev": "next dev -p 40625", "build": "next build", "start": "next start -p 40625", - "lint": "next lint", - "test": "jest" + "lint": "next lint" }, "dependencies": { "@hookform/resolvers": "^3.9.1", @@ -25,8 +24,6 @@ "@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-tabs": "^1.1.1", "@radix-ui/react-toast": "^1.2.4", - "@vercel/analytics": "^1.4.1", - "@vercel/speed-insights": "^1.1.0", "axios": "^1.7.7", "chalk": "^5.3.0", "class-variance-authority": "^0.7.0", @@ -49,7 +46,6 @@ "@types/react-dom": "^19.0.2", "eslint": "^9.17", "eslint-config-next": "15.1.4", - "jest": "^29.7.0", "postcss": "^8", "tailwindcss": "^3.4.15", "ts-jest": "^29.2.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7dc3d9c..68dcc61 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -53,12 +53,6 @@ importers: '@radix-ui/react-toast': specifier: ^1.2.4 version: 1.2.4(@types/react-dom@19.0.2(@types/react@19.0.4))(@types/react@19.0.4)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@vercel/analytics': - specifier: ^1.4.1 - version: 1.4.1(next@15.1.4(@babel/core@7.26.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0) - '@vercel/speed-insights': - specifier: ^1.1.0 - version: 1.1.0(next@15.1.4(@babel/core@7.26.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0) axios: specifier: ^1.7.7 version: 1.7.9 @@ -120,9 +114,6 @@ importers: eslint-config-next: specifier: 15.1.4 version: 15.1.4(eslint@9.17.0(jiti@1.21.7))(typescript@5.7.3) - jest: - specifier: ^29.7.0 - version: 29.7.0(@types/node@22.10.5) postcss: specifier: ^8 version: 8.4.49 @@ -1212,55 +1203,6 @@ packages: resolution: {integrity: sha512-fzmjU8CHK853V/avYZAvuVut3ZTfwN5YtMaoi+X9Y9MA9keaWNHC3zEQ9zvyX/7Hj+5JkNyK1l7TOR2hevHB6Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@vercel/analytics@1.4.1': - resolution: {integrity: sha512-ekpL4ReX2TH3LnrRZTUKjHHNpNy9S1I7QmS+g/RQXoSUQ8ienzosuX7T9djZ/s8zPhBx1mpHP/Rw5875N+zQIQ==} - peerDependencies: - '@remix-run/react': ^2 - '@sveltejs/kit': ^1 || ^2 - next: '>= 13' - react: ^18 || ^19 || ^19.0.0-rc - svelte: '>= 4' - vue: ^3 - vue-router: ^4 - peerDependenciesMeta: - '@remix-run/react': - optional: true - '@sveltejs/kit': - optional: true - next: - optional: true - react: - optional: true - svelte: - optional: true - vue: - optional: true - vue-router: - optional: true - - '@vercel/speed-insights@1.1.0': - resolution: {integrity: sha512-rAXxuhhO4mlRGC9noa5F7HLMtGg8YF1zAN6Pjd1Ny4pII4cerhtwSG4vympbCl+pWkH7nBS9kVXRD4FAn54dlg==} - peerDependencies: - '@sveltejs/kit': ^1 || ^2 - next: '>= 13' - react: ^18 || ^19 || ^19.0.0-rc - svelte: '>= 4' - vue: ^3 - vue-router: ^4 - peerDependenciesMeta: - '@sveltejs/kit': - optional: true - next: - optional: true - react: - optional: true - svelte: - optional: true - vue: - optional: true - vue-router: - optional: true - acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -4532,16 +4474,6 @@ snapshots: '@typescript-eslint/types': 8.19.1 eslint-visitor-keys: 4.2.0 - '@vercel/analytics@1.4.1(next@15.1.4(@babel/core@7.26.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)': - optionalDependencies: - next: 15.1.4(@babel/core@7.26.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - react: 19.0.0 - - '@vercel/speed-insights@1.1.0(next@15.1.4(@babel/core@7.26.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)': - optionalDependencies: - next: 15.1.4(@babel/core@7.26.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - react: 19.0.0 - acorn-jsx@5.3.2(acorn@8.14.0): dependencies: acorn: 8.14.0 diff --git a/theme.md b/theme.md deleted file mode 100644 index d20790e..0000000 --- a/theme.md +++ /dev/null @@ -1,121 +0,0 @@ -# Violet - -```css -@layer base { - :root { - --background: 0 0% 100%; - --foreground: 224 71.4% 4.1%; - --card: 0 0% 100%; - --card-foreground: 224 71.4% 4.1%; - --popover: 0 0% 100%; - --popover-foreground: 224 71.4% 4.1%; - --primary: 262.1 83.3% 57.8%; - --primary-foreground: 210 20% 98%; - --secondary: 220 14.3% 95.9%; - --secondary-foreground: 220.9 39.3% 11%; - --muted: 220 14.3% 95.9%; - --muted-foreground: 220 8.9% 46.1%; - --accent: 220 14.3% 95.9%; - --accent-foreground: 220.9 39.3% 11%; - --destructive: 0 84.2% 60.2%; - --destructive-foreground: 210 20% 98%; - --border: 220 13% 91%; - --input: 220 13% 91%; - --ring: 262.1 83.3% 57.8%; - --radius: 0.65rem; - --chart-1: 12 76% 61%; - --chart-2: 173 58% 39%; - --chart-3: 197 37% 24%; - --chart-4: 43 74% 66%; - --chart-5: 27 87% 67%; - } - - .dark { - --background: 224 71.4% 4.1%; - --foreground: 210 20% 98%; - --card: 224 71.4% 4.1%; - --card-foreground: 210 20% 98%; - --popover: 224 71.4% 4.1%; - --popover-foreground: 210 20% 98%; - --primary: 263.4 70% 50.4%; - --primary-foreground: 210 20% 98%; - --secondary: 215 27.9% 16.9%; - --secondary-foreground: 210 20% 98%; - --muted: 215 27.9% 16.9%; - --muted-foreground: 217.9 10.6% 64.9%; - --accent: 215 27.9% 16.9%; - --accent-foreground: 210 20% 98%; - --destructive: 0 62.8% 30.6%; - --destructive-foreground: 210 20% 98%; - --border: 215 27.9% 16.9%; - --input: 215 27.9% 16.9%; - --ring: 263.4 70% 50.4%; - --chart-1: 220 70% 50%; - --chart-2: 160 60% 45%; - --chart-3: 30 80% 55%; - --chart-4: 280 65% 60%; - --chart-5: 340 75% 55%; - } -} -``` - -# Blue - -```css@layer base { - :root { - --background: 0 0% 100%; - --foreground: 240 10% 3.9%; - --card: 0 0% 100%; - --card-foreground: 240 10% 3.9%; - --popover: 0 0% 100%; - --popover-foreground: 240 10% 3.9%; - --primary: 221.2 83.2% 53.3%; - --primary-foreground: 0 0% 98%; - --secondary: 240 4.8% 95.9%; - --secondary-foreground: 240 5.9% 10%; - --muted: 240 4.8% 95.9%; - --muted-foreground: 240 3.8% 46.1%; - --accent: 240 4.8% 95.9%; - --accent-foreground: 240 5.9% 10%; - --destructive: 0 84.2% 60.2%; - --destructive-foreground: 0 0% 98%; - --border: 240 5.9% 90%; - --input: 240 5.9% 90%; - --ring: 240 5.9% 10%; - --radius: 0.5rem; - --chart-1: 12 76% 61%; - --chart-2: 173 58% 39%; - --chart-3: 197 37% 24%; - --chart-4: 43 74% 66%; - --chart-5: 27 87% 67%; - } - - .dark { - --background: 240 10% 3.9%; - --foreground: 0 0% 98%; - --card: 240 10% 3.9%; - --card-foreground: 0 0% 98%; - --popover: 240 10% 3.9%; - --popover-foreground: 0 0% 98%; - --primary: 217.2 91.2% 59.8%; - --primary-foreground: 222.2 47.4% 11.2%; - --secondary: 217.2 32.6% 17.5%; - --secondary-foreground: 0 0% 98%; - --muted: 217.2 32.6% 17.5%; - --muted-foreground: 215 20.2% 65.1%; - --accent: 217.2 32.6% 17.5%; - --accent-foreground: 0 0% 98%; - --destructive: 0 62.8% 30.6%; - --destructive-foreground: 0 0% 98%; - --border: 217.2 32.6% 17.5%; - --input: 217.2 32.6% 17.5%; - --ring: 224.3 76.3% 48%; - --chart-1: 220 70% 50%; - --chart-2: 160 60% 45%; - --chart-3: 30 80% 55%; - --chart-4: 280 65% 60%; - --chart-5: 340 75% 55%; - --hover: 240 27% 11%; - } -} -``` \ No newline at end of file From 6d6b1baa6206b31cc6173c044cdca7f051cd0cab Mon Sep 17 00:00:00 2001 From: angel Date: Fri, 20 Jun 2025 02:47:12 +0000 Subject: [PATCH 2/5] feat: implement volume persistence and auto-play control in AudioPlayer and AudioPlayerContext --- app/components/AudioPlayer.tsx | 26 +++++++++++++++++++++++--- app/components/AudioPlayerContext.tsx | 25 +++++++++++++++---------- app/components/FullScreenPlayer.tsx | 2 +- 3 files changed, 39 insertions(+), 14 deletions(-) diff --git a/app/components/AudioPlayer.tsx b/app/components/AudioPlayer.tsx index bd0dd56..00b5bb3 100644 --- a/app/components/AudioPlayer.tsx +++ b/app/components/AudioPlayer.tsx @@ -31,6 +31,19 @@ export const AudioPlayer: React.FC = () => { useEffect(() => { setIsClient(true); + // Load saved volume + const savedVolume = localStorage.getItem('navidrome-volume'); + if (savedVolume) { + try { + const volumeValue = parseFloat(savedVolume); + if (volumeValue >= 0 && volumeValue <= 1) { + setVolume(volumeValue); + } + } catch (error) { + console.error('Failed to parse saved volume:', error); + } + } + // Clean up old localStorage entries with track IDs const keysToRemove: string[] = []; for (let i = 0; i < localStorage.length; i++) { @@ -42,6 +55,16 @@ export const AudioPlayer: React.FC = () => { keysToRemove.forEach(key => localStorage.removeItem(key)); }, []); + // Apply volume to audio element when volume changes + useEffect(() => { + const audioCurrent = audioRef.current; + if (audioCurrent) { + audioCurrent.volume = volume; + } + // Save volume to localStorage + localStorage.setItem('navidrome-volume', volume.toString()); + }, [volume]); + // Save position when component unmounts or track changes useEffect(() => { const audioCurrent = audioRef.current; @@ -248,9 +271,6 @@ export const AudioPlayer: React.FC = () => { const handleVolumeChange = (e: React.ChangeEvent) => { const newVolume = parseFloat(e.target.value); setVolume(newVolume); - if (audioCurrent) { - audioCurrent.volume = newVolume; - } }; function formatTime(seconds: number): string { diff --git a/app/components/AudioPlayerContext.tsx b/app/components/AudioPlayerContext.tsx index 51c8197..9996465 100644 --- a/app/components/AudioPlayerContext.tsx +++ b/app/components/AudioPlayerContext.tsx @@ -69,7 +69,10 @@ export const AudioPlayerProvider: React.FC<{ children: React.ReactNode }> = ({ c const savedCurrentTrack = localStorage.getItem('navidrome-currentTrack'); if (savedCurrentTrack) { try { - setCurrentTrack(JSON.parse(savedCurrentTrack)); + const track = JSON.parse(savedCurrentTrack); + // Clear autoPlay flag when loading from localStorage to prevent auto-play on refresh + track.autoPlay = false; + setCurrentTrack(track); } catch (error) { console.error('Failed to parse saved current track:', error); } @@ -78,7 +81,9 @@ export const AudioPlayerProvider: React.FC<{ children: React.ReactNode }> = ({ c useEffect(() => { if (currentTrack) { - localStorage.setItem('navidrome-currentTrack', JSON.stringify(currentTrack)); + // Remove autoPlay flag when saving to localStorage + const { autoPlay, ...trackToSave } = currentTrack; + localStorage.setItem('navidrome-currentTrack', JSON.stringify(trackToSave)); } else { localStorage.removeItem('navidrome-currentTrack'); } @@ -282,11 +287,11 @@ export const AudioPlayerProvider: React.FC<{ children: React.ReactNode }> = ({ c } // Play the first shuffled track and set the rest as queue - playTrack(shuffledTracks[0]); + playTrack(shuffledTracks[0], true); // Enable autoplay setQueue(shuffledTracks.slice(1)); } else { // Normal order: play first track and set the rest as queue - playTrack(tracks[0]); + playTrack(tracks[0], true); // Enable autoplay setQueue(tracks.slice(1)); } } @@ -332,11 +337,11 @@ export const AudioPlayerProvider: React.FC<{ children: React.ReactNode }> = ({ c } setQueue(remainingTracks); - playTrack(tracks[startingIndex]); + playTrack(tracks[startingIndex], true); // Enable autoplay } else { // Normal order: set the remaining tracks after the starting track as queue setQueue(tracks.slice(startingIndex + 1)); - playTrack(tracks[startingIndex]); + playTrack(tracks[startingIndex], true); // Enable autoplay } toast({ @@ -360,8 +365,8 @@ export const AudioPlayerProvider: React.FC<{ children: React.ReactNode }> = ({ c const targetTrack = queue[index]; // Remove all tracks before the target track (including the target track) setQueue((prevQueue) => prevQueue.slice(index + 1)); - // Play the target track - playTrack(targetTrack); + // Play the target track with autoplay enabled + playTrack(targetTrack, true); } }, [queue, playTrack]); @@ -445,11 +450,11 @@ export const AudioPlayerProvider: React.FC<{ children: React.ReactNode }> = ({ c } // Play the first shuffled track and set the rest as queue - playTrack(shuffledTracks[0]); + playTrack(shuffledTracks[0], true); // Enable autoplay setQueue(shuffledTracks.slice(1)); } else { // Normal order: play first track and set the rest as queue - playTrack(allTracks[0]); + playTrack(allTracks[0], true); // Enable autoplay setQueue(allTracks.slice(1)); } } diff --git a/app/components/FullScreenPlayer.tsx b/app/components/FullScreenPlayer.tsx index b9bbac8..99f205d 100644 --- a/app/components/FullScreenPlayer.tsx +++ b/app/components/FullScreenPlayer.tsx @@ -334,7 +334,7 @@ export const FullScreenPlayer: React.FC = ({ isOpen, onCl

{currentTrack.name}

- + {currentTrack.artist}
From e4b239e230502a33fc85f2b19b0f7cf9565c7531 Mon Sep 17 00:00:00 2001 From: angel Date: Fri, 20 Jun 2025 03:07:06 +0000 Subject: [PATCH 3/5] feat: integrate PostHog for analytics tracking and add PostHogProvider component --- .env.example | 3 +- app/components/AudioPlayer.tsx | 5 ++- app/components/PostHogProvider.tsx | 50 ++++++++++++++++++++++++++++++ app/layout.tsx | 25 ++++++++------- lib/posthog.ts | 11 +++++++ next.config.mjs | 20 +++++++++++- package.json | 2 ++ pnpm-lock.yaml | 50 ++++++++++++++++++++++++++++++ 8 files changed, 152 insertions(+), 14 deletions(-) create mode 100644 app/components/PostHogProvider.tsx create mode 100644 lib/posthog.ts diff --git a/.env.example b/.env.example index 71d492b..45060ab 100644 --- a/.env.example +++ b/.env.example @@ -2,7 +2,8 @@ NEXT_PUBLIC_NAVIDROME_URL=http://localhost:4533 NEXT_PUBLIC_NAVIDROME_USERNAME=your_username NEXT_PUBLIC_NAVIDROME_PASSWORD=your_password - +NEXT_PUBLIC_POSTHOG_KEY=KEY +NEXT_PUBLIC_POSTHOG_HOST=HOSTURL # Example for external server: # NEXT_PUBLIC_NAVIDROME_URL=https://your-navidrome-server.com # NEXT_PUBLIC_NAVIDROME_USERNAME=your_username diff --git a/app/components/AudioPlayer.tsx b/app/components/AudioPlayer.tsx index 00b5bb3..408bb5a 100644 --- a/app/components/AudioPlayer.tsx +++ b/app/components/AudioPlayer.tsx @@ -10,9 +10,10 @@ import { Progress } from '@/components/ui/progress'; import { useToast } from '@/hooks/use-toast'; export const AudioPlayer: React.FC = () => { - const { currentTrack, playPreviousTrack, addToQueue, playNextTrack, clearQueue } = useAudioPlayer(); + const { currentTrack, playPreviousTrack, addToQueue, playNextTrack, clearQueue, queue } = useAudioPlayer(); const router = useRouter(); const audioRef = useRef(null); + const preloadAudioRef = useRef(null); const [progress, setProgress] = useState(0); const [isPlaying, setIsPlaying] = useState(false); const [showVolumeSlider, setShowVolumeSlider] = useState(false); @@ -324,6 +325,7 @@ export const AudioPlayer: React.FC = () => {
); } @@ -377,6 +379,7 @@ export const AudioPlayer: React.FC = () => {