i18n
This commit is contained in:
@@ -12,7 +12,11 @@ import { playerKeys, playerQueries, useMe } from "@/features/players/queries";
|
||||
|
||||
interface AuthData {
|
||||
user: Player | undefined;
|
||||
metadata: { accentColor: MantineColor; colorScheme: MantineColorScheme };
|
||||
metadata: {
|
||||
accentColor: MantineColor;
|
||||
colorScheme: MantineColorScheme;
|
||||
locale?: string;
|
||||
};
|
||||
roles: string[];
|
||||
phone: string;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createContext, useCallback, useEffect, useMemo, useState, PropsWithChildren } from 'react';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { SpotifyAuth } from '@/lib/spotify/auth';
|
||||
import { useAuth } from './auth-context';
|
||||
import { useConfig } from '@/hooks/use-config';
|
||||
@@ -38,49 +39,51 @@ const deepEqual = (a: unknown, b: unknown): boolean => {
|
||||
);
|
||||
};
|
||||
|
||||
const makeSpotifyRequest = async (endpoint: string, options: RequestInit = {}) => {
|
||||
const response = await fetch(`/api/spotify/${endpoint}`, {
|
||||
...options,
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...options.headers,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
let errorMessage = 'Request failed';
|
||||
try {
|
||||
const errorData = await response.json();
|
||||
errorMessage = errorData.error || errorMessage;
|
||||
} catch {
|
||||
errorMessage = `HTTP ${response.status}: ${response.statusText}`;
|
||||
}
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
if (response.status === 204 || response.headers.get('content-length') === '0') {
|
||||
return {};
|
||||
}
|
||||
|
||||
const contentType = response.headers.get('content-type') || '';
|
||||
if (!contentType.includes('application/json')) {
|
||||
console.warn(`Non-JSON response from ${endpoint}:`, contentType);
|
||||
return {};
|
||||
}
|
||||
|
||||
try {
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.warn(`Failed to parse JSON response from ${endpoint}:`, error);
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
export const SpotifyProvider: React.FC<PropsWithChildren> = ({ children }) => {
|
||||
const { roles } = useAuth();
|
||||
const isAdmin = roles?.includes('Admin') || false;
|
||||
const config = useConfig();
|
||||
const { t } = useLingui();
|
||||
|
||||
// Defined inside the provider so error messages can be localized via `t`.
|
||||
const makeSpotifyRequest = async (endpoint: string, options: RequestInit = {}) => {
|
||||
const response = await fetch(`/api/spotify/${endpoint}`, {
|
||||
...options,
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...options.headers,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
let errorMessage = t`Request failed`;
|
||||
try {
|
||||
const errorData = await response.json();
|
||||
errorMessage = errorData.error || errorMessage;
|
||||
} catch {
|
||||
errorMessage = t`HTTP ${response.status}: ${response.statusText}`;
|
||||
}
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
if (response.status === 204 || response.headers.get('content-length') === '0') {
|
||||
return {};
|
||||
}
|
||||
|
||||
const contentType = response.headers.get('content-type') || '';
|
||||
if (!contentType.includes('application/json')) {
|
||||
console.warn(`Non-JSON response from ${endpoint}:`, contentType);
|
||||
return {};
|
||||
}
|
||||
|
||||
try {
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.warn(`Failed to parse JSON response from ${endpoint}:`, error);
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
const [authState, setAuthState] = useState<SpotifyAuthState>(defaultSpotifyState);
|
||||
|
||||
@@ -150,10 +153,9 @@ export const SpotifyProvider: React.FC<PropsWithChildren> = ({ children }) => {
|
||||
}
|
||||
|
||||
if (error) {
|
||||
let errorMessage = `Authentication failed: ${error}`;
|
||||
if (details) {
|
||||
errorMessage += ` - ${decodeURIComponent(details)}`;
|
||||
}
|
||||
const errorMessage = details
|
||||
? t`Authentication failed: ${error} - ${decodeURIComponent(details)}`
|
||||
: t`Authentication failed: ${error}`;
|
||||
setError(errorMessage);
|
||||
|
||||
console.error('Spotify OAuth Error:', { error, details });
|
||||
@@ -203,7 +205,7 @@ export const SpotifyProvider: React.FC<PropsWithChildren> = ({ children }) => {
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [authState.isAuthenticated]);
|
||||
}, [authState.isAuthenticated, t]);
|
||||
|
||||
const playTrack = useCallback(async (trackId: string, deviceId?: string, positionMs?: number) => {
|
||||
if (!authState.isAuthenticated) return;
|
||||
@@ -226,7 +228,7 @@ export const SpotifyProvider: React.FC<PropsWithChildren> = ({ children }) => {
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [authState.isAuthenticated]);
|
||||
}, [authState.isAuthenticated, t]);
|
||||
|
||||
const pause = useCallback(async () => {
|
||||
if (!authState.isAuthenticated) return;
|
||||
@@ -249,7 +251,7 @@ export const SpotifyProvider: React.FC<PropsWithChildren> = ({ children }) => {
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [authState.isAuthenticated]);
|
||||
}, [authState.isAuthenticated, t]);
|
||||
|
||||
const skipNext = useCallback(async () => {
|
||||
if (!authState.isAuthenticated) return;
|
||||
@@ -272,7 +274,7 @@ export const SpotifyProvider: React.FC<PropsWithChildren> = ({ children }) => {
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [authState.isAuthenticated]);
|
||||
}, [authState.isAuthenticated, t]);
|
||||
|
||||
const skipPrevious = useCallback(async () => {
|
||||
if (!authState.isAuthenticated) return;
|
||||
@@ -295,7 +297,7 @@ export const SpotifyProvider: React.FC<PropsWithChildren> = ({ children }) => {
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [authState.isAuthenticated]);
|
||||
}, [authState.isAuthenticated, t]);
|
||||
|
||||
const setVolume = useCallback(async (volumePercent: number) => {
|
||||
if (!authState.isAuthenticated) return;
|
||||
@@ -309,11 +311,11 @@ export const SpotifyProvider: React.FC<PropsWithChildren> = ({ children }) => {
|
||||
body: JSON.stringify({ action: 'volume', volumePercent }),
|
||||
});
|
||||
} catch (error) {
|
||||
setError(error instanceof Error ? error.message : 'Failed to set volume');
|
||||
setError(error instanceof Error ? error.message : t`Failed to set volume`);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [authState.isAuthenticated]);
|
||||
}, [authState.isAuthenticated, t]);
|
||||
|
||||
const getDevices = useCallback(async () => {
|
||||
if (!authState.isAuthenticated) return;
|
||||
@@ -330,11 +332,11 @@ export const SpotifyProvider: React.FC<PropsWithChildren> = ({ children }) => {
|
||||
setActiveDeviceState(active);
|
||||
}
|
||||
} catch (error) {
|
||||
setError(error instanceof Error ? error.message : 'Failed to get devices');
|
||||
setError(error instanceof Error ? error.message : t`Failed to get devices`);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [authState.isAuthenticated]);
|
||||
}, [authState.isAuthenticated, t]);
|
||||
|
||||
const setActiveDevice = useCallback(async (deviceId: string) => {
|
||||
if (!authState.isAuthenticated) return;
|
||||
@@ -355,11 +357,11 @@ export const SpotifyProvider: React.FC<PropsWithChildren> = ({ children }) => {
|
||||
|
||||
setTimeout(getDevices, 1000);
|
||||
} catch (error) {
|
||||
setError(error instanceof Error ? error.message : 'Failed to set active device');
|
||||
setError(error instanceof Error ? error.message : t`Failed to set active device`);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [authState.isAuthenticated, devices]);
|
||||
}, [authState.isAuthenticated, devices, t]);
|
||||
|
||||
const refreshPlaybackState = useCallback(async () => {
|
||||
if (!authState.isAuthenticated) return;
|
||||
@@ -382,7 +384,7 @@ export const SpotifyProvider: React.FC<PropsWithChildren> = ({ children }) => {
|
||||
} catch (error) {
|
||||
console.warn('Failed to refresh playback state:', error);
|
||||
}
|
||||
}, [authState.isAuthenticated]);
|
||||
}, [authState.isAuthenticated, t]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!authState.isAuthenticated) return;
|
||||
@@ -422,11 +424,11 @@ export const SpotifyProvider: React.FC<PropsWithChildren> = ({ children }) => {
|
||||
setCapturedState(response.snapshot);
|
||||
}
|
||||
} catch (error) {
|
||||
setError(error instanceof Error ? error.message : 'Failed to capture playback state');
|
||||
setError(error instanceof Error ? error.message : t`Failed to capture playback state`);
|
||||
} finally {
|
||||
setIsCaptureLoading(false);
|
||||
}
|
||||
}, [authState.isAuthenticated]);
|
||||
}, [authState.isAuthenticated, t]);
|
||||
|
||||
const resumePlaybackState = useCallback(async () => {
|
||||
if (!authState.isAuthenticated || !capturedState) return;
|
||||
@@ -442,11 +444,11 @@ export const SpotifyProvider: React.FC<PropsWithChildren> = ({ children }) => {
|
||||
|
||||
setTimeout(refreshPlaybackState, 1000);
|
||||
} catch (error) {
|
||||
setError(error instanceof Error ? error.message : 'Failed to resume playback state');
|
||||
setError(error instanceof Error ? error.message : t`Failed to resume playback state`);
|
||||
} finally {
|
||||
setIsResumeLoading(false);
|
||||
}
|
||||
}, [authState.isAuthenticated, capturedState, refreshPlaybackState]);
|
||||
}, [authState.isAuthenticated, capturedState, refreshPlaybackState, t]);
|
||||
|
||||
const clearCapturedState = useCallback(() => {
|
||||
setCapturedState(null);
|
||||
|
||||
Reference in New Issue
Block a user