Update player/shared components

This commit is contained in:
jeffvli
2022-10-24 22:30:16 -07:00
parent 8973571147
commit dd3de66232
21 changed files with 164 additions and 139 deletions
+28 -34
View File
@@ -1,20 +1,14 @@
import { ReactNode, useEffect } from 'react'; import { ReactNode, useEffect } from 'react';
import {
DndContext,
MouseSensor,
TouchSensor,
useSensor,
useSensors,
} from '@dnd-kit/core';
import { MantineProvider } from '@mantine/core'; import { MantineProvider } from '@mantine/core';
import { useLocalStorage } from '@mantine/hooks'; import { useLocalStorage } from '@mantine/hooks';
import { ModalsProvider } from '@mantine/modals';
import { NotificationsProvider } from '@mantine/notifications';
import isElectron from 'is-electron'; import isElectron from 'is-electron';
import { BrowserRouter, HashRouter } from 'react-router-dom'; import { BrowserRouter, HashRouter } from 'react-router-dom';
import { useDefaultSettings } from './features/settings'; import { useDefaultSettings } from './features/settings';
import { AppRouter } from './router/AppRouter'; import { AppRouter } from './router/app-router';
import './styles/global.scss'; import './styles/global.scss';
import 'ag-grid-community/styles/ag-grid.css'; import 'ag-grid-community/styles/ag-grid.css';
import './styles/ag-grid.scss';
const SelectRouter = ({ children }: { children: ReactNode }) => { const SelectRouter = ({ children }: { children: ReactNode }) => {
if (isElectron()) { if (isElectron()) {
@@ -36,27 +30,16 @@ export const App = () => {
document.body.setAttribute('data-theme', theme); document.body.setAttribute('data-theme', theme);
}, [theme]); }, [theme]);
const sensors = useSensors(
useSensor(MouseSensor, {
activationConstraint: {
delay: 200,
tolerance: 100,
},
}),
useSensor(TouchSensor, {
activationConstraint: {
delay: 500,
tolerance: 10,
},
})
);
return ( return (
<MantineProvider <MantineProvider
withGlobalStyles
withNormalizeCSS
theme={{ theme={{
colorScheme: 'dark', colorScheme: 'dark',
defaultRadius: 'xs', defaultRadius: 'xs',
focusRing: 'auto', dir: 'ltr',
focusRing: 'never',
fontFamily: 'Poppins, sans-serif',
fontSizes: { fontSizes: {
lg: 16, lg: 16,
md: 14, md: 14,
@@ -64,22 +47,33 @@ export const App = () => {
xl: 18, xl: 18,
xs: 10, xs: 10,
}, },
other: {}, other: {},
spacing: { spacing: {
lg: 12,
md: 8,
sm: 4,
xl: 16,
xs: 2, xs: 2,
}, },
}} }}
> >
<DndContext <NotificationsProvider
sensors={sensors} autoClose={1500}
onDragEnd={() => console.log('drag end')} position="bottom-right"
onDragStart={() => console.log('drag start')} style={{
marginBottom: '90px',
opacity: '.8',
userSelect: 'none',
width: '250px',
}}
transitionDuration={200}
> >
<SelectRouter> <ModalsProvider>
<AppRouter /> <SelectRouter>
</SelectRouter> <AppRouter />
</DndContext> </SelectRouter>
</ModalsProvider>
</NotificationsProvider>
</MantineProvider> </MantineProvider>
); );
}; };
@@ -1,7 +1,7 @@
import { Ref, useMemo } from 'react'; import { Ref, useMemo } from 'react';
import { FixedSizeList, FixedSizeListProps } from 'react-window'; import { FixedSizeList, FixedSizeListProps } from 'react-window';
import { GridCard } from '@/renderer/components/virtual-grid/grid-card'; import { GridCard } from '@/renderer/components/virtual-grid/grid-card';
import { usePlayQueueHandler } from '@/renderer/features/player/hooks/usePlayQueueHandler'; import { usePlayQueueHandler } from '@/renderer/features/player/hooks/use-playqueue-handler';
import { CardRow } from '@/renderer/types'; import { CardRow } from '@/renderer/types';
export const VirtualGridWrapper = ({ export const VirtualGridWrapper = ({
@@ -1,11 +1,11 @@
import { useRef } from 'react'; import { useRef } from 'react';
import styled from 'styled-components'; import styled from '@emotion/styled';
import { PlaybackType } from '../../../../types'; import { PlaybackType } from '../../../../types';
import { AudioPlayer } from '../../../components'; import { AudioPlayer } from '../../../components';
import { usePlayerStore } from '../../../store'; import { usePlayerStore } from '../../../store';
import { CenterControls } from './CenterControls'; import { CenterControls } from './center-controls';
import { LeftControls } from './LeftControls'; import { LeftControls } from './left-controls';
import { RightControls } from './RightControls'; import { RightControls } from './right-controls';
const PlayerbarContainer = styled.div` const PlayerbarContainer = styled.div`
width: 100%; width: 100%;
@@ -1,7 +1,7 @@
import { useMemo, useState } from 'react'; import { useMemo, useState } from 'react';
import styled from '@emotion/styled';
import format from 'format-duration'; import format from 'format-duration';
import ReactSlider, { ReactSliderProps } from 'react-slider'; import ReactSlider, { ReactSliderProps } from 'react-slider';
import styled from 'styled-components';
interface SliderProps extends ReactSliderProps { interface SliderProps extends ReactSliderProps {
hasTooltip?: boolean; hasTooltip?: boolean;
@@ -1,22 +1,22 @@
import { useEffect, useMemo, useState } from 'react'; import { useEffect, useMemo, useState } from 'react';
import styled from '@emotion/styled';
import format from 'format-duration'; import format from 'format-duration';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { import {
RiPauseLine, RiPauseLine,
RiPlayFill, RiPlayFill,
RiRewindFill, RiRepeat2Fill,
RiShuffleFill,
RiSkipBackFill, RiSkipBackFill,
RiSkipForwardFill, RiSkipForwardFill,
RiSpeedFill,
} from 'react-icons/ri'; } from 'react-icons/ri';
import styled from 'styled-components';
import { PlaybackType, PlayerStatus } from '../../../../types'; import { PlaybackType, PlayerStatus } from '../../../../types';
import { Text } from '../../../components'; import { Text } from '../../../components';
import { usePlayerStore } from '../../../store'; import { usePlayerStore } from '../../../store';
import { Font } from '../../../styles'; import { Font } from '../../../styles';
import { useCenterControls } from '../hooks/useCenterControls'; import { useCenterControls } from '../hooks/use-center-controls';
import { PlayerButton } from './PlayerButton'; import { PlayerButton } from './player-button';
import { Slider } from './Slider'; import { Slider } from './slider';
interface CenterControlsProps { interface CenterControlsProps {
playersRef: any; playersRef: any;
@@ -67,8 +67,6 @@ export const CenterControls = ({ playersRef }: CenterControlsProps) => {
handlePlayPause, handlePlayPause,
handlePrevTrack, handlePrevTrack,
handleSeekSlider, handleSeekSlider,
handleSkipBackward,
handleSkipForward,
} = useCenterControls({ playersRef }); } = useCenterControls({ playersRef });
const currentTime = usePlayerStore((state) => state.current.time); const currentTime = usePlayerStore((state) => state.current.time);
@@ -106,16 +104,16 @@ export const CenterControls = ({ playersRef }: CenterControlsProps) => {
<ControlsContainer onScroll={(e) => console.log(e)}> <ControlsContainer onScroll={(e) => console.log(e)}>
<ButtonsContainer> <ButtonsContainer>
<PlayerButton <PlayerButton
icon={<RiSkipBackFill size={15} />} icon={<RiShuffleFill size={15} />}
tooltip={{ label: `${t('player.prev')}` }} tooltip={{ label: `${t('player.shuffle')}` }}
variant="secondary" variant="secondary"
onClick={handlePrevTrack} onClick={handlePrevTrack}
/> />
<PlayerButton <PlayerButton
icon={<RiRewindFill size={15} />} icon={<RiSkipBackFill size={15} />}
tooltip={{ label: `${t('player.skipBack')}` }} tooltip={{ label: `${t('player.previous')}` }}
variant="secondary" variant="secondary"
onClick={handleSkipBackward} onClick={handlePrevTrack}
/> />
<PlayerButton <PlayerButton
icon={ icon={
@@ -134,18 +132,18 @@ export const CenterControls = ({ playersRef }: CenterControlsProps) => {
variant="main" variant="main"
onClick={handlePlayPause} onClick={handlePlayPause}
/> />
<PlayerButton
icon={<RiSpeedFill size={15} />}
tooltip={{ label: `${t('player.skipForward')}` }}
variant="secondary"
onClick={handleSkipForward}
/>
<PlayerButton <PlayerButton
icon={<RiSkipForwardFill size={15} />} icon={<RiSkipForwardFill size={15} />}
tooltip={{ label: `${t('player.next')}` }} tooltip={{ label: `${t('player.next')}` }}
variant="secondary" variant="secondary"
onClick={handleNextTrack} onClick={handleNextTrack}
/> />
<PlayerButton
icon={<RiRepeat2Fill size={15} />}
tooltip={{ label: `${t('player.repeat')}` }}
variant="secondary"
onClick={handleNextTrack}
/>
</ButtonsContainer> </ButtonsContainer>
</ControlsContainer> </ControlsContainer>
<SliderContainer> <SliderContainer>
@@ -1,4 +1,4 @@
import styled from 'styled-components'; import styled from '@emotion/styled';
import { Text } from '../../../components'; import { Text } from '../../../components';
import { usePlayerStore } from '../../../store'; import { usePlayerStore } from '../../../store';
import { Font } from '../../../styles'; import { Font } from '../../../styles';
@@ -34,7 +34,9 @@ export const LeftControls = () => {
return ( return (
<LeftControlsContainer> <LeftControlsContainer>
<ImageWrapper> <ImageWrapper>
<img alt="img" height={60} src={song?.imageUrl} width={60} /> {song?.imageUrl && (
<img alt="img" height={60} src={song?.imageUrl} width={60} />
)}
</ImageWrapper> </ImageWrapper>
<MetadataStack> <MetadataStack>
<Text <Text
@@ -1,11 +1,12 @@
import { ComponentPropsWithoutRef, ReactNode } from 'react'; import { ComponentPropsWithoutRef, ReactNode } from 'react';
import { css } from '@emotion/react';
import styled from '@emotion/styled';
import { import {
TooltipProps, TooltipProps,
UnstyledButton, UnstyledButton,
UnstyledButtonProps, UnstyledButtonProps,
} from '@mantine/core'; } from '@mantine/core';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import styled, { css } from 'styled-components';
import { Tooltip } from '../../../components'; import { Tooltip } from '../../../components';
type MantineButtonProps = UnstyledButtonProps & type MantineButtonProps = UnstyledButtonProps &
@@ -32,17 +33,11 @@ const MotionWrapper = styled(motion.div)<MotionWrapperProps>`
const ButtonMainVariant = css` const ButtonMainVariant = css`
padding: 0.5rem; padding: 0.5rem;
background: var(--playerbar-btn-color); border: 2px solid var(--playerbar-btn-color);
border-radius: 50%; border-radius: 50%;
svg { svg {
display: flex; display: flex;
fill: black;
stroke: black;
}
&:hover {
background: var(--playerbar-btn-color-hover);
} }
&:focus-visible { &:focus-visible {
@@ -109,13 +104,17 @@ export const PlayerButton = ({
}: PlayerButtonProps) => { }: PlayerButtonProps) => {
if (tooltip) { if (tooltip) {
return ( return (
<MotionWrapper variant={variant}> <Tooltip {...tooltip}>
<Tooltip {...tooltip}> <MotionWrapper
variant={variant}
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
>
<StyledPlayerButton variant={variant} {...rest}> <StyledPlayerButton variant={variant} {...rest}>
{icon} {icon}
</StyledPlayerButton> </StyledPlayerButton>
</Tooltip> </MotionWrapper>
</MotionWrapper> </Tooltip>
); );
} }
@@ -1,9 +1,9 @@
import styled from '@emotion/styled';
import { RiVolumeUpFill, RiVolumeMuteFill } from 'react-icons/ri'; import { RiVolumeUpFill, RiVolumeMuteFill } from 'react-icons/ri';
import styled from 'styled-components';
import { IconButton } from '../../../components';
import { usePlayerStore } from '../../../store'; import { usePlayerStore } from '../../../store';
import { useRightControls } from '../hooks/useRightControls'; import { useRightControls } from '../hooks/use-right-controls';
import { Slider } from './Slider'; import { PlayerButton } from './player-button';
import { Slider } from './slider';
const RightControlsContainer = styled.div` const RightControlsContainer = styled.div`
display: flex; display: flex;
@@ -39,23 +39,21 @@ export const RightControls = () => {
<RightControlsContainer> <RightControlsContainer>
<MetadataStack> <MetadataStack>
<VolumeSliderWrapper> <VolumeSliderWrapper>
<IconButton <PlayerButton
icon={ icon={
muted ? ( muted ? (
<RiVolumeMuteFill size={20} /> <RiVolumeMuteFill size={15} />
) : ( ) : (
<RiVolumeUpFill size={20} /> <RiVolumeUpFill size={15} />
) )
} }
size={20}
tooltip={{ label: muted ? 'Muted' : volume }} tooltip={{ label: muted ? 'Muted' : volume }}
variant="transparent" variant="secondary"
onClick={handleMute} onClick={handleMute}
/> />
<Slider <Slider
hasTooltip hasTooltip
height="100%" height="60%"
max={100} max={100}
min={0} min={0}
value={volume} value={volume}
@@ -42,7 +42,7 @@ export const useCenterControls = (args: { playersRef: any }) => {
}; };
const handlePlay = useCallback(() => { const handlePlay = useCallback(() => {
if (settings.type === PlaybackType.Local) { if (settings.type === PlaybackType.LOCAL) {
mpvPlayer.play(); mpvPlayer.play();
} else { } else {
currentPlayerRef.getInternalPlayer().play(); currentPlayerRef.getInternalPlayer().play();
@@ -52,7 +52,7 @@ export const useCenterControls = (args: { playersRef: any }) => {
}, [currentPlayerRef, play, settings]); }, [currentPlayerRef, play, settings]);
const handlePause = useCallback(() => { const handlePause = useCallback(() => {
if (settings.type === PlaybackType.Local) { if (settings.type === PlaybackType.LOCAL) {
mpvPlayer.pause(); mpvPlayer.pause();
} }
@@ -60,7 +60,7 @@ export const useCenterControls = (args: { playersRef: any }) => {
}, [pause, settings]); }, [pause, settings]);
const handleStop = () => { const handleStop = () => {
if (settings.type === PlaybackType.Local) { if (settings.type === PlaybackType.LOCAL) {
mpvPlayer.stop(); mpvPlayer.stop();
} else { } else {
stopPlayback(); stopPlayback();
@@ -73,7 +73,7 @@ export const useCenterControls = (args: { playersRef: any }) => {
const handleNextTrack = useCallback(() => { const handleNextTrack = useCallback(() => {
const playerData = next(); const playerData = next();
if (settings.type === PlaybackType.Local) { if (settings.type === PlaybackType.LOCAL) {
mpvPlayer.setQueue(playerData); mpvPlayer.setQueue(playerData);
mpvPlayer.next(); mpvPlayer.next();
} else { } else {
@@ -86,7 +86,7 @@ export const useCenterControls = (args: { playersRef: any }) => {
const handlePrevTrack = useCallback(() => { const handlePrevTrack = useCallback(() => {
const playerData = prev(); const playerData = prev();
if (settings.type === PlaybackType.Local) { if (settings.type === PlaybackType.LOCAL) {
mpvPlayer.setQueue(playerData); mpvPlayer.setQueue(playerData);
mpvPlayer.previous(); mpvPlayer.previous();
} else { } else {
@@ -98,7 +98,7 @@ export const useCenterControls = (args: { playersRef: any }) => {
const handlePlayPause = useCallback(() => { const handlePlayPause = useCallback(() => {
if (queue) { if (queue) {
if (playerStatus === PlayerStatus.Paused) { if (playerStatus === PlayerStatus.PAUSED) {
return handlePlay(); return handlePlay();
} }
@@ -111,7 +111,7 @@ export const useCenterControls = (args: { playersRef: any }) => {
const handleSkipBackward = () => { const handleSkipBackward = () => {
const skipBackwardSec = 5; const skipBackwardSec = 5;
if (settings.type === PlaybackType.Local) { if (settings.type === PlaybackType.LOCAL) {
const newTime = currentTime - skipBackwardSec; const newTime = currentTime - skipBackwardSec;
mpvPlayer.seek(-skipBackwardSec); mpvPlayer.seek(-skipBackwardSec);
setCurrentTime(newTime < 0 ? 0 : newTime); setCurrentTime(newTime < 0 ? 0 : newTime);
@@ -126,7 +126,7 @@ export const useCenterControls = (args: { playersRef: any }) => {
const handleSkipForward = () => { const handleSkipForward = () => {
const skipForwardSec = 5; const skipForwardSec = 5;
if (settings.type === PlaybackType.Local) { if (settings.type === PlaybackType.LOCAL) {
const newTime = currentTime + skipForwardSec; const newTime = currentTime + skipForwardSec;
mpvPlayer.seek(skipForwardSec); mpvPlayer.seek(skipForwardSec);
setCurrentTime(newTime); setCurrentTime(newTime);
@@ -147,7 +147,7 @@ export const useCenterControls = (args: { playersRef: any }) => {
(e: number | any) => { (e: number | any) => {
setCurrentTime(e); setCurrentTime(e);
if (settings.type === PlaybackType.Local) { if (settings.type === PlaybackType.LOCAL) {
mpvPlayer.seekTo(e); mpvPlayer.seekTo(e);
} else { } else {
currentPlayerRef.seekTo(e); currentPlayerRef.seekTo(e);
@@ -1,23 +1,19 @@
import { api } from '@/renderer/api';
import { Item, Play } from '../../../../types'; import { Item, Play } from '../../../../types';
import { albumsApi } from '../../../api/albumsApi'; import { useAuthStore, usePlayerStore } from '../../../store';
import { usePlayerStore } from '../../../store';
import {
getJellyfinStreamUrl,
getServerFolderAuth,
getSubsonicStreamUrl,
} from '../../../utils';
import { mpvPlayer } from '../utils/mpvPlayer'; import { mpvPlayer } from '../utils/mpvPlayer';
const getEndpointByItemType = (item: Item) => { const getEndpointByItemType = (item: Item) => {
switch (item) { switch (item) {
case Item.ALBUM: case Item.ALBUM:
return albumsApi.getAlbum; return api.albums.getAlbumDetail;
default: default:
return albumsApi.getAlbum; return api.albums.getAlbumDetail;
} }
}; };
export const usePlayQueueHandler = () => { export const usePlayQueueHandler = () => {
const serverId = useAuthStore((state) => state.currentServer?.id) || '';
const addToQueue = usePlayerStore((state) => state.addToQueue); const addToQueue = usePlayerStore((state) => state.addToQueue);
const handlePlayQueueAdd = async (options: { const handlePlayQueueAdd = async (options: {
@@ -35,36 +31,42 @@ export const usePlayQueueHandler = () => {
if (options.byItemType) { if (options.byItemType) {
const deviceId = localStorage.getItem('device_id'); const deviceId = localStorage.getItem('device_id');
const { serverUrl } = JSON.parse( // const { state } = JSON.parse(
localStorage.getItem('authentication') || '{}' // localStorage.getItem('authentication') || '{}'
); // );
if (deviceId) { if (deviceId) {
const endpoint = getEndpointByItemType(options.byItemType.type); const endpoint = getEndpointByItemType(options.byItemType.type);
const { data } = await endpoint({ const { data } = await endpoint({
id: options.byItemType.id, albumId: options.byItemType.id,
serverId,
}); });
const songs = data.songs.map((song) => { const songs = data.songs?.map((song) => {
const auth = getServerFolderAuth(serverUrl, song.serverFolderId); // const auth = getServerFolderAuth(
// state.serverUrl,
// song.serverFolderId
// );
if (auth) { // if (auth) {
const streamUrl = // const streamUrl =
auth.type === 'jellyfin' // auth.type === 'jellyfin'
? getJellyfinStreamUrl(auth, song, deviceId) // ? getJellyfinStreamUrl(auth, song, deviceId)
: getSubsonicStreamUrl(auth, song, deviceId); // : getSubsonicStreamUrl(auth, song, deviceId);
return { // return {
...song, // ...song,
streamUrl, // streamUrl,
}; // };
} // }
return song; return song;
}); });
const playerData = addToQueue(songs, options.play); const playerData = addToQueue(songs || [], options.play);
console.log('playerData', playerData);
if (options.play === Play.NEXT || options.play === Play.LAST) { if (options.play === Play.NEXT || options.play === Play.LAST) {
mpvPlayer.setQueueNext(playerData); mpvPlayer.setQueueNext(playerData);
+4 -4
View File
@@ -1,4 +1,4 @@
export * from './components/CenterControls'; export * from './components/center-controls';
export * from './components/LeftControls'; export * from './components/left-controls';
export * from './components/Playerbar'; export * from './components/playerbar';
export * from './components/Slider'; export * from './components/slider';
@@ -1,6 +1,6 @@
import { ReactNode } from 'react'; import { ReactNode } from 'react';
import styled from '@emotion/styled';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import styled from 'styled-components';
interface AnimatedPageProps { interface AnimatedPageProps {
children: ReactNode; children: ReactNode;
@@ -0,0 +1,22 @@
import { useMemo } from 'react';
import { useAuthStore } from '@/renderer/store';
export const usePermissions = () => {
const permissions = useAuthStore((state) => state.permissions);
const permissionSet = useMemo(() => {
const set = {
createServer: permissions.isAdmin,
createServerCredential: true,
createServerUrl: permissions.isAdmin,
deleteServer: permissions.isAdmin,
deleteServerCredential: true,
deleteServerUrl: permissions.isAdmin,
editServer: permissions.isAdmin,
};
return set;
}, [permissions]);
return permissionSet;
};
+2
View File
@@ -0,0 +1,2 @@
export * from './components/animated-page';
export * from './hooks/use-permissions';
@@ -1,11 +1,11 @@
import styled from '@emotion/styled';
import { import {
RiDashboardFill, RiDashboardFill,
RiFileList2Fill, RiFileList2Fill,
RiSearch2Fill, RiSearch2Line,
} from 'react-icons/ri'; } from 'react-icons/ri';
import styled from 'styled-components'; import { AppRoute } from '../../../router/routes';
import { AppRoute } from '../../../router/utils/routes'; import { ListItem } from './list-item';
import { ListItem } from './ListItem';
const StyledSidebar = styled.div``; const StyledSidebar = styled.div``;
@@ -19,15 +19,15 @@ export const Sidebar = () => {
</ListItem.Link> </ListItem.Link>
</ListItem> </ListItem>
<ListItem> <ListItem>
<ListItem.Link to={AppRoute.SEARCH}> <ListItem.Link to={AppRoute.LIBRARY}>
<RiSearch2Fill size={20} /> <RiFileList2Fill size={20} />
Search Library
</ListItem.Link> </ListItem.Link>
</ListItem> </ListItem>
<ListItem> <ListItem>
<ListItem.Link to={AppRoute.LIBRARY}> <ListItem.Link to={AppRoute.SEARCH}>
<RiFileList2Fill size={20} /> <RiSearch2Line size={20} />
Your Library Search
</ListItem.Link> </ListItem.Link>
</ListItem> </ListItem>
</StyledSidebar> </StyledSidebar>
@@ -1,7 +1,8 @@
import { ReactNode } from 'react'; import { ReactNode } from 'react';
import { css } from '@emotion/react';
import styled from '@emotion/styled';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { Link, LinkProps } from 'react-router-dom'; import { Link, LinkProps } from 'react-router-dom';
import styled, { css } from 'styled-components';
import { fontInter } from '../../../styles'; import { fontInter } from '../../../styles';
interface ListItemProps { interface ListItemProps {
+1 -1
View File
@@ -1 +1 @@
export * from './components/Sidebar'; export * from './components/sidebar';
+2 -2
View File
@@ -1,9 +1,9 @@
import { QueryClientProvider } from '@tanstack/react-query';
import { createRoot } from 'react-dom/client'; import { createRoot } from 'react-dom/client';
import { I18nextProvider } from 'react-i18next'; import { I18nextProvider } from 'react-i18next';
import { QueryClientProvider } from 'react-query'; import { queryClient } from '@/renderer/lib/react-query';
import i18n from '../i18n/i18n'; import i18n from '../i18n/i18n';
import { App } from './app'; import { App } from './app';
import { queryClient } from './lib';
const container = document.getElementById('root')!; const container = document.getElementById('root')!;
const root = createRoot(container); const root = createRoot(container);
+1 -1
View File
@@ -1,4 +1,4 @@
import { AppRoute } from './router/utils/routes'; import { AppRoute } from './router/routes';
export interface CardRow { export interface CardRow {
align?: 'left' | 'center' | 'right'; align?: 'left' | 'center' | 'right';
+7
View File
@@ -0,0 +1,7 @@
import '@emotion/react';
import type { MantineTheme } from '@mantine/core';
declare module '@emotion/react' {
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface GREY extends MantineTheme {}
}