Files
boWave/dashboard/src/contexts/SpotifyContext.jsx
denshooter beeee82896 Phase 3: Real Spotify account integration with zone mapping
- 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>
2026-03-27 15:02:20 +01:00

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
}