feat: remove unused files and dependencies, streamline project structure
This commit is contained in:
34
.github/workflows/jest.yml
vendored
34
.github/workflows/jest.yml
vendored
@@ -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
|
||||
@@ -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`
|
||||
@@ -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<FullScreenPlayerProps> = ({ isOpen, onCl
|
||||
<h1 className="text-lg sm:text-xl lg:text-3xl font-bold text-foreground mb-2 line-clamp-2 leading-tight">
|
||||
{currentTrack.name}
|
||||
</h1>
|
||||
<p className="text-base sm:text-lg lg:text-xl text-foreground/80 mb-1 line-clamp-1">{currentTrack.artist}</p>
|
||||
<Link href={`/album${currentTrack.artistId}`} className="text-base sm:text-lg lg:text-xl text-foreground/80 mb-1 line-clamp-1">
|
||||
{currentTrack.artist}
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* Progress */}
|
||||
<div className="w-full max-w-sm lg:max-w-md mb-4 lg:mb-6 px-4 flex-shrink-0">
|
||||
<div
|
||||
className="h-2 bg-white/20 rounded-full cursor-pointer relative overflow-hidden"
|
||||
onClick={handleSeek}
|
||||
>
|
||||
<div
|
||||
className="h-full bg-foreground transition-all duration-150"
|
||||
style={{ width: `${progress}%` }}
|
||||
/>
|
||||
<div className="w-full" onClick={handleSeek}>
|
||||
<Progress value={progress} className="h-2 cursor-pointer" />
|
||||
</div>
|
||||
<div className="flex justify-between text-sm text-foreground/60 mt-2">
|
||||
<span>{formatTime(currentTime)}</span>
|
||||
|
||||
@@ -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) {
|
||||
<NavidromeConfigProvider>
|
||||
<NavidromeProvider>
|
||||
<AudioPlayerProvider>
|
||||
<SpeedInsights />
|
||||
<Analytics />
|
||||
<Ihateserverside>
|
||||
{children}
|
||||
</Ihateserverside>
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
moduleNameMapper: {
|
||||
'^@/(.*)$': '<rootDir>/$1',
|
||||
},
|
||||
};
|
||||
@@ -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",
|
||||
|
||||
68
pnpm-lock.yaml
generated
68
pnpm-lock.yaml
generated
@@ -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
|
||||
|
||||
121
theme.md
121
theme.md
@@ -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%;
|
||||
}
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user