Files
flxn-app/src/features/spotify/components/spotify-sheet.tsx
T
kyle 0a7b5fa00f
CI/CD Pipeline / Build and Push PocketBase Docker Image (push) Successful in 8s
CI/CD Pipeline / i18n Catalog Check (push) Failing after 10s
CI/CD Pipeline / Build and Push App Docker Image (push) Skipped
CI/CD Pipeline / Deploy to Kubernetes (push) Skipped
i18n
2026-08-08 15:29:40 -07:00

197 lines
5.8 KiB
TypeScript

import {
ActionIcon,
Box,
Button,
Divider,
Group,
NativeSelect,
Stack,
Text,
Title
} from '@mantine/core';
import {
SpotifyLogoIcon,
DevicesIcon,
SignOutIcon,
ArrowsClockwiseIcon
} from '@phosphor-icons/react';
import { Trans, useLingui } from '@lingui/react/macro';
import { useSpotify } from '@/lib/spotify/hooks';
import { useAuth } from '@/contexts/auth-context';
import Sheet from '@/components/sheet/sheet';
interface SpotifySheetProps {
opened: boolean;
onClose: () => void;
}
const SpotifySheet: React.FC<SpotifySheetProps> = ({ opened, onClose }) => {
const { roles } = useAuth();
const isAdmin = roles?.includes('Admin') || false;
const spotify = useSpotify();
const { t } = useLingui();
if (!isAdmin || !spotify) return null;
const {
isAuthenticated,
login,
logout,
devices,
activeDevice,
currentTrack,
getDevices,
setActiveDevice,
isLoading,
error,
} = spotify;
if (!isAdmin) return null;
const handleDeviceChange = (deviceId: string) => {
if (deviceId && deviceId !== activeDevice?.id) {
setActiveDevice(deviceId);
}
};
const refreshDevices = () => {
getDevices();
};
const handleChange = (opened: boolean) => {
if (!opened) onClose();
};
return (
<Sheet
opened={opened}
onChange={handleChange}
title={t`Spotify Controls`}
>
<Stack gap="lg">
{!isAuthenticated ? (
<>
<Box>
<Text size="sm" c="dimmed" mb="md">
<Trans>Connect your Spotify account</Trans>
</Text>
</Box>
<Button
leftSection={<SpotifyLogoIcon size={20} />}
color="green"
size="lg"
onClick={login}
loading={isLoading}
fullWidth
>
<Trans>Connect with Spotify</Trans>
</Button>
</>
) : (
<>
{/* Current track display */}
{currentTrack && (
<>
<Box>
<Title order={5} mb="xs"><Trans>Now Playing</Trans></Title>
<Group gap="md">
{currentTrack.album.images[2] && (
<img
src={currentTrack.album.images[2].url}
alt={currentTrack.album.name}
style={{ width: 64, height: 64, borderRadius: 4 }}
/>
)}
<Box flex={1}>
<Text fw={600} size="sm" truncate>
{currentTrack.name}
</Text>
<Text size="xs" c="dimmed" truncate>
{currentTrack.artists.map(a => a.name).join(', ')}
</Text>
<Text size="xs" c="dimmed" truncate>
{currentTrack.album.name}
</Text>
</Box>
</Group>
</Box>
<Divider />
</>
)}
<Box>
<Group justify="space-between" align="center" mb="sm">
<Title order={5}>
<Group gap="xs">
<DevicesIcon size={20} />
<Trans>Select Device</Trans>
</Group>
</Title>
<ActionIcon
variant="light"
size="sm"
onClick={refreshDevices}
loading={isLoading}
>
<ArrowsClockwiseIcon size={16} />
</ActionIcon>
</Group>
{devices.length > 0 ? (
<NativeSelect
value={activeDevice?.id || ''}
onChange={(event) => handleDeviceChange(event.currentTarget.value)}
data={[
{ value: '', label: t`Select a device...` },
...devices.map(device => ({
value: device.id,
label: `${device.name} ${device.is_active ? t`(Active)` : ''} - ${device.type}`,
})),
]}
disabled={isLoading}
/>
) : (
<Text size="sm" c="dimmed">
<Trans>No devices found.</Trans>
</Text>
)}
{activeDevice && (
<Text size="xs" c="dimmed" mt="xs">
<Trans>Active device: {activeDevice.name}</Trans>
{activeDevice.volume_percent !== null &&
t` (Volume: ${activeDevice.volume_percent}%)`
}
</Text>
)}
</Box>
{error && (
<>
<Divider />
<Box>
<Text size="sm" c="red">
{error}
</Text>
</Box>
</>
)}
<Divider />
<Button
leftSection={<SignOutIcon size={18} />}
variant="light"
color="red"
onClick={logout}
fullWidth
>
<Trans>Disconnect Spotify</Trans>
</Button>
</>
)}
</Stack>
</Sheet>
);
};
export default SpotifySheet;