Files
MusicAnalyser/frontend/src/components/PlaylistsSection.jsx

170 lines
6.6 KiB
JavaScript

import React, { useState, useEffect } from 'react';
import axios from 'axios';
import { Card, Button, Typography, Space, Spin, message, Tooltip as AntTooltip } from 'antd';
import {
PlayCircleOutlined,
ReloadOutlined,
HistoryOutlined,
InfoCircleOutlined,
CustomerServiceOutlined
} from '@ant-design/icons';
import Tooltip from './Tooltip';
import TrackList from './TrackList';
const { Title, Text, Paragraph } = Typography;
const PlaylistsSection = () => {
const [loading, setLoading] = useState(true);
const [refreshing, setRefreshing] = useState({ sixHour: false, daily: false });
const [playlists, setPlaylists] = useState(null);
const fetchPlaylists = async () => {
try {
const response = await axios.get('/api/playlists');
setPlaylists(response.data);
} catch (error) {
console.error('Failed to fetch playlists:', error);
message.error('Failed to load playlist metadata');
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchPlaylists();
}, []);
const handleRefresh = async (type) => {
const isSixHour = type === 'six-hour';
setRefreshing(prev => ({ ...prev, [isSixHour ? 'sixHour' : 'daily']: true }));
try {
const endpoint = isSixHour ? '/api/playlists/refresh/six-hour' : '/api/playlists/refresh/daily';
await axios.post(endpoint);
message.success(`${isSixHour ? '6-Hour' : 'Daily'} playlist refreshed!`);
await fetchPlaylists();
} catch (error) {
console.error(`Refresh failed for ${type}:`, error);
message.error(`Failed to refresh ${type} playlist`);
} finally {
setRefreshing(prev => ({ ...prev, [isSixHour ? 'sixHour' : 'daily']: false }));
}
};
if (loading) return <div className="flex justify-center p-8"><Spin size="large" /></div>;
return (
<div className="mt-8 space-y-6">
<div className="flex items-center space-x-2">
<Title level={3} className="!mb-0 text-white flex items-center">
<CustomerServiceOutlined className="mr-2 text-blue-400" />
AI Curated Playlists
</Title>
<Tooltip text="Dynamic playlists that evolve with your taste. Refreshed automatically, or trigger manually here.">
<InfoCircleOutlined className="text-gray-400 cursor-help" />
</Tooltip>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{/* 6-Hour Playlist */}
<Card
className="bg-slate-800 border-slate-700 shadow-xl"
title={<span className="text-blue-400 flex items-center"><HistoryOutlined className="mr-2" /> Short & Sweet (6h)</span>}
extra={
<Button
type="text"
icon={<ReloadOutlined spin={refreshing.sixHour} />}
onClick={() => handleRefresh('six-hour')}
className="text-gray-400 hover:text-white"
disabled={refreshing.sixHour}
/>
}
>
<div className="space-y-4">
<div>
<Text className="text-gray-400 text-xs uppercase tracking-wider block mb-1">Current Theme</Text>
<Title level={4} className="!mt-0 !mb-1 text-white">{playlists?.six_hour?.theme || 'Calculating...'}</Title>
<Paragraph className="text-gray-300 text-sm italic mb-0">
"{playlists?.six_hour?.reasoning || 'Analyzing your recent listening patterns to find the perfect vibe.'}"
</Paragraph>
<TrackList tracks={playlists?.six_hour?.composition} />
</div>
<div className="flex items-center justify-between pt-2 border-t border-slate-700">
<div className="flex flex-col">
<Text className="text-gray-500 text-xs">Last Updated</Text>
<Text className="text-gray-300 text-xs font-mono">
{playlists?.six_hour?.last_refresh ? new Date(playlists.six_hour.last_refresh).toLocaleString() : 'Never'}
</Text>
</div>
<Button
type="primary"
shape="round"
icon={<PlayCircleOutlined />}
href={`https://open.spotify.com/playlist/${playlists?.six_hour?.id}`}
target="_blank"
disabled={!playlists?.six_hour?.id}
className="bg-blue-600 hover:bg-blue-500 border-none"
>
Open Spotify
</Button>
</div>
</div>
</Card>
{/* Daily Playlist */}
<Card
className="bg-slate-800 border-slate-700 shadow-xl"
title={<span className="text-purple-400 flex items-center"><PlayCircleOutlined className="mr-2" /> Proof of Commitment (24h)</span>}
extra={
<Button
type="text"
icon={<ReloadOutlined spin={refreshing.daily} />}
onClick={() => handleRefresh('daily')}
className="text-gray-400 hover:text-white"
disabled={refreshing.daily}
/>
}
>
<div className="space-y-4">
<div>
<Text className="text-gray-400 text-xs uppercase tracking-wider block mb-1">Daily Mix Strategy</Text>
<Title level={4} className="!mt-0 !mb-1 text-white">Daily Devotion Mix</Title>
<Paragraph className="text-gray-300 text-sm mb-0">
A blend of 30 all-time favorites and 20 recent discoveries to keep your rotation fresh but familiar.
</Paragraph>
<TrackList tracks={playlists?.daily?.composition} />
</div>
<div className="flex items-center justify-between pt-2 border-t border-slate-700">
<div className="flex flex-col">
<Text className="text-gray-500 text-xs">Last Updated</Text>
<Text className="text-gray-300 text-xs font-mono">
{playlists?.daily?.last_refresh ? new Date(playlists.daily.last_refresh).toLocaleString() : 'Never'}
</Text>
</div>
<Button
type="primary"
shape="round"
icon={<PlayCircleOutlined />}
href={`https://open.spotify.com/playlist/${playlists?.daily?.id}`}
target="_blank"
disabled={!playlists?.daily?.id}
className="bg-purple-600 hover:bg-purple-500 border-none"
>
Open Spotify
</Button>
</div>
</div>
</Card>
</div>
</div>
);
};
export default PlaylistsSection;