- Created SpotifyContext for account management * Add/remove Spotify accounts with email and display name * Persistent storage to localStorage * Automatic account activation when assigned to zone - Implemented zone-to-account mapping system * Each zone can be assigned a Spotify account * Multiple zones can share one account * Switching between sources preserves account assignment - Enhanced ZoneCard component: * Account selector dropdown when Spotify is selected * Display account name/email under zone name * Auto-select first account when switching to Spotify * Green-tinted account dropdown for visual distinction - Created SpotifyAccountManager component: * Add new Spotify accounts with email and display name * List all configured accounts * Remove accounts (cleans up zone mappings) * Collapsible form for adding new accounts * Glassmorphic styling with green accent - Updated Audio page: * New 'Accounts' tab for Spotify account management * Accessible alongside Zones, Radio, and Library tabs * Smooth tab transitions with animations - Architecture supports: * Real Spotify API integration (ready for OAuth) * Multiple accounts simultaneously * Spotify Connect per account (one instance per account) * Zone grouping with shared account control - Build: 70 modules, 1.25 MB (345 KB gzipped) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
150 lines
4.2 KiB
JavaScript
150 lines
4.2 KiB
JavaScript
import { createContext, useContext, useState, useCallback } from 'react'
|
|
|
|
const SpotifyContext = createContext()
|
|
|
|
export function SpotifyProvider({ children }) {
|
|
// Account management
|
|
const [accounts, setAccounts] = useState(() => {
|
|
const saved = localStorage.getItem('spotify_accounts')
|
|
return saved ? JSON.parse(saved) : []
|
|
})
|
|
|
|
// Zone to account mapping: { zoneId: accountId }
|
|
const [zoneAccounts, setZoneAccounts] = useState(() => {
|
|
const saved = localStorage.getItem('zone_accounts')
|
|
return saved ? JSON.parse(saved) : {}
|
|
})
|
|
|
|
// Active Spotify Connect instances per account
|
|
const [spotifyConnects, setSpotifyConnects] = useState({})
|
|
|
|
// Add or update account
|
|
const addAccount = useCallback((account) => {
|
|
setAccounts(prev => {
|
|
const filtered = prev.filter(a => a.id !== account.id)
|
|
const updated = [...filtered, account]
|
|
localStorage.setItem('spotify_accounts', JSON.stringify(updated))
|
|
return updated
|
|
})
|
|
}, [])
|
|
|
|
// Remove account
|
|
const removeAccount = useCallback((accountId) => {
|
|
setAccounts(prev => {
|
|
const updated = prev.filter(a => a.id !== accountId)
|
|
localStorage.setItem('spotify_accounts', JSON.stringify(updated))
|
|
return updated
|
|
})
|
|
|
|
// Remove zone mappings for this account
|
|
setZoneAccounts(prev => {
|
|
const updated = { ...prev }
|
|
Object.keys(updated).forEach(zoneId => {
|
|
if (updated[zoneId] === accountId) delete updated[zoneId]
|
|
})
|
|
localStorage.setItem('zone_accounts', JSON.stringify(updated))
|
|
return updated
|
|
})
|
|
}, [])
|
|
|
|
// Assign account to zone
|
|
const assignAccountToZone = useCallback((zoneId, accountId) => {
|
|
setZoneAccounts(prev => {
|
|
const updated = { ...prev, [zoneId]: accountId }
|
|
localStorage.setItem('zone_accounts', JSON.stringify(updated))
|
|
return updated
|
|
})
|
|
|
|
// Activate Spotify Connect for this account if not already active
|
|
if (accountId && !spotifyConnects[accountId]) {
|
|
activateSpotifyConnect(accountId)
|
|
}
|
|
}, [spotifyConnects])
|
|
|
|
// Remove account from zone
|
|
const removeAccountFromZone = useCallback((zoneId) => {
|
|
const accountId = zoneAccounts[zoneId]
|
|
setZoneAccounts(prev => {
|
|
const updated = { ...prev }
|
|
delete updated[zoneId]
|
|
localStorage.setItem('zone_accounts', JSON.stringify(updated))
|
|
return updated
|
|
})
|
|
|
|
// Deactivate Spotify Connect if no zones use this account
|
|
if (accountId && !Object.values(zoneAccounts).includes(accountId)) {
|
|
deactivateSpotifyConnect(accountId)
|
|
}
|
|
}, [zoneAccounts])
|
|
|
|
// Activate Spotify Connect for account
|
|
const activateSpotifyConnect = useCallback((accountId) => {
|
|
if (!accountId) return
|
|
|
|
setSpotifyConnects(prev => {
|
|
if (prev[accountId]) return prev
|
|
|
|
return {
|
|
...prev,
|
|
[accountId]: {
|
|
id: `spotify-${accountId}`,
|
|
status: 'active',
|
|
device: 'Bordanlage',
|
|
connectedAt: new Date().toISOString(),
|
|
}
|
|
}
|
|
})
|
|
}, [])
|
|
|
|
// Deactivate Spotify Connect for account
|
|
const deactivateSpotifyConnect = useCallback((accountId) => {
|
|
setSpotifyConnects(prev => {
|
|
const updated = { ...prev }
|
|
delete updated[accountId]
|
|
return updated
|
|
})
|
|
}, [])
|
|
|
|
// Get account for zone
|
|
const getAccountForZone = useCallback((zoneId) => {
|
|
const accountId = zoneAccounts[zoneId]
|
|
if (!accountId) return null
|
|
return accounts.find(a => a.id === accountId)
|
|
}, [zoneAccounts, accounts])
|
|
|
|
// Get zones using account
|
|
const getZonesForAccount = useCallback((accountId) => {
|
|
return Object.entries(zoneAccounts)
|
|
.filter(([_, aId]) => aId === accountId)
|
|
.map(([zId]) => zId)
|
|
}, [zoneAccounts])
|
|
|
|
return (
|
|
<SpotifyContext.Provider
|
|
value={{
|
|
accounts,
|
|
addAccount,
|
|
removeAccount,
|
|
zoneAccounts,
|
|
assignAccountToZone,
|
|
removeAccountFromZone,
|
|
spotifyConnects,
|
|
activateSpotifyConnect,
|
|
deactivateSpotifyConnect,
|
|
getAccountForZone,
|
|
getZonesForAccount,
|
|
}}
|
|
>
|
|
{children}
|
|
</SpotifyContext.Provider>
|
|
)
|
|
}
|
|
|
|
export function useSpotify() {
|
|
const context = useContext(SpotifyContext)
|
|
if (!context) {
|
|
throw new Error('useSpotify must be used within SpotifyProvider')
|
|
}
|
|
return context
|
|
}
|