Massive Category overhaul.

Many new categories added, All categories have an "Other" field that is sorted last

Media category removed, its subcategories are now main categories. WARNING: This is a breaking change that will disrupt categories for some currently published files

Categories associated with Copyright Infringement have been removed.

Categories are sorted by name, "Other" is always last

The FileList component now only stores the file list. The rest of it was moved to Home.tsx

Category <select> components moved into a CategoryList.tsx component

Icons now load before file title in FileContent.tsx

Icons are an optional field of each category and loaded dynamically from Category Data to improve performance
This commit is contained in:
2024-03-11 09:22:17 -06:00
parent fa0a1dc16a
commit 9d483cf65d
40 changed files with 3131 additions and 3382 deletions

View File

@@ -1,326 +1,44 @@
import React, { useCallback, useEffect, useRef, useState } from "react";
import { useNavigate } from "react-router-dom";
import ReactDOM from "react-dom";
import { useSelector, useDispatch } from "react-redux";
import { RootState } from "../../state/store";
import AttachFileIcon from '@mui/icons-material/AttachFile';
import {
Avatar,
Box,
Button,
FormControl,
Grid,
Input,
InputLabel,
MenuItem,
OutlinedInput,
Select,
SelectChangeEvent,
Skeleton,
Tooltip,
Typography,
useTheme,
} from "@mui/material";
import { useFetchFiles } from "../../hooks/useFetchFiles.tsx";
import LazyLoad from "../../components/common/LazyLoad";
import { Avatar, Box, Skeleton, Tooltip } from "@mui/material";
import {
BlockIconContainer,
BottomParent,
FilterSelect,
FiltersCheckbox,
FiltersCol,
FiltersContainer,
FiltersRow,
FiltersSubContainer,
FiltersTitle,
IconsBox,
NameContainer,
VideoCard,
VideoCardName,
VideoCardTitle,
VideoContainer,
FileContainer,
VideoUploadDate,
} from "./FileList-styles.tsx";
import ResponsiveImage from "../../components/ResponsiveImage";
import { formatDate, formatTimestampSeconds } from "../../utils/time";
import { Subtitle, SubtitleContainer } from "./Home-styles";
import { ExpandMoreSVG } from "../../assets/svgs/ExpandMoreSVG";
import EditIcon from "@mui/icons-material/Edit";
import {
addVideos,
blockUser,
changeFilterType,
changeSelectedCategoryVideos,
changeSelectedSubCategoryVideos,
changeSelectedSubCategoryVideos2,
changeSelectedSubCategoryVideos3,
changefilterName,
changefilterSearch,
clearVideoList,
setEditPlaylist,
setEditVideo,
} from "../../state/features/videoSlice";
import { Playlists } from "../../components/Playlists/Playlists";
import { PlaylistSVG } from "../../assets/svgs/PlaylistSVG";
setEditFile,
Video,
} from "../../state/features/fileSlice.ts";
import BlockIcon from "@mui/icons-material/Block";
import EditIcon from '@mui/icons-material/Edit';
import { formatBytes } from "../VideoContent/VideoContent";
import {categories, icons, subCategories, subCategories2, subCategories3} from "../../constants/Categories.ts";
import AttachFileIcon from "@mui/icons-material/AttachFile";
import { formatBytes } from "../FileContent/FileContent.tsx";
import { formatDate } from "../../utils/time.ts";
import React, { useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { RootState } from "../../state/store.ts";
import { useNavigate } from "react-router-dom";
import { getIconsFromObject } from "../../constants/Categories/CategoryFunctions.ts";
interface VideoListProps {
mode?: string;
interface FileListProps {
files: Video[];
}
export const FileList = ({ mode }: VideoListProps) => {
const theme = useTheme();
const prevVal = useRef("");
const isFiltering = useSelector(
(state: RootState) => state.video.isFiltering
export const FileList = ({ files }: FileListProps) => {
const hashMapFiles = useSelector(
(state: RootState) => state.file.hashMapFiles
);
const filterValue = useSelector(
(state: RootState) => state.video.filterValue
);
const [isLoading, setIsLoading] = useState<boolean>(false);
const [showIcons, setShowIcons] = useState(null);
const filterType = useSelector((state: RootState) => state.video.filterType);
const setFilterType = (payload) => {
dispatch(changeFilterType(payload));
};
const filterSearch = useSelector(
(state: RootState) => state.video.filterSearch
);
const setFilterSearch = (payload) => {
dispatch(changefilterSearch(payload));
};
const filterName = useSelector((state: RootState) => state.video.filterName);
const setFilterName = (payload) => {
dispatch(changefilterName(payload));
};
const selectedCategoryVideos = useSelector(
(state: RootState) => state.video.selectedCategoryVideos
);
const setSelectedCategoryVideos = (payload) => {
dispatch(changeSelectedCategoryVideos(payload));
};
const selectedSubCategoryVideos = useSelector(
(state: RootState) => state.video.selectedSubCategoryVideos
);
const selectedSubCategoryVideos2 = useSelector(
(state: RootState) => state.video.selectedSubCategoryVideos2
);
const selectedSubCategoryVideos3 = useSelector(
(state: RootState) => state.video.selectedSubCategoryVideos3
);
const setSelectedSubCategoryVideos = (payload) => {
dispatch(changeSelectedSubCategoryVideos(payload));
};
const setSelectedSubCategoryVideos2 = (payload) => {
dispatch(changeSelectedSubCategoryVideos2(payload));
};
const setSelectedSubCategoryVideos3 = (payload) => {
dispatch(changeSelectedSubCategoryVideos3(payload));
};
const dispatch = useDispatch();
const filteredVideos = useSelector(
(state: RootState) => state.video.filteredVideos
);
const username = useSelector((state: RootState) => state.auth?.user?.name);
const isFilterMode = useRef(false);
const firstFetch = useRef(false);
const afterFetch = useRef(false);
const isFetchingFiltered = useRef(false);
const isFetching = useRef(false);
const hashMapVideos = useSelector(
(state: RootState) => state.video.hashMapVideos
);
const countNewVideos = useSelector(
(state: RootState) => state.video.countNewVideos
);
const userAvatarHash = useSelector(
(state: RootState) => state.global.userAvatarHash
);
const { videos: globalVideos } = useSelector(
(state: RootState) => state.video
);
const dispatch = useDispatch();
const navigate = useNavigate();
const { getFiles, getNewFiles, checkNewFiles, getFilesFiltered } =
useFetchFiles();
const getFilesHandler = React.useCallback(
async (reset?: boolean, resetFilers?: boolean) => {
if (!firstFetch.current || !afterFetch.current) return;
if (isFetching.current) return;
isFetching.current = true;
console.log({
category: selectedCategoryVideos?.id,
subcategory: selectedSubCategoryVideos?.id,
subcategory2: selectedSubCategoryVideos2?.id,
subcategory3: selectedSubCategoryVideos3?.id,
})
await getFiles(
{
name: filterName,
category: selectedCategoryVideos?.id,
subcategory: selectedSubCategoryVideos?.id,
subcategory2: selectedSubCategoryVideos2?.id,
subcategory3: selectedSubCategoryVideos3?.id,
keywords: filterSearch,
type: filterType,
},
reset ? true : false,
resetFilers
);
isFetching.current = false;
},
[
getFiles,
filterValue,
getFilesFiltered,
isFiltering,
filterName,
selectedCategoryVideos,
selectedSubCategoryVideos,
selectedSubCategoryVideos2,
selectedSubCategoryVideos3,
filterSearch,
filterType,
]
);
const searchOnEnter = e => {
if (e.keyCode == 13) {
getFilesHandler(true);
}
};
useEffect(() => {
if (isFiltering && filterValue !== prevVal?.current) {
prevVal.current = filterValue;
getFilesHandler();
}
}, [filterValue, isFiltering, filteredVideos]);
const getFilesHandlerMount = React.useCallback(async () => {
if (firstFetch.current) return;
firstFetch.current = true;
setIsLoading(true);
await getFiles();
afterFetch.current = true;
isFetching.current = false;
setIsLoading(false);
}, [getFiles]);
let videos = globalVideos;
if (isFiltering) {
videos = filteredVideos;
isFilterMode.current = true;
} else {
isFilterMode.current = false;
}
// const interval = useRef<any>(null);
// const checkNewVideosFunc = useCallback(() => {
// let isCalling = false;
// interval.current = setInterval(async () => {
// if (isCalling || !firstFetch.current) return;
// isCalling = true;
// await checkNewVideos();
// isCalling = false;
// }, 30000); // 1 second interval
// }, [checkNewVideos]);
// useEffect(() => {
// if (isFiltering && interval.current) {
// clearInterval(interval.current);
// return;
// }
// checkNewVideosFunc();
// return () => {
// if (interval?.current) {
// clearInterval(interval.current);
// }
// };
// }, [mode, checkNewVideosFunc, isFiltering]);
useEffect(() => {
if (
!firstFetch.current &&
!isFilterMode.current &&
globalVideos.length === 0
) {
isFetching.current = true;
getFilesHandlerMount();
} else {
firstFetch.current = true;
afterFetch.current = true;
}
}, [getFilesHandlerMount, globalVideos]);
const filtersToDefault = async () => {
setFilterType("videos");
setFilterSearch("");
setFilterName("");
setSelectedCategoryVideos(null);
setSelectedSubCategoryVideos(null);
ReactDOM.flushSync(() => {
getFilesHandler(true, true);
});
};
const handleOptionCategoryChangeVideos = (
event: SelectChangeEvent<string>
) => {
const optionId = event.target.value;
const selectedOption = categories.find((option) => option.id === +optionId);
setSelectedCategoryVideos(selectedOption || null);
};
const handleOptionSubCategoryChangeVideos = (
event: SelectChangeEvent<string>,
subcategories: any[]
) => {
const optionId = event.target.value;
const selectedOption = subcategories.find(
(option) => option.id === +optionId
);
setSelectedSubCategoryVideos(selectedOption || null);
};
const handleOptionSubCategoryChangeVideos2 = (
event: SelectChangeEvent<string>,
subcategories: any[]
) => {
const optionId = event.target.value;
const selectedOption = subcategories.find(
(option) => option.id === +optionId
);
setSelectedSubCategoryVideos2(selectedOption || null);
};
const handleOptionSubCategoryChangeVideos3 = (
event: SelectChangeEvent<string>,
subcategories: any[]
) => {
const optionId = event.target.value;
const selectedOption = subcategories.find(
(option) => option.id === +optionId
);
setSelectedSubCategoryVideos3(selectedOption || null);
};
const blockUserFunc = async (user: string) => {
if (user === "Q-Share") return;
@@ -332,520 +50,158 @@ export const FileList = ({ mode }: VideoListProps) => {
});
if (response === true) {
dispatch(blockUser(user))
dispatch(blockUser(user));
}
} catch (error) {}
};
return (
<Grid container sx={{ width: "100%" }}>
<FiltersCol item xs={12} md={2} sm={3}>
<FiltersContainer>
<Input
id="standard-adornment-name"
onChange={(e) => {
setFilterSearch(e.target.value);
}}
onKeyDown={searchOnEnter}
value={filterSearch}
placeholder="Search"
<FileContainer>
{files.map((file: any, index: number) => {
const existingFile = hashMapFiles[file?.id];
let hasHash = false;
let fileObj = file;
if (existingFile) {
fileObj = existingFile;
hasHash = true;
}
const icon = getIconsFromObject(fileObj);
return (
<Box
sx={{
borderBottom: "1px solid white",
"&&:before": {
borderBottom: "none",
},
"&&:after": {
borderBottom: "none",
},
"&&:hover:before": {
borderBottom: "none",
},
"&&.Mui-focused:before": {
borderBottom: "none",
},
"&&.Mui-focused": {
outline: "none",
},
fontSize: "18px",
}}
/>
<Input
id="standard-adornment-name"
onChange={(e) => {
setFilterName(e.target.value);
}}
onKeyDown={searchOnEnter}
value={filterName}
placeholder="User's Name (Exact)"
sx={{
marginTop: "20px",
borderBottom: "1px solid white",
"&&:before": {
borderBottom: "none",
},
"&&:after": {
borderBottom: "none",
},
"&&:hover:before": {
borderBottom: "none",
},
"&&.Mui-focused:before": {
borderBottom: "none",
},
"&&.Mui-focused": {
outline: "none",
},
fontSize: "18px",
}}
/>
<FiltersTitle>
Categories
<ExpandMoreSVG
color={theme.palette.text.primary}
height={"22"}
width={"22"}
/>
</FiltersTitle>
<FiltersSubContainer>
<FormControl sx={{ width: "100%" }}>
<Box
sx={{
display: "flex",
gap: "20px",
alignItems: "center",
flexDirection: "column",
}}
>
<FormControl fullWidth sx={{ marginBottom: 1 }}>
<InputLabel
sx={{
fontSize: "16px",
}}
id="Category"
>
Category
</InputLabel>
<Select
labelId="Category"
input={<OutlinedInput label="Category" />}
value={selectedCategoryVideos?.id || ""}
onChange={handleOptionCategoryChangeVideos}
sx={{
// Target the input field
".MuiSelect-select": {
fontSize: "16px", // Change font size for the selected value
padding: "10px 5px 15px 15px;",
},
// Target the dropdown icon
".MuiSelect-icon": {
fontSize: "20px", // Adjust if needed
},
// Target the dropdown menu
"& .MuiMenu-paper": {
".MuiMenuItem-root": {
fontSize: "14px", // Change font size for the menu items
},
},
}}
>
{categories.map((option) => (
<MenuItem key={option.id} value={option.id}>
{option.name}
</MenuItem>
))}
</Select>
</FormControl>
{selectedCategoryVideos &&
subCategories[selectedCategoryVideos?.id] && (
<FormControl fullWidth sx={{ marginBottom: 2 }}>
<InputLabel
sx={{
fontSize: "16px",
}}
id="Sub-Category"
>
Sub-Category
</InputLabel>
<Select
labelId="Sub-Category"
input={<OutlinedInput label="Sub-Category" />}
value={selectedSubCategoryVideos?.id || ""}
onChange={(e) =>
handleOptionSubCategoryChangeVideos(
e,
subCategories[selectedCategoryVideos?.id]
)
}
sx={{
// Target the input field
".MuiSelect-select": {
fontSize: "16px", // Change font size for the selected value
padding: "10px 5px 15px 15px;",
},
// Target the dropdown icon
".MuiSelect-icon": {
fontSize: "20px", // Adjust if needed
},
// Target the dropdown menu
"& .MuiMenu-paper": {
".MuiMenuItem-root": {
fontSize: "14px", // Change font size for the menu items
},
},
}}
>
{subCategories[selectedCategoryVideos.id].map(
(option) => (
<MenuItem key={option.id} value={option.id}>
{option.name}
</MenuItem>
)
)}
</Select>
</FormControl>
)}
{selectedSubCategoryVideos &&
subCategories2[selectedSubCategoryVideos?.id] && (
<FormControl fullWidth sx={{ marginBottom: 2 }}>
<InputLabel
sx={{
fontSize: "16px",
}}
id="Sub-2x-Category"
>
Sub-2x-Category
</InputLabel>
<Select
labelId="Sub-2x-Category"
input={<OutlinedInput label="Sub-2x-Category" />}
value={selectedSubCategoryVideos2?.id || ""}
onChange={(e) =>
handleOptionSubCategoryChangeVideos2(
e,
subCategories2[selectedSubCategoryVideos?.id]
)
}
sx={{
// Target the input field
".MuiSelect-select": {
fontSize: "16px", // Change font size for the selected value
padding: "10px 5px 15px 15px;",
},
// Target the dropdown icon
".MuiSelect-icon": {
fontSize: "20px", // Adjust if needed
},
// Target the dropdown menu
"& .MuiMenu-paper": {
".MuiMenuItem-root": {
fontSize: "14px", // Change font size for the menu items
},
},
}}
>
{subCategories2[selectedSubCategoryVideos.id].map(
(option) => (
<MenuItem key={option.id} value={option.id}>
{option.name}
</MenuItem>
)
)}
</Select>
</FormControl>
)}
{selectedSubCategoryVideos2 &&
subCategories3[selectedSubCategoryVideos2?.id] && (
<FormControl fullWidth sx={{ marginBottom: 2 }}>
<InputLabel
sx={{
fontSize: "16px",
}}
id="Sub-2x-Category"
>
Sub-3x-Category
</InputLabel>
<Select
labelId="Sub-3x-Category"
input={<OutlinedInput label="Sub-sx-Category" />}
value={selectedSubCategoryVideos3?.id || ""}
onChange={(e) =>
handleOptionSubCategoryChangeVideos3(
e,
subCategories3[selectedSubCategoryVideos2?.id]
)
}
sx={{
// Target the input field
".MuiSelect-select": {
fontSize: "16px", // Change font size for the selected value
padding: "10px 5px 15px 15px;",
},
// Target the dropdown icon
".MuiSelect-icon": {
fontSize: "20px", // Adjust if needed
},
// Target the dropdown menu
"& .MuiMenu-paper": {
".MuiMenuItem-root": {
fontSize: "14px", // Change font size for the menu items
},
},
}}
>
{subCategories3[selectedSubCategoryVideos2.id].map(
(option) => (
<MenuItem key={option.id} value={option.id}>
{option.name}
</MenuItem>
)
)}
</Select>
</FormControl>
)}
</Box>
</FormControl>
</FiltersSubContainer>
{/* <FiltersTitle>
Type
<ExpandMoreSVG
color={theme.palette.text.primary}
height={"22"}
width={"22"}
/>
</FiltersTitle>
<FiltersSubContainer>
<FiltersRow>
Videos
<FiltersCheckbox
checked={filterType === "videos"}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setFilterType("videos");
}}
inputProps={{ "aria-label": "controlled" }}
/>
</FiltersRow>
<FiltersRow>
Playlists
<FiltersCheckbox
checked={filterType === "playlists"}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setFilterType("playlists");
}}
inputProps={{ "aria-label": "controlled" }}
/>
</FiltersRow>
</FiltersSubContainer> */}
<Button
onClick={() => {
filtersToDefault();
}}
sx={{
marginTop: "20px",
}}
variant="contained"
>
reset
</Button>
<Button
onClick={() => {
getFilesHandler(true);
}}
sx={{
marginTop: "20px",
}}
variant="contained"
>
Search
</Button>
</FiltersContainer>
</FiltersCol>
<Grid item xs={12} md={10} sm={9}>
<Box
sx={{
width: "100%",
display: "flex",
flexDirection: "column",
alignItems: "center",
marginTop: "20px",
}}
>
<SubtitleContainer
sx={{
justifyContent: "flex-start",
paddingLeft: "15px",
display: "flex",
alignItems: "center",
width: "100%",
maxWidth: "1400px",
height: "75px",
position: "relative",
}}
key={fileObj.id}
onMouseEnter={() => setShowIcons(fileObj.id)}
onMouseLeave={() => setShowIcons(null)}
>
</SubtitleContainer>
<VideoContainer>
{videos.map((video: any, index: number) => {
const existingVideo = hashMapVideos[video?.id];
let hasHash = false;
let videoObj = video;
if (existingVideo) {
videoObj = existingVideo;
hasHash = true;
}
const category = categories?.find(item => item?.id === videoObj?.category);
const subcategory = subCategories[category?.id]?.find(item => item?.id === videoObj?.subcategory);
const subcategory2 = subCategories2[subcategory?.id]?.find(item => item.id === videoObj?.subcategory2);
const subcategory3 = subCategories3[subcategory2?.id]?.find(item => item.id === videoObj?.subcategory3);
const catId = category?.id || null;
const subId = subcategory?.id || null;
const sub2Id = subcategory2?.id || null;
const sub3Id = subcategory3?.id || null;
const icon = icons[sub3Id] || icons[sub2Id] || icons[subId] || icons[catId] || null;
return (
<Box
{hasHash ? (
<>
<IconsBox
sx={{
display: "flex",
alignItems: "center",
width: "100%",
height: "75px",
position:"relative"
opacity: showIcons === fileObj.id ? 1 : 0,
zIndex: 2,
}}
key={videoObj.id}
onMouseEnter={() => setShowIcons(videoObj.id)}
onMouseLeave={() => setShowIcons(null)}
>
{hasHash ? (
<>
<IconsBox
sx={{
opacity: showIcons === videoObj.id ? 1 : 0,
zIndex: 2,
}}
>
{videoObj?.user === username && (
<Tooltip title="Edit video properties" placement="top">
<BlockIconContainer>
<EditIcon
onClick={() => {
dispatch(setEditVideo(videoObj));
}}
/>
</BlockIconContainer>
</Tooltip>
)}
<Tooltip title="Block user content" placement="top">
{fileObj?.user === username && (
<Tooltip title="Edit video properties" placement="top">
<BlockIconContainer>
<BlockIcon
<EditIcon
onClick={() => {
blockUserFunc(videoObj?.user);
dispatch(setEditFile(fileObj));
}}
/>
</BlockIconContainer>
</Tooltip>
</IconsBox>
<VideoCard
onClick={() => {
navigate(`/share/${videoObj?.user}/${videoObj?.id}`);
}}
)}
<Tooltip title="Block user content" placement="top">
<BlockIconContainer>
<BlockIcon
onClick={() => {
blockUserFunc(fileObj?.user);
}}
/>
</BlockIconContainer>
</Tooltip>
</IconsBox>
<VideoCard
onClick={() => {
navigate(`/share/${fileObj?.user}/${fileObj?.id}`);
}}
sx={{
height: "100%",
width: "100%",
display: "flex",
gap: "25px",
flexDirection: "row",
justifyContent: "space-between",
}}
>
<Box
sx={{
height: '100%',
width: '100%',
display: 'flex',
gap: '25px',
flexDirection: 'row',
justifyContent: 'space-between'
display: "flex",
gap: "25px",
alignItems: "center",
}}
>
<Box sx={{
display: 'flex',
gap: '25px',
alignItems: 'center'
}}>
{icon ? <img src={icon} width="50px" style={{
borderRadius: '5px'
}}/> : (
<AttachFileIcon />
{icon ? (
<img
src={icon}
width="50px"
style={{
borderRadius: "5px",
}}
/>
) : (
<AttachFileIcon />
)}
<VideoCardTitle
sx={{
width: "100px",
}}
>
{formatBytes(
fileObj?.files.reduce(
(acc, cur) => acc + (cur?.size || 0),
0
)
)}
<VideoCardTitle sx={{
width: '100px'
}}>
{formatBytes(videoObj?.files.reduce((acc, cur) => acc + (cur?.size || 0), 0))}
</VideoCardTitle>
<VideoCardTitle>{videoObj.title}</VideoCardTitle>
</Box>
<BottomParent>
<NameContainer
onClick={(e) => {
e.stopPropagation();
navigate(`/channel/${videoObj?.user}`);
</VideoCardTitle>
<VideoCardTitle>{fileObj.title}</VideoCardTitle>
</Box>
<BottomParent>
<NameContainer
onClick={e => {
e.stopPropagation();
navigate(`/channel/${fileObj?.user}`);
}}
>
<Avatar
sx={{ height: 24, width: 24 }}
src={`/arbitrary/THUMBNAIL/${fileObj?.user}/qortal_avatar`}
alt={`${fileObj?.user}'s avatar`}
/>
<VideoCardName
sx={{
":hover": {
textDecoration: "underline",
},
}}
>
<Avatar
sx={{ height: 24, width: 24 }}
src={`/arbitrary/THUMBNAIL/${videoObj?.user}/qortal_avatar`}
alt={`${videoObj?.user}'s avatar`}
/>
<VideoCardName
sx={{
":hover": {
textDecoration: "underline",
},
}}
>
{videoObj?.user}
</VideoCardName>
</NameContainer>
{fileObj?.user}
</VideoCardName>
</NameContainer>
{videoObj?.created && (
<VideoUploadDate>
{formatDate(videoObj.created)}
</VideoUploadDate>
)}
</BottomParent>
</VideoCard>
</>
) : (
<Skeleton
variant="rectangular"
style={{
width: "100%",
height: "100%",
paddingBottom: "10px",
objectFit: "contain",
visibility: "visible",
borderRadius: "8px",
}}
/>
)}
</Box>
);
})}
</VideoContainer>
<LazyLoad
onLoadMore={getFilesHandler}
isLoading={isLoading}
></LazyLoad>
</Box>
</Grid>
</Grid>
{fileObj?.created && (
<VideoUploadDate>
{formatDate(fileObj.created)}
</VideoUploadDate>
)}
</BottomParent>
</VideoCard>
</>
) : (
<Skeleton
variant="rectangular"
style={{
width: "100%",
height: "100%",
paddingBottom: "10px",
objectFit: "contain",
visibility: "visible",
borderRadius: "8px",
}}
/>
)}
</Box>
);
})}
</FileContainer>
);
};