feat: auth & admin

This commit is contained in:
2026-06-03 10:41:53 +03:00
parent 612d0f0125
commit 7dc59fb3c4
120 changed files with 4683 additions and 2159 deletions
+2 -1
View File
@@ -2,4 +2,5 @@ import { useDispatch, useSelector } from 'react-redux';
import type { AppDispatch, RootState } from '../store';
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector = <T>(selector: (state: RootState) => T) => useSelector(selector);
export const useAppSelector = <T>(selector: (state: RootState) => T) =>
useSelector(selector);
+27 -12
View File
@@ -1,7 +1,10 @@
import { useEffect, useRef, useCallback } from 'react';
import { useAppDispatch, useAppSelector } from './useAppDispatch';
import {
pause, resume, setPosition, setDuration,
pause,
resume,
setPosition,
setDuration,
setVolume as setVolumeAction,
} from '../store/slices/player';
import { nextTrack, prevTrack } from '../store/slices/queue';
@@ -87,7 +90,9 @@ export function useAudioPlayer() {
useEffect(() => {
if (!('mediaSession' in navigator)) return;
navigator.mediaSession.playbackState = player.isPlaying ? 'playing' : 'paused';
navigator.mediaSession.playbackState = player.isPlaying
? 'playing'
: 'paused';
}, [player.isPlaying]);
useEffect(() => {
@@ -120,18 +125,28 @@ export function useAudioPlayer() {
}
}, [queue.currentIndex, queue.entries, player.currentTrackId, dispatch]);
const seek = useCallback((seconds: number) => {
const audio = getAudio();
audio.currentTime = seconds;
dispatch(setPosition(seconds));
}, [dispatch]);
const seek = useCallback(
(seconds: number) => {
const audio = getAudio();
audio.currentTime = seconds;
dispatch(setPosition(seconds));
},
[dispatch],
);
const setPlayerVolume = useCallback((vol: number) => {
dispatch(setVolumeAction(vol));
}, [dispatch]);
const setPlayerVolume = useCallback(
(vol: number) => {
dispatch(setVolumeAction(vol));
},
[dispatch],
);
const playNext = useCallback(() => { dispatch(nextTrack()); }, [dispatch]);
const playPrev = useCallback(() => { dispatch(prevTrack()); }, [dispatch]);
const playNext = useCallback(() => {
dispatch(nextTrack());
}, [dispatch]);
const playPrev = useCallback(() => {
dispatch(prevTrack());
}, [dispatch]);
return { seek, setVolume: setPlayerVolume, playNext, playPrev };
}
+10 -3
View File
@@ -13,7 +13,9 @@ export function useConnectionStatus() {
if (cancelled) return;
setStatus('connecting');
try {
const res = await fetch(`${getApiBaseUrl()}/health`, { signal: AbortSignal.timeout(5000) });
const res = await fetch(`${getApiBaseUrl()}/health`, {
signal: AbortSignal.timeout(5000),
});
if (!cancelled) setStatus(res.ok ? 'connected' : 'error');
} catch {
if (!cancelled) setStatus('disconnected');
@@ -21,8 +23,13 @@ export function useConnectionStatus() {
};
void check();
const interval = setInterval(() => { void check(); }, 30_000);
return () => { cancelled = true; clearInterval(interval); };
const interval = setInterval(() => {
void check();
}, 30_000);
return () => {
cancelled = true;
clearInterval(interval);
};
}, []);
return status;
+15 -2
View File
@@ -1,9 +1,22 @@
import { useAppSelector } from './useAppDispatch';
type Permission = 'download' | 'upload' | 'admin' | 'manage_users' | 'edit_metadata' | 'delete_tracks';
type Permission =
| 'download'
| 'upload'
| 'admin'
| 'manage_users'
| 'edit_metadata'
| 'delete_tracks';
const ROLE_PERMISSIONS: Record<string, Permission[]> = {
admin: ['download', 'upload', 'admin', 'manage_users', 'edit_metadata', 'delete_tracks'],
admin: [
'download',
'upload',
'admin',
'manage_users',
'edit_metadata',
'delete_tracks',
],
user: ['download', 'upload'],
};