diff --git a/frontend/package.json b/frontend/package.json index 1c22cc7d7..0527762f6 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -37,6 +37,7 @@ "@dnd-kit/core": "6.1.0", "@emotion/react": "11.13.0", "@emotion/styled": "11.13.0", + "@fortawesome/free-regular-svg-icons": "6.6.0", "@fortawesome/free-solid-svg-icons": "6.6.0", "@fortawesome/react-fontawesome": "0.2.2", "@reduxjs/toolkit": "2.2.7", diff --git a/frontend/src/bundles/studio/components/audio-player/audio-player.tsx b/frontend/src/bundles/common/components/audio-player/audio-player.tsx similarity index 92% rename from frontend/src/bundles/studio/components/audio-player/audio-player.tsx rename to frontend/src/bundles/common/components/audio-player/audio-player.tsx index 314dc5300..614209cc3 100644 --- a/frontend/src/bundles/studio/components/audio-player/audio-player.tsx +++ b/frontend/src/bundles/common/components/audio-player/audio-player.tsx @@ -14,7 +14,7 @@ type Properties = { isPlaying: boolean; audioUrl: string; onAudioEnd: () => void; - onSetDuration: (duration: number) => void; + onSetDuration?: (duration: number) => void; }; const AudioPlayer: React.FC = ({ @@ -41,7 +41,9 @@ const AudioPlayer: React.FC = ({ getAudioData(audioUrl) .then(({ durationInSeconds }) => { setDurationInFrames(Math.round(durationInSeconds * FPS)); - onSetDuration(durationInSeconds); + if (onSetDuration) { + onSetDuration(durationInSeconds); + } }) .catch(() => { setDurationInFrames(1); diff --git a/frontend/src/bundles/studio/components/audio-player/constants/constants.ts b/frontend/src/bundles/common/components/audio-player/constants/constants.ts similarity index 100% rename from frontend/src/bundles/studio/components/audio-player/constants/constants.ts rename to frontend/src/bundles/common/components/audio-player/constants/constants.ts diff --git a/frontend/src/bundles/studio/components/audio-player/enums/audio-event.ts b/frontend/src/bundles/common/components/audio-player/enums/audio-event.ts similarity index 100% rename from frontend/src/bundles/studio/components/audio-player/enums/audio-event.ts rename to frontend/src/bundles/common/components/audio-player/enums/audio-event.ts diff --git a/frontend/src/bundles/studio/components/audio-player/enums/enums.ts b/frontend/src/bundles/common/components/audio-player/enums/enums.ts similarity index 100% rename from frontend/src/bundles/studio/components/audio-player/enums/enums.ts rename to frontend/src/bundles/common/components/audio-player/enums/enums.ts diff --git a/frontend/src/bundles/common/components/components.ts b/frontend/src/bundles/common/components/components.ts index 3f948daf2..9d938499a 100644 --- a/frontend/src/bundles/common/components/components.ts +++ b/frontend/src/bundles/common/components/components.ts @@ -1,3 +1,4 @@ +export { AudioPlayer } from './audio-player/audio-player.js'; export { Button } from './button/button.js'; export { ComponentsProvider } from './components-provider/components-provider.js'; export { Header } from './header/header.js'; diff --git a/frontend/src/bundles/common/components/sidebar/sidebar.tsx b/frontend/src/bundles/common/components/sidebar/sidebar.tsx index 92c8eb02e..cef1e94df 100644 --- a/frontend/src/bundles/common/components/sidebar/sidebar.tsx +++ b/frontend/src/bundles/common/components/sidebar/sidebar.tsx @@ -6,6 +6,7 @@ import { IconButton, Link, Spacer, + Text, } from '~/bundles/common/components/components.js'; import { AppRoute } from '~/bundles/common/enums/enums.js'; import { @@ -119,6 +120,29 @@ const Sidebar: React.FC = ({ children }) => { label="Templates" /> + {isCollapsed ? ( + + Assets + + ) : ( + + Assets + + )} + + + } + isCollapsed={isCollapsed} + label="AI Voices" + /> + + ), +}); + +export { Voice }; diff --git a/frontend/src/bundles/common/icons/helper/icon-conversion.helper.ts b/frontend/src/bundles/common/icons/helper/icon-conversion.helper.ts index 75d8c3b94..1dfe8d66f 100644 --- a/frontend/src/bundles/common/icons/helper/icon-conversion.helper.ts +++ b/frontend/src/bundles/common/icons/helper/icon-conversion.helper.ts @@ -1,3 +1,4 @@ +import { faHeart as faHeartRegular } from '@fortawesome/free-regular-svg-icons'; import { faBackwardStep, faCircle, @@ -8,6 +9,7 @@ import { faFileLines, faFont, faForwardStep, + faHeart, faHouse, faImage, faMagnifyingGlass, @@ -48,6 +50,8 @@ const Stop = convertIcon(faStop); const VideoCamera = convertIcon(faVideoCamera); const Image = convertIcon(faImage); const Circle = convertIcon(faCircle); +const HeartFill = convertIcon(faHeart); +const HeartOutline = convertIcon(faHeartRegular); const Magnifying = convertIcon(faMagnifyingGlass); export { @@ -60,6 +64,8 @@ export { FileLines, Font, ForwardStep, + HeartFill, + HeartOutline, House, Image, Magnifying, diff --git a/frontend/src/bundles/common/icons/icon-name.ts b/frontend/src/bundles/common/icons/icon-name.ts index 644059bf9..0230f26c6 100644 --- a/frontend/src/bundles/common/icons/icon-name.ts +++ b/frontend/src/bundles/common/icons/icon-name.ts @@ -13,7 +13,7 @@ import { WarningIcon, } from '@chakra-ui/icons'; -import { Logo, LogoText, OpenAi } from './custom-icons/custom-icons.js'; +import { Logo, LogoText, OpenAi, Voice } from './custom-icons/custom-icons.js'; import { BackwardStep, Circle, @@ -24,6 +24,8 @@ import { FileLines, Font, ForwardStep, + HeartFill, + HeartOutline, House, Image, Magnifying, @@ -78,6 +80,9 @@ const IconName = { IMAGE: Image, CIRCLE: Circle, ARROW_BACK: ArrowBackIcon, + VOICE: Voice, + HEART_FILL: HeartFill, + HEART_OUTLINE: HeartOutline, MAGNIFYING: Magnifying, } as const; diff --git a/frontend/src/bundles/home/store/actions.ts b/frontend/src/bundles/home/store/actions.ts index 56bd864c7..77de9acb8 100644 --- a/frontend/src/bundles/home/store/actions.ts +++ b/frontend/src/bundles/home/store/actions.ts @@ -1,7 +1,12 @@ import { createAsyncThunk } from '@reduxjs/toolkit'; import { type AsyncThunkConfig } from '~/bundles/common/types/types.js'; -import { type VideoGetAllResponseDto } from '~/bundles/home/types/types.js'; +import { + type GenerateSpeechRequestDto, + type GenerateSpeechResponseDto, + type GetVoicesResponseDto, + type VideoGetAllResponseDto, +} from '~/bundles/home/types/types.js'; import { name as sliceName } from './slice.js'; @@ -24,4 +29,24 @@ const deleteVideo = createAsyncThunk, string, AsyncThunkConfig>( }, ); -export { deleteVideo, loadUserVideos }; +const loadVoices = createAsyncThunk< + GetVoicesResponseDto, + undefined, + AsyncThunkConfig +>(`${sliceName}/load-voices`, (_, { extra }) => { + const { speechApi } = extra; + + return speechApi.loadVoices(); +}); + +const generateScriptSpeechPreview = createAsyncThunk< + GenerateSpeechResponseDto, + GenerateSpeechRequestDto, + AsyncThunkConfig +>(`${sliceName}/generate-script-speech-preview`, (payload, { extra }) => { + const { speechApi } = extra; + + return speechApi.generateScriptSpeech(payload); +}); + +export { deleteVideo, generateScriptSpeechPreview, loadUserVideos, loadVoices }; diff --git a/frontend/src/bundles/home/store/home.ts b/frontend/src/bundles/home/store/home.ts index 9b26c21f9..b7ad69cfe 100644 --- a/frontend/src/bundles/home/store/home.ts +++ b/frontend/src/bundles/home/store/home.ts @@ -1,10 +1,15 @@ -import { deleteVideo, loadUserVideos } from './actions.js'; +import { + deleteVideo, + generateScriptSpeechPreview, + loadUserVideos, +} from './actions.js'; import { actions } from './slice.js'; const allActions = { ...actions, deleteVideo, loadUserVideos, + generateScriptSpeechPreview, }; export { reducer } from './slice.js'; diff --git a/frontend/src/bundles/home/store/slice.ts b/frontend/src/bundles/home/store/slice.ts index 3c185a756..caa5e1eab 100644 --- a/frontend/src/bundles/home/store/slice.ts +++ b/frontend/src/bundles/home/store/slice.ts @@ -1,25 +1,53 @@ +import { type PayloadAction } from '@reduxjs/toolkit'; import { createSlice } from '@reduxjs/toolkit'; import { DataStatus } from '~/bundles/common/enums/enums.js'; import { type ValueOf } from '~/bundles/common/types/types.js'; -import { type VideoGetAllItemResponseDto } from '~/bundles/home/types/types.js'; +import { + type VideoGetAllItemResponseDto, + type Voice, +} from '~/bundles/home/types/types.js'; -import { deleteVideo, loadUserVideos } from './actions.js'; +import { deleteVideo, loadUserVideos, loadVoices } from './actions.js'; + +type VoicePlayer = { + isPlaying: boolean; + url: string | null; +}; type State = { dataStatus: ValueOf; videos: Array | []; + voices: Voice[]; + voicePlayer: VoicePlayer; }; const initialState: State = { dataStatus: DataStatus.IDLE, videos: [], + voices: [], + voicePlayer: { + isPlaying: false, + url: null, + }, }; const { reducer, actions, name } = createSlice({ initialState, name: 'home', - reducers: {}, + reducers: { + toogleVoiceLike(state, action: PayloadAction) { + state.voices = state.voices.map((voice) => { + const { shortName, isLiked } = voice; + return shortName === action.payload + ? { ...voice, isLiked: !isLiked } + : voice; + }); + }, + playVoice(state, action: PayloadAction>) { + state.voicePlayer = { ...state.voicePlayer, ...action.payload }; + }, + }, extraReducers(builder) { builder.addCase(loadUserVideos.pending, (state) => { state.dataStatus = DataStatus.PENDING; @@ -44,6 +72,20 @@ const { reducer, actions, name } = createSlice({ builder.addCase(deleteVideo.rejected, (state) => { state.dataStatus = DataStatus.REJECTED; }); + builder.addCase(loadVoices.pending, (state) => { + state.dataStatus = DataStatus.PENDING; + }); + builder.addCase(loadVoices.fulfilled, (state, action) => { + state.voices = action.payload.items.map((voice) => ({ + ...voice, + isLiked: false, + })); + state.dataStatus = DataStatus.FULFILLED; + }); + builder.addCase(loadVoices.rejected, (state) => { + state.voices = []; + state.dataStatus = DataStatus.REJECTED; + }); }, }); diff --git a/frontend/src/bundles/home/types/types.ts b/frontend/src/bundles/home/types/types.ts index e79c0f579..3f0c442ff 100644 --- a/frontend/src/bundles/home/types/types.ts +++ b/frontend/src/bundles/home/types/types.ts @@ -1,4 +1,8 @@ +export { type Voice } from '~/bundles/home/types/voice.type.js'; export { + type GenerateSpeechRequestDto, + type GenerateSpeechResponseDto, + type GetVoicesResponseDto, type VideoGetAllItemResponseDto, type VideoGetAllResponseDto, } from 'shared'; diff --git a/frontend/src/bundles/home/types/voice.type.ts b/frontend/src/bundles/home/types/voice.type.ts new file mode 100644 index 000000000..c735f838c --- /dev/null +++ b/frontend/src/bundles/home/types/voice.type.ts @@ -0,0 +1,7 @@ +import { type Voice as SharedVoice } from 'shared'; + +type Voice = SharedVoice & { + isLiked: boolean; +}; + +export { type Voice }; diff --git a/frontend/src/bundles/studio/components/control/control.tsx b/frontend/src/bundles/studio/components/control/control.tsx index 93c1f7e57..3130c0024 100644 --- a/frontend/src/bundles/studio/components/control/control.tsx +++ b/frontend/src/bundles/studio/components/control/control.tsx @@ -11,6 +11,7 @@ type Properties = { label: string; size: IconSizeT; icon: ElementType; + iconColor?: string; onClick?: (event: React.MouseEvent) => void; width?: string; height?: string; @@ -22,6 +23,7 @@ const Control: React.FC = ({ label, size, icon, + iconColor, onClick = (): void => {}, width, height, @@ -37,7 +39,9 @@ const Control: React.FC = ({ {...(height && { height })} size={size} variant={variant} - icon={} + icon={ + + } onClick={onClick} /> diff --git a/frontend/src/bundles/studio/components/player-controls/player-controls.tsx b/frontend/src/bundles/studio/components/player-controls/player-controls.tsx index cd14ace55..ab9f1289b 100644 --- a/frontend/src/bundles/studio/components/player-controls/player-controls.tsx +++ b/frontend/src/bundles/studio/components/player-controls/player-controls.tsx @@ -2,6 +2,7 @@ import { type PlayerRef } from '@remotion/player'; import { secondsToMilliseconds } from 'date-fns'; import { type RefObject } from 'react'; +import { FPS } from '~/bundles/common/components/audio-player/constants/constants.js'; import { Flex, Spinner } from '~/bundles/common/components/components.js'; import { useAppDispatch, @@ -16,7 +17,6 @@ import { setItemsSpan } from '~/bundles/studio/helpers/set-items-span.js'; import { selectTotalDuration } from '~/bundles/studio/store/selectors.js'; import { actions as studioActions } from '~/bundles/studio/store/studio.js'; -import { FPS } from '../audio-player/constants/constants.js'; import { Control } from '../components.js'; import { TimeDisplay } from './components/components.js'; diff --git a/frontend/src/bundles/studio/pages/studio.tsx b/frontend/src/bundles/studio/pages/studio.tsx index 837916138..15dd38ece 100644 --- a/frontend/src/bundles/studio/pages/studio.tsx +++ b/frontend/src/bundles/studio/pages/studio.tsx @@ -1,5 +1,6 @@ import { type PlayerRef } from '@remotion/player'; +import { AudioPlayer } from '~/bundles/common/components/audio-player/audio-player.js'; import { Box, Button, @@ -30,7 +31,6 @@ import { import { IconName } from '~/bundles/common/icons/icons.js'; import { notificationService } from '~/bundles/common/services/services.js'; -import { AudioPlayer } from '../components/audio-player/audio-player.js'; import { PlayerControls, Timeline, diff --git a/frontend/src/bundles/voices/components/components.ts b/frontend/src/bundles/voices/components/components.ts new file mode 100644 index 000000000..e4a123065 --- /dev/null +++ b/frontend/src/bundles/voices/components/components.ts @@ -0,0 +1,3 @@ +export { MainContent } from './main-content/main-content.js'; +export { VoiceCard } from './voice-card/voice-card.js'; +export { VoiceSection } from './voice-section/voice-section.js'; diff --git a/frontend/src/bundles/voices/components/main-content/main-content.tsx b/frontend/src/bundles/voices/components/main-content/main-content.tsx new file mode 100644 index 000000000..884113766 --- /dev/null +++ b/frontend/src/bundles/voices/components/main-content/main-content.tsx @@ -0,0 +1,50 @@ +import { + Box, + Loader, + Overlay, +} from '~/bundles/common/components/components.js'; +import { useCollapse } from '~/bundles/common/components/sidebar/hooks/use-collapse.hook.js'; +import { DataStatus } from '~/bundles/common/enums/enums.js'; +import { + useAppDispatch, + useAppSelector, + useEffect, + useMemo, +} from '~/bundles/common/hooks/hooks.js'; +import { loadVoices } from '~/bundles/home/store/actions.js'; +import { VoiceSection } from '~/bundles/voices/components/components.js'; +import { VoicesSections } from '~/bundles/voices/enums/voices-sections.js'; + +import styles from './styles.module.css'; + +const MainContent: React.FC = () => { + const dispatch = useAppDispatch(); + const { isCollapsed } = useCollapse(); + + const { voices, dataStatus } = useAppSelector(({ home }) => home); + + const myVoices = useMemo( + () => voices.filter((voice) => voice.isLiked), + [voices], + ); + + useEffect(() => { + void dispatch(loadVoices()); + }, [dispatch]); + + return ( + + + + + + + + + ); +}; + +export { MainContent }; diff --git a/frontend/src/bundles/voices/components/main-content/styles.module.css b/frontend/src/bundles/voices/components/main-content/styles.module.css new file mode 100644 index 000000000..c0d57c638 --- /dev/null +++ b/frontend/src/bundles/voices/components/main-content/styles.module.css @@ -0,0 +1,6 @@ +.main-content { + background-color: var(--chakra-colors-background-50); + border-radius: var(--chakra-radii-lg); + height: calc(100vh - 75px); + overflow: auto; +} diff --git a/frontend/src/bundles/voices/components/voice-card/voice-card.tsx b/frontend/src/bundles/voices/components/voice-card/voice-card.tsx new file mode 100644 index 000000000..a7b766313 --- /dev/null +++ b/frontend/src/bundles/voices/components/voice-card/voice-card.tsx @@ -0,0 +1,117 @@ +import { v4 as uuidv4 } from 'uuid'; + +import { + Card, + CardBody, + HStack, + Spinner, + Text, +} from '~/bundles/common/components/components.js'; +import { + useAppDispatch, + useAppSelector, + useCallback, + useMemo, + useState, +} from '~/bundles/common/hooks/hooks.js'; +import { IconName, IconSize } from '~/bundles/common/icons/icons.js'; +import { actions as homeActions } from '~/bundles/home/store/home.js'; +import { type Voice } from '~/bundles/home/types/types.js'; +import { Control } from '~/bundles/studio/components/control/control.js'; +import { TEXT_FOR_VOICES } from '~/bundles/voices/constants/constants.js'; + +type Properties = { + voice: Voice; +}; + +const VoiceCard: React.FC = ({ voice }) => { + const [url, setUrl] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const dispatch = useAppDispatch(); + + const { isPlaying: playerIsPlaying, url: playerUrl } = useAppSelector( + ({ home }) => home.voicePlayer, + ); + + const isPlaying = useMemo( + () => playerIsPlaying && playerUrl === url, + [playerIsPlaying, playerUrl, url], + ); + const handlePlayClick = useCallback( + (event: React.MouseEvent): void => { + event.stopPropagation(); + if (isLoading) { + return; + } + if (url) { + dispatch(homeActions.playVoice({ isPlaying: !isPlaying, url })); + return; + } + setIsLoading(true); + void dispatch( + homeActions.generateScriptSpeechPreview({ + scriptId: uuidv4(), + text: TEXT_FOR_VOICES, + voiceName: voice.shortName, + }), + ) + .unwrap() + .then(({ audioUrl }) => { + setUrl(audioUrl); + setIsLoading(false); + dispatch( + homeActions.playVoice({ + isPlaying: true, + url: audioUrl, + }), + ); + }); + }, + [dispatch, url, voice, isPlaying, setUrl, setIsLoading, isLoading], + ); + const handleLikeClick = useCallback((): void => { + dispatch(homeActions.toogleVoiceLike(voice.shortName)); + }, [voice, dispatch]); + + const iconComponent = useMemo(() => { + if (isLoading) { + return Spinner; + } + if (!url) { + return IconName.DOWNLOAD; + } + return isPlaying ? IconName.STOP : IconName.PLAY; + }, [isPlaying, isLoading, url]); + + return ( + + + + + + {voice.name} + + + + + + ); +}; + +export { VoiceCard }; diff --git a/frontend/src/bundles/voices/components/voice-section/styles.module.css b/frontend/src/bundles/voices/components/voice-section/styles.module.css new file mode 100644 index 000000000..64d222997 --- /dev/null +++ b/frontend/src/bundles/voices/components/voice-section/styles.module.css @@ -0,0 +1,6 @@ +.horizontal { + display: flex; + overflow-x: auto; + scrollbar-width: thin; + scrollbar-color: #c1c1c1 #f1f1f1; +} diff --git a/frontend/src/bundles/voices/components/voice-section/voice-section.tsx b/frontend/src/bundles/voices/components/voice-section/voice-section.tsx new file mode 100644 index 000000000..051006891 --- /dev/null +++ b/frontend/src/bundles/voices/components/voice-section/voice-section.tsx @@ -0,0 +1,71 @@ +import { + Badge, + Box, + Flex, + Heading, + SimpleGrid, + Text, +} from '~/bundles/common/components/components.js'; +import { EMPTY_VALUE } from '~/bundles/common/constants/constants.js'; +import { type Voice } from '~/bundles/home/types/types.js'; +import { VoiceCard } from '~/bundles/voices/components/components.js'; +import { VoicesSections } from '~/bundles/voices/enums/voices-sections.js'; + +import styles from './styles.module.css'; + +type Properties = { + voices: Voice[]; + title: string; +}; + +const VoiceSection: React.FC = ({ voices, title }) => { + return ( + + + + {title} + + + {voices.length} + + + + {voices.length > EMPTY_VALUE ? ( + title === VoicesSections.MY_VOICES ? ( + + {voices.map((voice) => ( + + + + ))} + + ) : ( + + {voices.map((voice) => ( + + ))} + + ) + ) : ( + + You have no voices right now. + + )} + + ); +}; + +export { VoiceSection }; diff --git a/frontend/src/bundles/voices/constants/constants.ts b/frontend/src/bundles/voices/constants/constants.ts new file mode 100644 index 000000000..624b13d5f --- /dev/null +++ b/frontend/src/bundles/voices/constants/constants.ts @@ -0,0 +1,2 @@ +export { TEXT_FOR_VOICES } from './text-for-voices.constant.js'; +export { EMPTY_VALUE } from 'shared'; diff --git a/frontend/src/bundles/voices/constants/text-for-voices.constant.ts b/frontend/src/bundles/voices/constants/text-for-voices.constant.ts new file mode 100644 index 000000000..e6f13b4a6 --- /dev/null +++ b/frontend/src/bundles/voices/constants/text-for-voices.constant.ts @@ -0,0 +1,4 @@ +const TEXT_FOR_VOICES = + 'Hello, I can handle video speech for you, choose me if you like it!'; + +export { TEXT_FOR_VOICES }; diff --git a/frontend/src/bundles/voices/enums/voices-sections.ts b/frontend/src/bundles/voices/enums/voices-sections.ts new file mode 100644 index 000000000..261d97dfc --- /dev/null +++ b/frontend/src/bundles/voices/enums/voices-sections.ts @@ -0,0 +1,6 @@ +const VoicesSections = { + VOICES: 'OutreachVids Library', + MY_VOICES: 'My Voices', +} as const; + +export { VoicesSections }; diff --git a/frontend/src/bundles/voices/pages/voices.tsx b/frontend/src/bundles/voices/pages/voices.tsx new file mode 100644 index 000000000..1d5db4ea5 --- /dev/null +++ b/frontend/src/bundles/voices/pages/voices.tsx @@ -0,0 +1,41 @@ +import { + AudioPlayer, + Box, + Header, + Sidebar, +} from '~/bundles/common/components/components.js'; +import { + useAppDispatch, + useAppSelector, + useCallback, +} from '~/bundles/common/hooks/hooks.js'; +import { actions as homeActions } from '~/bundles/home/store/home.js'; +import { MainContent } from '~/bundles/voices/components/components.js'; + +const Voices: React.FC = () => { + const dispatch = useAppDispatch(); + const { isPlaying, url } = useAppSelector(({ home }) => home.voicePlayer); + const handleAudioEnd = useCallback((): void => { + dispatch(homeActions.playVoice({ isPlaying: false })); + }, [dispatch]); + + return ( + <> + +
+ + + + + {url && ( + + )} + + ); +}; + +export { Voices }; diff --git a/frontend/src/routes/routes.tsx b/frontend/src/routes/routes.tsx index 6db4541bb..eacee362f 100644 --- a/frontend/src/routes/routes.tsx +++ b/frontend/src/routes/routes.tsx @@ -8,6 +8,7 @@ import { Home } from '~/bundles/home/pages/home.js'; import { MyAvatar } from '~/bundles/my-avatar/pages/my-avatar.js'; import { Studio } from '~/bundles/studio/pages/studio.js'; import { Templates } from '~/bundles/template/pages/templates.js'; +import { Voices } from '~/bundles/voices/pages/voices.js'; const routes = [ { @@ -46,6 +47,14 @@ const routes = [ ), }, + { + path: AppRoute.VOICES, + element: ( + + + + ), + }, { path: AppRoute.CREATE_AVATAR, element: ( diff --git a/package-lock.json b/package-lock.json index c97c8bb5f..75034ef25 100644 --- a/package-lock.json +++ b/package-lock.json @@ -131,6 +131,7 @@ "@dnd-kit/core": "6.1.0", "@emotion/react": "11.13.0", "@emotion/styled": "11.13.0", + "@fortawesome/free-regular-svg-icons": "6.6.0", "@fortawesome/free-solid-svg-icons": "6.6.0", "@fortawesome/react-fontawesome": "0.2.2", "@reduxjs/toolkit": "2.2.7", @@ -9058,6 +9059,17 @@ "node": ">=6" } }, + "node_modules/@fortawesome/free-regular-svg-icons": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.6.0.tgz", + "integrity": "sha512-Yv9hDzL4aI73BEwSEh20clrY8q/uLxawaQ98lekBx6t9dQKDHcDzzV1p2YtBGTtolYtNqcWdniOnhzB+JPnQEQ==", + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.6.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/@fortawesome/free-solid-svg-icons": { "version": "6.6.0", "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.6.0.tgz",