3
0
mirror of https://github.com/Qortal/q-support.git synced 2025-02-11 17:55:50 +00:00

First Commit

Q-Support is forked from Q-Share and has the following changes from it:

Changed Q-Share Categories and Identifiers to Q-Support Categories and Identifiers.

Theme updated with main colors being the same as Qortal logo.

Added Support Icon

StatsData component only displays if published posts

Full Publish form shown before choosing file

User doesn't input Issue State Category in Publish Form because it is always open by default

Publishing File is Optional in publish form

User can add multiple images in PublishFile.tsx
This commit is contained in:
Qortal Dev 2024-04-15 16:15:07 -06:00
parent a3bce74ad8
commit da9bc5ab59
44 changed files with 1431 additions and 1057 deletions

854
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
{ {
"name": "qtube", "name": "qsupport",
"private": true, "private": true,
"version": "0.0.0", "version": "1.0.0",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
@ -43,6 +43,6 @@
"eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.3.4", "eslint-plugin-react-refresh": "^0.3.4",
"typescript": "^5.0.2", "typescript": "^5.0.2",
"vite": "^4.3.2" "vite": "6.0.0-alpha.1"
} }
} }

View File

@ -1,14 +1,14 @@
import { useState } from "react"; import { useState } from "react";
import { Routes, Route } from "react-router-dom"; import { Route, Routes } from "react-router-dom";
import { ThemeProvider } from "@mui/material/styles"; import { ThemeProvider } from "@mui/material/styles";
import { CssBaseline } from "@mui/material"; import { CssBaseline } from "@mui/material";
import { lightTheme, darkTheme } from "./styles/theme"; import { darkTheme, lightTheme } from "./styles/theme";
import { store } from "./state/store"; import { store } from "./state/store";
import { Provider } from "react-redux"; import { Provider } from "react-redux";
import GlobalWrapper from "./wrappers/GlobalWrapper"; import GlobalWrapper from "./wrappers/GlobalWrapper";
import Notification from "./components/common/Notification/Notification"; import Notification from "./components/common/Notification/Notification";
import { Home } from "./pages/Home/Home"; import { Home } from "./pages/Home/Home";
import { FileContent } from "./pages/FileContent/FileContent.tsx"; import { IssueContent } from "./pages/IssueContent/IssueContent.tsx";
import DownloadWrapper from "./wrappers/DownloadWrapper"; import DownloadWrapper from "./wrappers/DownloadWrapper";
import { IndividualProfile } from "./pages/IndividualProfile/IndividualProfile"; import { IndividualProfile } from "./pages/IndividualProfile/IndividualProfile";
@ -26,7 +26,7 @@ function App() {
<CssBaseline /> <CssBaseline />
<Routes> <Routes>
<Route path="/" element={<Home />} /> <Route path="/" element={<Home />} />
<Route path="/share/:name/:id" element={<FileContent />} /> <Route path="/share/:name/:id" element={<IssueContent />} />
<Route path="/channel/:name" element={<IndividualProfile />} /> <Route path="/channel/:name" element={<IndividualProfile />} />
</Routes> </Routes>
</GlobalWrapper> </GlobalWrapper>

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

View File

@ -21,7 +21,7 @@ import {
updateFile, updateFile,
updateInHashMap, updateInHashMap,
} from "../../state/features/fileSlice.ts"; } from "../../state/features/fileSlice.ts";
import { QSHARE_FILE_BASE } from "../../constants/Identifiers.ts"; import { QSUPPORT_FILE_BASE } from "../../constants/Identifiers.ts";
import { MultiplePublish } from "../common/MultiplePublish/MultiplePublishAll"; import { MultiplePublish } from "../common/MultiplePublish/MultiplePublishAll";
import { TextEditor } from "../common/TextEditor/TextEditor"; import { TextEditor } from "../common/TextEditor/TextEditor";
import { extractTextFromHTML } from "../common/TextEditor/utils"; import { extractTextFromHTML } from "../common/TextEditor/utils";
@ -32,6 +32,10 @@ import {
CategoryListRef, CategoryListRef,
getCategoriesFromObject, getCategoriesFromObject,
} from "../common/CategoryList/CategoryList.tsx"; } from "../common/CategoryList/CategoryList.tsx";
import {
ImagePublisher,
ImagePublisherRef,
} from "../common/ImagePublisher/ImagePublisher.tsx";
const uid = new ShortUniqueId(); const uid = new ShortUniqueId();
const shortuid = new ShortUniqueId({ length: 5 }); const shortuid = new ShortUniqueId({ length: 5 });
@ -53,7 +57,7 @@ interface VideoFile {
identifier?: string; identifier?: string;
filename?: string; filename?: string;
} }
export const EditFile = () => { export const EditIssue = () => {
const theme = useTheme(); const theme = useTheme();
const dispatch = useDispatch(); const dispatch = useDispatch();
const username = useSelector((state: RootState) => state.auth?.user?.name); const username = useSelector((state: RootState) => state.auth?.user?.name);
@ -75,6 +79,7 @@ export const EditFile = () => {
const [files, setFiles] = useState<VideoFile[]>([]); const [files, setFiles] = useState<VideoFile[]>([]);
const [editCategories, setEditCategories] = useState<string[]>([]); const [editCategories, setEditCategories] = useState<string[]>([]);
const categoryListRef = useRef<CategoryListRef>(null); const categoryListRef = useRef<CategoryListRef>(null);
const imagePublisherRef = useRef<ImagePublisherRef>(null);
const { getRootProps, getInputProps } = useDropzone({ const { getRootProps, getInputProps } = useDropzone({
maxFiles: 10, maxFiles: 10,
@ -121,7 +126,10 @@ export const EditFile = () => {
const paragraph = `<p>${editFileProperties?.fullDescription}</p>`; const paragraph = `<p>${editFileProperties?.fullDescription}</p>`;
setDescription(paragraph); setDescription(paragraph);
} }
setEditCategories(getCategoriesFromObject(editFileProperties));
const categoriesFromEditFile =
getCategoriesFromObject(editFileProperties);
setEditCategories(categoriesFromEditFile);
} }
}, [editFileProperties]); }, [editFileProperties]);
const onClose = () => { const onClose = () => {
@ -141,7 +149,6 @@ export const EditFile = () => {
if (!categoryList[0]) throw new Error("Please select a category"); if (!categoryList[0]) throw new Error("Please select a category");
if (!editFileProperties) return; if (!editFileProperties) return;
if (!userAddress) throw new Error("Unable to locate user address"); if (!userAddress) throw new Error("Unable to locate user address");
if (files.length === 0) throw new Error("Add at least one file");
let errorMsg = ""; let errorMsg = "";
let name = ""; let name = "";
@ -186,7 +193,7 @@ export const EditFile = () => {
const file = publish.file; const file = publish.file;
const id = uid(); const id = uid();
const identifier = `${QSHARE_FILE_BASE}${sanitizeTitle.slice(0, 30)}_${id}`; const identifier = `${QSUPPORT_FILE_BASE}${sanitizeTitle.slice(0, 30)}_${id}`;
let fileExtension = ""; let fileExtension = "";
const fileExtensionSplit = file?.name?.split("."); const fileExtensionSplit = file?.name?.split(".");
@ -227,7 +234,7 @@ export const EditFile = () => {
description: metadescription, description: metadescription,
identifier, identifier,
filename, filename,
tag1: QSHARE_FILE_BASE, tag1: QSUPPORT_FILE_BASE,
}; };
listOfPublishes.push(requestBodyVideo); listOfPublishes.push(requestBodyVideo);
fileReferences.push({ fileReferences.push({
@ -248,24 +255,25 @@ export const EditFile = () => {
commentsId: editFileProperties.commentsId, commentsId: editFileProperties.commentsId,
...categoryListRef.current?.categoriesToObject(), ...categoryListRef.current?.categoriesToObject(),
files: fileReferences, files: fileReferences,
images: imagePublisherRef?.current?.getImageArray(),
}; };
let metadescription = let metadescription =
`**${categoryListRef.current?.getCategoriesFetchString()}**` + `**${categoryListRef.current?.getCategoriesFetchString()}**` +
fullDescription.slice(0, 150); fullDescription.slice(0, 150);
const crowdfundObjectToBase64 = await objectToBase64(fileObject); const fileObjectToBase64 = await objectToBase64(fileObject);
// Description is obtained from raw data // Description is obtained from raw data
const requestBodyJson: any = { const requestBodyJson: any = {
action: "PUBLISH_QDN_RESOURCE", action: "PUBLISH_QDN_RESOURCE",
name: name, name: name,
service: "DOCUMENT", service: "DOCUMENT",
data64: crowdfundObjectToBase64, data64: fileObjectToBase64,
title: title.slice(0, 50), title: title.slice(0, 50),
description: metadescription, description: metadescription,
identifier: editFileProperties.id, identifier: editFileProperties.id,
tag1: QSHARE_FILE_BASE, tag1: QSUPPORT_FILE_BASE,
filename: `video_metadata.json`, filename: `video_metadata.json`,
}; };
listOfPublishes.push(requestBodyJson); listOfPublishes.push(requestBodyJson);
@ -336,7 +344,7 @@ export const EditFile = () => {
justifyContent: "space-between", justifyContent: "space-between",
}} }}
> >
<NewCrowdfundTitle>Update share</NewCrowdfundTitle> <NewCrowdfundTitle>Update Issue</NewCrowdfundTitle>
</Box> </Box>
<> <>
<Box <Box
@ -390,56 +398,52 @@ export const EditFile = () => {
alignItems: "flex-start", alignItems: "flex-start",
}} }}
> >
{files?.length > 0 && ( <Box
<> sx={{
<Box display: "flex",
sx={{ flexDirection: "column",
display: "flex", gap: "20px",
flexDirection: "column", width: "100%",
gap: "20px", }}
width: "100%", >
}} <CategoryList
> categoryData={allCategoryData}
<CategoryList initialCategories={editCategories}
categoryData={allCategoryData} columns={3}
initialCategories={editCategories} ref={categoryListRef}
columns={3} />
ref={categoryListRef} </Box>
/>
</Box>
</>
)}
</Box> </Box>
{files?.length > 0 && ( <ImagePublisher
<> ref={imagePublisherRef}
<CustomInputField initialImages={editFileProperties?.images}
name="title" />
label="Title of share" <CustomInputField
variant="filled" name="title"
value={title} label="Title of Issue"
onChange={e => { variant="filled"
const value = e.target.value; value={title}
const formattedValue = value.replace(titleFormatter, ""); onChange={e => {
setTitle(formattedValue); const value = e.target.value;
}} const formattedValue = value.replace(titleFormatter, "");
inputProps={{ maxLength: 180 }} setTitle(formattedValue);
required }}
/> inputProps={{ maxLength: 180 }}
<Typography required
sx={{ />
fontSize: "18px", <Typography
}} sx={{
> fontSize: "18px",
Description of share }}
</Typography> >
<TextEditor Description of Issue
inlineContent={description} </Typography>
setInlineContent={value => { <TextEditor
setDescription(value); inlineContent={description}
}} setInlineContent={value => {
/> setDescription(value);
</> }}
)} />
</> </>
<CrowdfundActionButtonRow> <CrowdfundActionButtonRow>

View File

@ -6,11 +6,9 @@ import {
CrowdfundActionButton, CrowdfundActionButton,
CrowdfundActionButtonRow, CrowdfundActionButtonRow,
CustomInputField, CustomInputField,
CustomSelect,
LogoPreviewRow, LogoPreviewRow,
ModalBody, ModalBody,
NewCrowdfundTitle, NewCrowdfundTitle,
StyledButton,
TimesIcon, TimesIcon,
} from "./Upload-styles.tsx"; } from "./Upload-styles.tsx";
import { import {
@ -27,27 +25,20 @@ import {
} from "@mui/material"; } from "@mui/material";
import ShortUniqueId from "short-unique-id"; import ShortUniqueId from "short-unique-id";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import AddBoxIcon from "@mui/icons-material/AddBox";
import { useDropzone } from "react-dropzone";
import { setNotification } from "../../state/features/notificationsSlice"; import { setNotification } from "../../state/features/notificationsSlice";
import { objectToBase64, uint8ArrayToBase64 } from "../../utils/toBase64"; import { objectToBase64 } from "../../utils/toBase64";
import { RootState } from "../../state/store"; import { RootState } from "../../state/store";
import { import {
upsertFilesBeginning, setEditPlaylist,
addToHashMap,
upsertFiles,
setEditFile,
updateFile, updateFile,
updateInHashMap, updateInHashMap,
setEditPlaylist,
} from "../../state/features/fileSlice.ts"; } from "../../state/features/fileSlice.ts";
import ImageUploader from "../common/ImageUploader"; import ImageUploader from "../common/ImagePublisher/ImageUploader.tsx";
import { import {
QSHARE_PLAYLIST_BASE, QSUPPORT_FILE_BASE,
QSHARE_FILE_BASE, QSUPPORT_PLAYLIST_BASE,
} from "../../constants/Identifiers.ts"; } from "../../constants/Identifiers.ts";
import { Playlists } from "../Playlists/Playlists";
import { PlaylistListEdit } from "../PlaylistListEdit/PlaylistListEdit"; import { PlaylistListEdit } from "../PlaylistListEdit/PlaylistListEdit";
import { TextEditor } from "../common/TextEditor/TextEditor"; import { TextEditor } from "../common/TextEditor/TextEditor";
import { extractTextFromHTML } from "../common/TextEditor/utils"; import { extractTextFromHTML } from "../common/TextEditor/utils";
@ -87,7 +78,7 @@ export const EditPlaylist = () => {
const [playlistData, setPlaylistData] = useState<any>(null); const [playlistData, setPlaylistData] = useState<any>(null);
const [title, setTitle] = useState<string>(""); const [title, setTitle] = useState<string>("");
const [description, setDescription] = useState<string>(""); const [description, setDescription] = useState<string>("");
const [coverImage, setCoverImage] = useState<string>(""); const [coverImage, setCoverImage] = useState<string[]>([]);
const [videos, setVideos] = useState([]); const [videos, setVideos] = useState([]);
const [selectedCategoryVideos, setSelectedCategoryVideos] = const [selectedCategoryVideos, setSelectedCategoryVideos] =
useState<any>(null); useState<any>(null);
@ -222,7 +213,7 @@ export const EditPlaylist = () => {
setPlaylistData(null); setPlaylistData(null);
setSelectedCategoryVideos(null); setSelectedCategoryVideos(null);
setSelectedSubCategoryVideos(null); setSelectedSubCategoryVideos(null);
setCoverImage(""); setCoverImage([]);
dispatch(setEditPlaylist(null)); dispatch(setEditPlaylist(null));
}; };
@ -292,7 +283,7 @@ export const EditPlaylist = () => {
let commentsId = editVideoProperties?.id; let commentsId = editVideoProperties?.id;
if (isNew) { if (isNew) {
commentsId = `${QSHARE_PLAYLIST_BASE}_cm_${id}`; commentsId = `${QSUPPORT_PLAYLIST_BASE}_cm_${id}`;
} }
const stringDescription = extractTextFromHTML(description); const stringDescription = extractTextFromHTML(description);
@ -324,7 +315,7 @@ export const EditPlaylist = () => {
.trim() .trim()
.toLowerCase(); .toLowerCase();
if (isNew) { if (isNew) {
identifier = `${QSHARE_PLAYLIST_BASE}${sanitizeTitle.slice(0, 30)}_${id}`; identifier = `${QSUPPORT_PLAYLIST_BASE}${sanitizeTitle.slice(0, 30)}_${id}`;
} }
const requestBodyJson: any = { const requestBodyJson: any = {
action: "PUBLISH_QDN_RESOURCE", action: "PUBLISH_QDN_RESOURCE",
@ -334,7 +325,7 @@ export const EditPlaylist = () => {
title: title.slice(0, 50), title: title.slice(0, 50),
description: metadescription, description: metadescription,
identifier: identifier, identifier: identifier,
tag1: QSHARE_FILE_BASE, tag1: QSUPPORT_FILE_BASE,
}; };
await qortalRequest(requestBodyJson); await qortalRequest(requestBodyJson);
@ -519,7 +510,7 @@ export const EditPlaylist = () => {
</Box> </Box>
<React.Fragment> <React.Fragment>
{!coverImage ? ( {!coverImage ? (
<ImageUploader onPick={(img: string) => setCoverImage(img)}> <ImageUploader onPick={(img: string[]) => setCoverImage(img)}>
<AddCoverImageButton variant="contained"> <AddCoverImageButton variant="contained">
Add Cover Image Add Cover Image
<AddLogoIcon <AddLogoIcon
@ -532,10 +523,15 @@ export const EditPlaylist = () => {
</ImageUploader> </ImageUploader>
) : ( ) : (
<LogoPreviewRow> <LogoPreviewRow>
<CoverImagePreview src={coverImage} alt="logo" /> {coverImage.map(
image =>
image && (
<CoverImagePreview src={image} alt="logo" key={image} />
)
)}
<TimesIcon <TimesIcon
color={theme.palette.text.primary} color={theme.palette.text.primary}
onClickFunc={() => setCoverImage("")} onClickFunc={() => setCoverImage([])}
height={"32"} height={"32"}
width={"32"} width={"32"}
></TimesIcon> ></TimesIcon>

View File

@ -3,15 +3,15 @@ import { CardContentContainerComment } from "../common/Comments/Comments-styles"
import { import {
CrowdfundSubTitle, CrowdfundSubTitle,
CrowdfundSubTitleRow, CrowdfundSubTitleRow,
} from "../PublishFile/Upload-styles.tsx"; } from "../PublishIssue/Upload-styles.tsx";
import { Box, Button, Input, Typography, useTheme } from "@mui/material"; import { Box, Button, Input, Typography, useTheme } from "@mui/material";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import DeleteOutlineIcon from "@mui/icons-material/DeleteOutline"; import DeleteOutlineIcon from "@mui/icons-material/DeleteOutline";
import { removeFile } from "../../state/features/fileSlice.ts";
import AddIcon from "@mui/icons-material/Add"; import AddIcon from "@mui/icons-material/Add";
import { QSHARE_FILE_BASE } from "../../constants/Identifiers.ts"; import { QSUPPORT_FILE_BASE } from "../../constants/Identifiers.ts";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import { RootState } from "../../state/store"; import { RootState } from "../../state/store";
export const PlaylistListEdit = ({ playlistData, removeVideo, addVideo }) => { export const PlaylistListEdit = ({ playlistData, removeVideo, addVideo }) => {
const theme = useTheme(); const theme = useTheme();
const navigate = useNavigate(); const navigate = useNavigate();
@ -20,7 +20,7 @@ export const PlaylistListEdit = ({ playlistData, removeVideo, addVideo }) => {
const [searchResults, setSearchResults] = useState([]); const [searchResults, setSearchResults] = useState([]);
const [filterSearch, setFilterSearch] = useState(""); const [filterSearch, setFilterSearch] = useState("");
const search = async () => { const search = async () => {
const url = `/arbitrary/resources/search?mode=ALL&service=DOCUMENT&mode=ALL&identifier=${QSHARE_FILE_BASE}&title=${filterSearch}&limit=20&includemetadata=true&reverse=true&name=${username}&exactmatchnames=true&offset=0`; const url = `/arbitrary/resources/search?mode=ALL&service=DOCUMENT&mode=ALL&identifier=${QSUPPORT_FILE_BASE}&title=${filterSearch}&limit=20&includemetadata=true&reverse=true&name=${username}&exactmatchnames=true&offset=0`;
const response = await fetch(url, { const response = await fetch(url, {
method: "GET", method: "GET",
headers: { headers: {

View File

@ -1,66 +1,79 @@
import React from 'react' import React from "react";
import { CardContentContainerComment } from '../common/Comments/Comments-styles' import { CardContentContainerComment } from "../common/Comments/Comments-styles";
import { CrowdfundSubTitle, CrowdfundSubTitleRow } from '../PublishFile/Upload-styles.tsx' import {
import { Box, Typography, useTheme } from '@mui/material' CrowdfundSubTitle,
import { useNavigate } from 'react-router-dom' CrowdfundSubTitleRow,
} from "../PublishIssue/Upload-styles.tsx";
export const Playlists = ({playlistData, currentVideoIdentifier}) => { import { Box, Typography, useTheme } from "@mui/material";
const theme = useTheme(); import { useNavigate } from "react-router-dom";
const navigate = useNavigate()
export const Playlists = ({ playlistData, currentVideoIdentifier }) => {
const theme = useTheme();
const navigate = useNavigate();
return ( return (
<Box sx={{ <Box
display: 'flex', sx={{
flexDirection: 'column', display: "flex",
flexDirection: "column",
maxWidth: '400px',
width: '100%' maxWidth: "400px",
}}> width: "100%",
<CrowdfundSubTitleRow > }}
>
<CrowdfundSubTitleRow>
<CrowdfundSubTitle>Playlist</CrowdfundSubTitle> <CrowdfundSubTitle>Playlist</CrowdfundSubTitle>
</CrowdfundSubTitleRow> </CrowdfundSubTitleRow>
<CardContentContainerComment sx={{ <CardContentContainerComment
marginTop: '25px', sx={{
height: '450px', marginTop: "25px",
overflow: 'auto' height: "450px",
}}> overflow: "auto",
{playlistData?.videos?.map((vid, index)=> { }}
const isCurrentVidPlayling = vid?.identifier === currentVideoIdentifier; >
{playlistData?.videos?.map((vid, index) => {
const isCurrentVidPlayling =
vid?.identifier === currentVideoIdentifier;
return (
<Box key={vid?.identifier} sx={{
display: 'flex',
gap: '10px',
width: '100%',
background: isCurrentVidPlayling && theme.palette.primary.main,
alignItems: 'center',
padding: '10px',
borderRadius: '5px',
cursor: isCurrentVidPlayling ? 'default' : 'pointer',
userSelect: 'none'
}}
onClick={()=> {
if(isCurrentVidPlayling) return
navigate(`/video/${vid.name}/${vid.identifier}`) return (
<Box
key={vid?.identifier}
sx={{
display: "flex",
gap: "10px",
width: "100%",
background: isCurrentVidPlayling && theme.palette.primary.main,
alignItems: "center",
padding: "10px",
borderRadius: "5px",
cursor: isCurrentVidPlayling ? "default" : "pointer",
userSelect: "none",
}}
onClick={() => {
if (isCurrentVidPlayling) return;
navigate(`/video/${vid.name}/${vid.identifier}`);
}}
>
<Typography
sx={{
fontSize: "14px",
}} }}
> >
<Typography sx={{ {index + 1}
fontSize: '14px' </Typography>
}}>{index + 1}</Typography> <Typography
<Typography sx={{ sx={{
fontSize: '18px', fontSize: "18px",
wordBreak: 'break-word' wordBreak: "break-word",
}}>{vid?.metadata?.title}</Typography> }}
>
</Box> {vid?.metadata?.title}
) </Typography>
</Box>
);
})} })}
</CardContentContainerComment> </CardContentContainerComment>
</Box> </Box>
);
) };
}

View File

@ -1,7 +1,7 @@
import React, { useEffect, useRef, useState } from "react"; import React, { useEffect, useRef, useState } from "react";
import { import {
CrowdfundActionButton, ActionButton,
CrowdfundActionButtonRow, ActionButtonRow,
CustomInputField, CustomInputField,
ModalBody, ModalBody,
NewCrowdfundTitle, NewCrowdfundTitle,
@ -17,16 +17,22 @@ import { useDropzone } from "react-dropzone";
import { setNotification } from "../../state/features/notificationsSlice"; import { setNotification } from "../../state/features/notificationsSlice";
import { objectToBase64 } from "../../utils/toBase64"; import { objectToBase64 } from "../../utils/toBase64";
import { RootState } from "../../state/store"; import { RootState } from "../../state/store";
import { QSHARE_FILE_BASE } from "../../constants/Identifiers.ts"; import { QSUPPORT_FILE_BASE } from "../../constants/Identifiers.ts";
import { MultiplePublish } from "../common/MultiplePublish/MultiplePublishAll"; import { MultiplePublish } from "../common/MultiplePublish/MultiplePublishAll";
import { TextEditor } from "../common/TextEditor/TextEditor"; import { TextEditor } from "../common/TextEditor/TextEditor";
import { extractTextFromHTML } from "../common/TextEditor/utils"; import { extractTextFromHTML } from "../common/TextEditor/utils";
import { allCategoryData } from "../../constants/Categories/1stCategories.ts"; import { allCategoryData } from "../../constants/Categories/1stCategories.ts";
import { titleFormatter } from "../../constants/Misc.ts"; import { titleFormatter } from "../../constants/Misc.ts";
import { import {
appendCategoryToList,
CategoryList, CategoryList,
CategoryListRef, CategoryListRef,
} from "../common/CategoryList/CategoryList.tsx"; } from "../common/CategoryList/CategoryList.tsx";
import { SupportState } from "../../constants/Categories/2ndCategories.ts";
import {
ImagePublisher,
ImagePublisherRef,
} from "../common/ImagePublisher/ImagePublisher.tsx";
const uid = new ShortUniqueId(); const uid = new ShortUniqueId();
const shortuid = new ShortUniqueId({ length: 5 }); const shortuid = new ShortUniqueId({ length: 5 });
@ -46,7 +52,7 @@ interface VideoFile {
description: string; description: string;
coverImage?: string; coverImage?: string;
} }
export const PublishFile = ({ editId, editContent }: NewCrowdfundProps) => { export const PublishIssue = ({ editId, editContent }: NewCrowdfundProps) => {
const theme = useTheme(); const theme = useTheme();
const dispatch = useDispatch(); const dispatch = useDispatch();
const [isOpenMultiplePublish, setIsOpenMultiplePublish] = useState(false); const [isOpenMultiplePublish, setIsOpenMultiplePublish] = useState(false);
@ -73,7 +79,7 @@ export const PublishFile = ({ editId, editContent }: NewCrowdfundProps) => {
const [playlistSetting, setPlaylistSetting] = useState<null | string>(null); const [playlistSetting, setPlaylistSetting] = useState<null | string>(null);
const [publishes, setPublishes] = useState<any>(null); const [publishes, setPublishes] = useState<any>(null);
const categoryListRef = useRef<CategoryListRef>(null); const categoryListRef = useRef<CategoryListRef>(null);
const imagePublisherRef = useRef<ImagePublisherRef>(null);
const { getRootProps, getInputProps } = useDropzone({ const { getRootProps, getInputProps } = useDropzone({
maxFiles: 10, maxFiles: 10,
maxSize: 419430400, // 400 MB in bytes maxSize: 419430400, // 400 MB in bytes
@ -127,7 +133,6 @@ export const PublishFile = ({ editId, editContent }: NewCrowdfundProps) => {
if (!description) throw new Error("Please enter a description"); if (!description) throw new Error("Please enter a description");
if (!categoryListRef.current?.getSelectedCategories()[0]) if (!categoryListRef.current?.getSelectedCategories()[0])
throw new Error("Please select a category"); throw new Error("Please select a category");
if (files.length === 0) throw new Error("Add at least one file");
let errorMsg = ""; let errorMsg = "";
let name = ""; let name = "";
if (username) { if (username) {
@ -169,7 +174,7 @@ export const PublishFile = ({ editId, editContent }: NewCrowdfundProps) => {
const file = publish.file; const file = publish.file;
const id = uid(); const id = uid();
const identifier = `${QSHARE_FILE_BASE}${sanitizeTitle.slice(0, 30)}_${id}`; const identifier = `${QSUPPORT_FILE_BASE}${sanitizeTitle.slice(0, 30)}_${id}`;
let fileExtension = ""; let fileExtension = "";
const fileExtensionSplit = file?.name?.split("."); const fileExtensionSplit = file?.name?.split(".");
@ -197,9 +202,12 @@ export const PublishFile = ({ editId, editContent }: NewCrowdfundProps) => {
filename = alphanumericString; filename = alphanumericString;
} }
let metadescription = const categoryList = appendCategoryToList(
`**${categoryListRef.current?.getCategoriesFetchString()}**` + categoryListRef.current?.getSelectedCategories(),
fullDescription.slice(0, 150); "101"
);
const categoryString = `**${categoryListRef.current?.getCategoriesFetchString(categoryList)}**`;
let metadescription = categoryString + fullDescription.slice(0, 150);
const requestBodyVideo: any = { const requestBodyVideo: any = {
action: "PUBLISH_QDN_RESOURCE", action: "PUBLISH_QDN_RESOURCE",
@ -210,7 +218,7 @@ export const PublishFile = ({ editId, editContent }: NewCrowdfundProps) => {
description: metadescription, description: metadescription,
identifier, identifier,
filename, filename,
tag1: QSHARE_FILE_BASE, tag1: QSUPPORT_FILE_BASE,
}; };
listOfPublishes.push(requestBodyVideo); listOfPublishes.push(requestBodyVideo);
fileReferences.push({ fileReferences.push({
@ -224,32 +232,38 @@ export const PublishFile = ({ editId, editContent }: NewCrowdfundProps) => {
} }
const idMeta = uid(); const idMeta = uid();
const identifier = `${QSHARE_FILE_BASE}${sanitizeTitle.slice(0, 30)}_${idMeta}`; const identifier = `${QSUPPORT_FILE_BASE}${sanitizeTitle.slice(0, 30)}_${idMeta}`;
const categoryList = appendCategoryToList(
categoryListRef.current?.getSelectedCategories(),
"101"
);
const fileObject: any = { const fileObject: any = {
title, title,
version: 1, version: 1,
fullDescription, fullDescription,
htmlDescription: description, htmlDescription: description,
commentsId: `${QSHARE_FILE_BASE}_cm_${idMeta}`, commentsId: `${QSUPPORT_FILE_BASE}_cm_${idMeta}`,
...categoryListRef.current?.categoriesToObject(), ...categoryListRef.current?.categoriesToObject(categoryList),
files: fileReferences, files: fileReferences,
images: imagePublisherRef?.current?.getImageArray(),
}; };
let metadescription = const categoryString = `**${categoryListRef.current?.getCategoriesFetchString(categoryList)}**`;
`**${categoryListRef.current?.getCategoriesFetchString()}**` + let metadescription = categoryString + fullDescription.slice(0, 150);
fullDescription.slice(0, 150);
const crowdfundObjectToBase64 = await objectToBase64(fileObject); const fileObjectToBase64 = await objectToBase64(fileObject);
// Description is obtained from raw data // Description is obtained from raw data
const requestBodyJson: any = { const requestBodyJson: any = {
action: "PUBLISH_QDN_RESOURCE", action: "PUBLISH_QDN_RESOURCE",
name: name, name: name,
service: "DOCUMENT", service: "DOCUMENT",
data64: crowdfundObjectToBase64, data64: fileObjectToBase64,
title: title.slice(0, 50), title: title.slice(0, 50),
description: metadescription, description: metadescription,
identifier: identifier + "_metadata", identifier: identifier + "_metadata",
tag1: QSHARE_FILE_BASE, tag1: QSUPPORT_FILE_BASE,
filename: `video_metadata.json`, filename: `video_metadata.json`,
}; };
listOfPublishes.push(requestBodyJson); listOfPublishes.push(requestBodyJson);
@ -264,17 +278,17 @@ export const PublishFile = ({ editId, editContent }: NewCrowdfundProps) => {
let notificationObj: any = null; let notificationObj: any = null;
if (typeof error === "string") { if (typeof error === "string") {
notificationObj = { notificationObj = {
msg: error || "Failed to publish share", msg: error || "Failed to publish issue",
alertType: "error", alertType: "error",
}; };
} else if (typeof error?.error === "string") { } else if (typeof error?.error === "string") {
notificationObj = { notificationObj = {
msg: error?.error || "Failed to publish share", msg: error?.error || "Failed to publish issue",
alertType: "error", alertType: "error",
}; };
} else { } else {
notificationObj = { notificationObj = {
msg: error?.message || "Failed to publish share", msg: error?.message || "Failed to publish issue",
alertType: "error", alertType: "error",
}; };
} }
@ -295,7 +309,7 @@ export const PublishFile = ({ editId, editContent }: NewCrowdfundProps) => {
setIsOpen(true); setIsOpen(true);
}} }}
> >
share Open an Issue
</StyledButton> </StyledButton>
)} )}
</> </>
@ -314,7 +328,7 @@ export const PublishFile = ({ editId, editContent }: NewCrowdfundProps) => {
justifyContent: "space-between", justifyContent: "space-between",
}} }}
> >
<NewCrowdfundTitle>Share</NewCrowdfundTitle> <NewCrowdfundTitle>Issue</NewCrowdfundTitle>
</Box> </Box>
{step === "videos" && ( {step === "videos" && (
@ -331,7 +345,7 @@ export const PublishFile = ({ editId, editContent }: NewCrowdfundProps) => {
> >
<input {...getInputProps()} /> <input {...getInputProps()} />
<Typography> <Typography>
Drag and drop files here or click to select files Publish files related to issue (Optional)
</Typography> </Typography>
</Box> </Box>
{files.map((file, index) => { {files.map((file, index) => {
@ -362,53 +376,53 @@ export const PublishFile = ({ editId, editContent }: NewCrowdfundProps) => {
); );
})} })}
{files?.length > 0 && ( <>
<> <Box
<Box sx={{
sx={{ display: "flex",
display: "flex", gap: "20px",
gap: "20px", alignItems: "flex-start",
alignItems: "flex-start", }}
}} >
> <CategoryList
<CategoryList categoryData={allCategoryData}
categoryData={allCategoryData} ref={categoryListRef}
ref={categoryListRef} columns={3}
columns={3} excludeCategories={SupportState}
/>
</Box>
<CustomInputField
name="title"
label="Title of share"
variant="filled"
value={title}
onChange={e => {
const value = e.target.value;
const formattedValue = value.replace(titleFormatter, "");
setTitle(formattedValue);
}}
inputProps={{ maxLength: 180 }}
required
/> />
<Typography </Box>
sx={{ <ImagePublisher ref={imagePublisherRef} />
fontSize: "18px", <CustomInputField
}} name="title"
> label="Title of Issue"
Description of share variant="filled"
</Typography> value={title}
<TextEditor onChange={e => {
inlineContent={description} const value = e.target.value;
setInlineContent={value => { const formattedValue = value.replace(titleFormatter, "");
setDescription(value); setTitle(formattedValue);
}} }}
/> inputProps={{ maxLength: 180 }}
</> required
)} />
<Typography
sx={{
fontSize: "18px",
}}
>
Description of Issue
</Typography>
<TextEditor
inlineContent={description}
setInlineContent={value => {
setDescription(value);
}}
/>
</>
</> </>
)} )}
<CrowdfundActionButtonRow> <ActionButtonRow>
<CrowdfundActionButton <ActionButton
onClick={() => { onClick={() => {
onClose(); onClose();
}} }}
@ -416,7 +430,7 @@ export const PublishFile = ({ editId, editContent }: NewCrowdfundProps) => {
color="error" color="error"
> >
Cancel Cancel
</CrowdfundActionButton> </ActionButton>
<Box <Box
sx={{ sx={{
display: "flex", display: "flex",
@ -424,16 +438,16 @@ export const PublishFile = ({ editId, editContent }: NewCrowdfundProps) => {
alignItems: "center", alignItems: "center",
}} }}
> >
<CrowdfundActionButton <ActionButton
variant="contained" variant="contained"
onClick={() => { onClick={() => {
publishQDNResource(); publishQDNResource();
}} }}
> >
Publish Publish
</CrowdfundActionButton> </ActionButton>
</Box> </Box>
</CrowdfundActionButtonRow> </ActionButtonRow>
</ModalBody> </ModalBody>
</Modal> </Modal>
@ -466,7 +480,7 @@ export const PublishFile = ({ editId, editContent }: NewCrowdfundProps) => {
categoryListRef.current?.clearCategories(); categoryListRef.current?.clearCategories();
dispatch( dispatch(
setNotification({ setNotification({
msg: "Files published", msg: "Issue published",
alertType: "success", alertType: "success",
}) })
); );

View File

@ -7,9 +7,9 @@ import {
Button, Button,
Grid, Grid,
Rating, Rating,
Select,
TextField, TextField,
Typography, Typography,
Select
} from "@mui/material"; } from "@mui/material";
import AddPhotoAlternateIcon from "@mui/icons-material/AddPhotoAlternate"; import AddPhotoAlternateIcon from "@mui/icons-material/AddPhotoAlternate";
import { TimesSVG } from "../../assets/svgs/TimesSVG"; import { TimesSVG } from "../../assets/svgs/TimesSVG";
@ -67,9 +67,9 @@ export const ModalBody = styled(Box)(({ theme }) => ({
overflowY: "auto", overflowY: "auto",
maxHeight: "95vh", maxHeight: "95vh",
boxShadow: boxShadow:
theme.palette.mode === "dark" theme.palette.mode === "dark"
? "0px 4px 5px 0px hsla(0,0%,0%,0.14), 0px 1px 10px 0px hsla(0,0%,0%,0.12), 0px 2px 4px -1px hsla(0,0%,0%,0.2)" ? "0px 4px 5px 0px hsla(0,0%,0%,0.14), 0px 1px 10px 0px hsla(0,0%,0%,0.12), 0px 2px 4px -1px hsla(0,0%,0%,0.2)"
: "rgba(99, 99, 99, 0.2) 0px 2px 8px 0px", : "rgba(99, 99, 99, 0.2) 0px 2px 8px 0px",
"&::-webkit-scrollbar-track": { "&::-webkit-scrollbar-track": {
backgroundColor: theme.palette.background.paper, backgroundColor: theme.palette.background.paper,
}, },
@ -159,8 +159,6 @@ export const CustomInputField = styled(TextField)(({ theme }) => ({
}, },
})); }));
export const CrowdfundTitle = styled(Typography)(({ theme }) => ({ export const CrowdfundTitle = styled(Typography)(({ theme }) => ({
fontFamily: "Copse", fontFamily: "Copse",
letterSpacing: "1px", letterSpacing: "1px",
@ -203,11 +201,11 @@ export const CrowdfundDescription = styled(Typography)(({ theme }) => ({
export const Spacer = ({ height }: any) => { export const Spacer = ({ height }: any) => {
return ( return (
<Box <Box
sx={{ sx={{
height: height, height: height,
}} }}
/> />
); );
}; };
@ -314,14 +312,14 @@ export const AddCrowdFundButton = styled(Button)(({ theme }) => ({
gap: "8px", gap: "8px",
color: "#ffffff", color: "#ffffff",
backgroundColor: backgroundColor:
theme.palette.mode === "dark" ? theme.palette.primary.main : "#2a9a86", theme.palette.mode === "dark" ? theme.palette.primary.main : "#2a9a86",
border: "none", border: "none",
borderRadius: "5px", borderRadius: "5px",
transition: "all 0.3s ease-in-out", transition: "all 0.3s ease-in-out",
"&:hover": { "&:hover": {
cursor: "pointer", cursor: "pointer",
backgroundColor: backgroundColor:
theme.palette.mode === "dark" ? theme.palette.primary.dark : "#217e6d", theme.palette.mode === "dark" ? theme.palette.primary.dark : "#217e6d",
}, },
})); }));
@ -333,14 +331,14 @@ export const EditCrowdFundButton = styled(Button)(({ theme }) => ({
gap: "8px", gap: "8px",
color: "#ffffff", color: "#ffffff",
backgroundColor: backgroundColor:
theme.palette.mode === "dark" ? theme.palette.primary.main : "#2a9a86", theme.palette.mode === "dark" ? theme.palette.primary.main : "#2a9a86",
border: "none", border: "none",
borderRadius: "5px", borderRadius: "5px",
transition: "all 0.3s ease-in-out", transition: "all 0.3s ease-in-out",
"&:hover": { "&:hover": {
cursor: "pointer", cursor: "pointer",
backgroundColor: backgroundColor:
theme.palette.mode === "dark" ? theme.palette.primary.dark : "#217e6d", theme.palette.mode === "dark" ? theme.palette.primary.dark : "#217e6d",
}, },
})); }));
@ -466,14 +464,14 @@ export const CoverImage = styled("img")({
objectPosition: "center", objectPosition: "center",
}); });
export const CrowdfundActionButtonRow = styled(Box)({ export const ActionButtonRow = styled(Box)({
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",
justifyContent: "space-between", justifyContent: "space-between",
width: "100%", width: "100%",
}); });
export const CrowdfundActionButton = styled(Button)(({ theme }) => ({ export const ActionButton = styled(Button)(({ theme }) => ({
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",
fontFamily: "Montserrat", fontFamily: "Montserrat",
@ -540,8 +538,8 @@ export const NoReviewsFont = styled(Typography)(({ theme }) => ({
export const StyledButton = styled(Button)(({ theme }) => ({ export const StyledButton = styled(Button)(({ theme }) => ({
fontWeight: 600, fontWeight: 600,
color: theme.palette.text.primary, color: theme.palette.text.primary,
fontFamily: "Cairo" fontFamily: "Cairo",
})) }));
export const CustomSelect = styled(Select)(({ theme }) => ({ export const CustomSelect = styled(Select)(({ theme }) => ({
fontFamily: "Mulish", fontFamily: "Mulish",
@ -550,38 +548,38 @@ export const CustomSelect = styled(Select)(({ theme }) => ({
fontWeight: 400, fontWeight: 400,
color: theme.palette.text.primary, color: theme.palette.text.primary,
backgroundColor: theme.palette.background.default, backgroundColor: theme.palette.background.default,
'& .MuiSelect-select': { "& .MuiSelect-select": {
padding: '12px', padding: "12px",
fontFamily: "Mulish", fontFamily: "Mulish",
fontSize: "19px", fontSize: "19px",
letterSpacing: "0px", letterSpacing: "0px",
fontWeight: 400, fontWeight: 400,
borderRadius: theme.shape.borderRadius, // Match border radius borderRadius: theme.shape.borderRadius, // Match border radius
}, },
'&:before': { "&:before": {
// Underline style // Underline style
borderBottomColor: theme.palette.mode === "light" ? "#B2BAC2" : "#c9cccf", borderBottomColor: theme.palette.mode === "light" ? "#B2BAC2" : "#c9cccf",
}, },
'&:after': { "&:after": {
// Underline style when focused // Underline style when focused
borderBottomColor: theme.palette.secondary.main, borderBottomColor: theme.palette.secondary.main,
}, },
'& .MuiOutlinedInput-root': { "& .MuiOutlinedInput-root": {
'& fieldset': { "& fieldset": {
borderColor: "#E0E3E7", borderColor: "#E0E3E7",
}, },
'&:hover fieldset': { "&:hover fieldset": {
borderColor: "#B2BAC2", borderColor: "#B2BAC2",
}, },
'&.Mui-focused fieldset': { "&.Mui-focused fieldset": {
borderColor: "#6F7E8C", borderColor: "#6F7E8C",
}, },
}, },
'& .MuiInputBase-root': { "& .MuiInputBase-root": {
fontFamily: "Mulish", fontFamily: "Mulish",
fontSize: "19px", fontSize: "19px",
letterSpacing: "0px", letterSpacing: "0px",
fontWeight: 400, fontWeight: 400,
color: theme.palette.text.primary, color: theme.palette.text.primary,
}, },
})); }));

View File

@ -25,13 +25,13 @@ export const StatsData = () => {
getFilesCount, getFilesCount,
} = useFetchFiles(); } = useFetchFiles();
const totalVideosPublished = useSelector( const totalIssuesPublished = useSelector(
(state: RootState) => state.global.totalFilesPublished (state: RootState) => state.global.totalFilesPublished
); );
const totalNamesPublished = useSelector( const totalNamesPublished = useSelector(
(state: RootState) => state.global.totalNamesPublished (state: RootState) => state.global.totalNamesPublished
); );
const videosPerNamePublished = useSelector( const issuesPerNamePublished = useSelector(
(state: RootState) => state.global.filesPerNamePublished (state: RootState) => state.global.filesPerNamePublished
); );
@ -40,22 +40,28 @@ export const StatsData = () => {
}, [getFilesCount]); }, [getFilesCount]);
return ( return (
<StatsCol> totalIssuesPublished > 0 && (
<div> <StatsCol>
Shares:{" "} <div>
<span style={{ fontWeight: "bold" }}>{totalVideosPublished}</span> Issues Published:{" "}
</div> <span style={{ fontWeight: "bold" }}>
<div> {totalIssuesPublished || ""}
Publishers:{" "} </span>
<span style={{ fontWeight: "bold" }}>{totalNamesPublished}</span> </div>
</div> <div>
<div> Publishers:{" "}
Average:{" "} <span style={{ fontWeight: "bold" }}>
<span style={{ fontWeight: "bold" }}> {totalNamesPublished || ""}
{videosPerNamePublished > 0 && </span>
Number(videosPerNamePublished).toFixed(0)} </div>
</span> <div>
</div> Average:{" "}
</StatsCol> <span style={{ fontWeight: "bold" }}>
{issuesPerNamePublished > 0 &&
Number(issuesPerNamePublished).toFixed(0)}
</span>
</div>
</StatsCol>
)
); );
}; };

View File

@ -10,9 +10,10 @@ import {
Theme, Theme,
} from "@mui/material"; } from "@mui/material";
import React, { forwardRef, useImperativeHandle, useState } from "react"; import React, { useEffect, useImperativeHandle, useState } from "react";
import { CategoryContainer } from "./CategoryList-styles.tsx"; import { CategoryContainer } from "./CategoryList-styles.tsx";
import { allCategoryData } from "../../../constants/Categories/1stCategories.ts"; import { allCategoryData } from "../../../constants/Categories/1stCategories.ts";
import { log } from "../../../constants/Misc.ts";
export interface Category { export interface Category {
id: number; id: number;
@ -29,19 +30,22 @@ export interface CategoryData {
} }
type ListDirection = "column" | "row"; type ListDirection = "column" | "row";
interface CategoryListProps { interface CategoryListProps {
sx?: SxProps<Theme>; sx?: SxProps<Theme>;
categoryData: CategoryData; categoryData: CategoryData;
initialCategories?: string[]; initialCategories?: string[];
columns?: number; columns?: number;
afterChange?: (categories: string[]) => void;
excludeCategories?: Category[];
} }
export type CategoryListRef = { export type CategoryListRef = {
getSelectedCategories: () => string[]; getSelectedCategories: () => string[];
setSelectedCategories: (arr: string[]) => void; setSelectedCategories: (arr: string[]) => void;
clearCategories: () => void; clearCategories: () => void;
getCategoriesFetchString: () => string; getCategoriesFetchString: (categories?: string[]) => string;
categoriesToObject: () => object; categoriesToObject: (categories?: string[]) => object;
}; };
export const CategoryList = React.forwardRef< export const CategoryList = React.forwardRef<
@ -49,7 +53,14 @@ export const CategoryList = React.forwardRef<
CategoryListProps CategoryListProps
>( >(
( (
{ sx, categoryData, initialCategories, columns = 1 }: CategoryListProps, {
sx,
categoryData,
initialCategories,
columns = 1,
afterChange,
excludeCategories,
}: CategoryListProps,
ref ref
) => { ) => {
const categoriesLength = categoryData.subCategories.length + 1; const categoriesLength = categoryData.subCategories.length + 1;
@ -60,20 +71,27 @@ export const CategoryList = React.forwardRef<
const [selectedCategories, setSelectedCategories] = useState<string[]>( const [selectedCategories, setSelectedCategories] = useState<string[]>(
initialCategories || emptyCategories initialCategories || emptyCategories
); );
useEffect(() => {
if (initialCategories) setSelectedCategories(initialCategories);
}, [initialCategories]);
const categoriesToObject = () => { const updateCategories = (categories: string[]) => {
setSelectedCategories(categories);
if (afterChange) afterChange(categories);
};
const categoriesToObject = (categories: string[]) => {
let categoriesObject = {}; let categoriesObject = {};
selectedCategories.map((category, index) => { categories.map((category, index) => {
if (index === 0) categoriesObject["category"] = category; if (index === 0) categoriesObject["category"] = category;
else if (index === 1) categoriesObject["subcategory"] = category; else if (index === 1) categoriesObject["subcategory"] = category;
else categoriesObject[`subcategory${index}`] = category; else categoriesObject[`subcategory${index}`] = category;
}); });
console.log("categoriesObject is: ", categoriesObject); if (log) console.log("categoriesObject is: ", categoriesObject);
return categoriesObject; return categoriesObject;
}; };
const clearCategories = () => { const clearCategories = () => {
setSelectedCategories(emptyCategories); updateCategories(emptyCategories);
}; };
useImperativeHandle(ref, () => ({ useImperativeHandle(ref, () => ({
@ -81,26 +99,31 @@ export const CategoryList = React.forwardRef<
return selectedCategories; return selectedCategories;
}, },
setSelectedCategories: categories => { setSelectedCategories: categories => {
console.log("setSelectedCategories: ", categories); if (log) console.log("setSelectedCategories: ", categories);
//categories.map((category, index) => selectCategory(category, index)); updateCategories(categories);
setSelectedCategories(categories);
}, },
clearCategories, clearCategories,
getCategoriesFetchString: () => getCategoriesFetchString: (categories?: string[]) =>
getCategoriesFetchString(selectedCategories), getCategoriesFetchString(categories || selectedCategories),
categoriesToObject, categoriesToObject: (categories?: string[]) =>
categoriesToObject(categories || selectedCategories),
})); }));
const selectCategory = (optionId: string, index: number) => { const selectCategory = (optionId: string, index: number) => {
const isMainCategory = index === 0; const isMainCategory = index === 0;
const subCategoryIndex = index - 1; const subCategoryIndex = index - 1;
let selectedOption: Category | undefined;
if (isMainCategory)
selectedOption = categoryData.category.find(
option => option.id === +optionId
);
else {
const subCategoryLevel = categoryData.subCategories[subCategoryIndex];
const parentCategory = selectedCategories[subCategoryIndex];
const subCategory = subCategoryLevel[parentCategory];
const selectedOption = isMainCategory selectedOption = subCategory.find(option => option.id === +optionId);
? categoryData.category.find(option => option.id === +optionId) }
: categoryData.subCategories[subCategoryIndex][
selectedCategories[subCategoryIndex]
].find(option => option.id === +optionId);
const newSelectedCategories: string[] = selectedCategories.map( const newSelectedCategories: string[] = selectedCategories.map(
(category, categoryIndex) => { (category, categoryIndex) => {
if (index > categoryIndex) return category; if (index > categoryIndex) return category;
@ -108,7 +131,7 @@ export const CategoryList = React.forwardRef<
else return ""; else return "";
} }
); );
setSelectedCategories(newSelectedCategories); updateCategories(newSelectedCategories);
}; };
const selectCategoryEvent = (event: SelectChangeEvent, index: number) => { const selectCategoryEvent = (event: SelectChangeEvent, index: number) => {
@ -136,15 +159,16 @@ export const CategoryList = React.forwardRef<
const fillMenu = (category: Categories, index: number) => { const fillMenu = (category: Categories, index: number) => {
const subCategoryIndex = selectedCategories[index]; const subCategoryIndex = selectedCategories[index];
console.log("selected categories: ", selectedCategories); if (log) console.log("selected categories: ", selectedCategories);
console.log("index is: ", index); if (log) console.log("index is: ", index);
console.log("subCategoryIndex is: ", subCategoryIndex); if (log) console.log("subCategoryIndex is: ", subCategoryIndex);
console.log("category is: ", category); if (log) console.log("category is: ", category);
console.log( if (log)
"subCategoryIndex within category: ", console.log(
selectedCategories[subCategoryIndex] "subCategoryIndex within category: ",
); selectedCategories[subCategoryIndex]
console.log("categoryData: ", categoryData); );
if (log) console.log("categoryData: ", categoryData);
const menuToFill = category[subCategoryIndex]; const menuToFill = category[subCategoryIndex];
if (menuToFill) if (menuToFill)
@ -158,6 +182,7 @@ export const CategoryList = React.forwardRef<
const hasSubCategory = (category: Categories, index: number) => { const hasSubCategory = (category: Categories, index: number) => {
const subCategoryIndex = selectedCategories[index]; const subCategoryIndex = selectedCategories[index];
const subCategory = category[subCategoryIndex]; const subCategory = category[subCategoryIndex];
if (excludeCategories && subCategory === excludeCategories) return false;
return subCategory && subCategoryIndex; return subCategory && subCategoryIndex;
}; };
@ -265,10 +290,21 @@ export const getCategoriesFetchString = (categories: string[]) => {
else fetchString += `;sub${index}:${category}`; else fetchString += `;sub${index}:${category}`;
} }
}); });
console.log("categoriesAsDescription: ", fetchString); if (log) console.log("categoriesAsDescription: ", fetchString);
return fetchString; return fetchString;
}; };
export const appendCategoryToList = (
categories: string[],
appendedCategoryID: string
) => {
const filteredCategories = categories.filter(
categoryString => categoryString.length > 0
);
filteredCategories.push(appendedCategoryID);
return filteredCategories;
};
export const getCategoriesFromObject = (editFileProperties: any) => { export const getCategoriesFromObject = (editFileProperties: any) => {
const categoryList: string[] = []; const categoryList: string[] = [];
const categoryCount = allCategoryData.subCategories.length + 1; const categoryCount = allCategoryData.subCategories.length + 1;

View File

@ -11,7 +11,7 @@ import {
CommentInputContainer, CommentInputContainer,
SubmitCommentButton, SubmitCommentButton,
} from "./Comments-styles"; } from "./Comments-styles";
import { QSHARE_COMMENT_BASE } from "../../../constants/Identifiers.ts"; import { QSUPPORT_COMMENT_BASE } from "../../../constants/Identifiers.ts";
const uid = new ShortUniqueId(); const uid = new ShortUniqueId();
const notification = localforage.createInstance({ const notification = localforage.createInstance({
@ -201,13 +201,13 @@ export const CommentEditor = ({
try { try {
const id = uid(); const id = uid();
let identifier = `${QSHARE_COMMENT_BASE}${postId.slice(-12)}_base_${id}`; let identifier = `${QSUPPORT_COMMENT_BASE}${postId.slice(-12)}_base_${id}`;
let idForNotification = identifier; let idForNotification = identifier;
if (isReply && commentId) { if (isReply && commentId) {
const removeBaseCommentId = commentId; const removeBaseCommentId = commentId;
removeBaseCommentId.replace("_base_", ""); removeBaseCommentId.replace("_base_", "");
identifier = `${QSHARE_COMMENT_BASE}${postId.slice( identifier = `${QSUPPORT_COMMENT_BASE}${postId.slice(
-12 -12
)}_reply_${removeBaseCommentId.slice(-6)}_${id}`; )}_reply_${removeBaseCommentId.slice(-6)}_${id}`;
idForNotification = commentId; idForNotification = commentId;

View File

@ -1,11 +1,11 @@
import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useCallback, useEffect, useMemo, useState } from "react";
import { CommentEditor } from "./CommentEditor"; import { CommentEditor } from "./CommentEditor";
import { Comment } from "./Comment"; import { Comment } from "./Comment";
import { Box, Button, CircularProgress, useTheme } from "@mui/material"; import { CircularProgress } from "@mui/material";
import { styled } from "@mui/system"; import { styled } from "@mui/system";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import { RootState } from "../../../state/store"; import { RootState } from "../../../state/store";
import { useNavigate, useLocation } from "react-router-dom"; import { useLocation, useNavigate } from "react-router-dom";
import { import {
CommentContainer, CommentContainer,
CommentEditorContainer, CommentEditorContainer,
@ -14,8 +14,11 @@ import {
LoadMoreCommentsButtonRow, LoadMoreCommentsButtonRow,
NoCommentsRow, NoCommentsRow,
} from "./Comments-styles"; } from "./Comments-styles";
import { QSHARE_COMMENT_BASE } from "../../../constants/Identifiers.ts"; import { QSUPPORT_COMMENT_BASE } from "../../../constants/Identifiers.ts";
import { CrowdfundSubTitle, CrowdfundSubTitleRow } from "../../PublishFile/Upload-styles.tsx"; import {
CrowdfundSubTitle,
CrowdfundSubTitleRow,
} from "../../PublishIssue/Upload-styles.tsx";
interface CommentSectionProps { interface CommentSectionProps {
postId: string; postId: string;
@ -105,7 +108,7 @@ export const CommentSection = ({ postId, postName }: CommentSectionProps) => {
const offset = 0; const offset = 0;
const removeBaseCommentId = commentId.replace("_base_", ""); const removeBaseCommentId = commentId.replace("_base_", "");
const url = `/arbitrary/resources/search?mode=ALL&service=BLOG_COMMENT&query=${QSHARE_COMMENT_BASE}${postId.slice( const url = `/arbitrary/resources/search?mode=ALL&service=BLOG_COMMENT&query=${QSUPPORT_COMMENT_BASE}${postId.slice(
-12 -12
)}_reply_${removeBaseCommentId.slice( )}_reply_${removeBaseCommentId.slice(
-6 -6
@ -150,7 +153,7 @@ export const CommentSection = ({ postId, postName }: CommentSectionProps) => {
if (isNewMessages && numberOfComments) { if (isNewMessages && numberOfComments) {
offset = numberOfComments; offset = numberOfComments;
} }
const url = `/arbitrary/resources/search?mode=ALL&service=BLOG_COMMENT&query=${QSHARE_COMMENT_BASE}${postId.slice( const url = `/arbitrary/resources/search?mode=ALL&service=BLOG_COMMENT&query=${QSUPPORT_COMMENT_BASE}${postId.slice(
-12 -12
)}_base_&limit=20&includemetadata=false&offset=${offset}&reverse=false&excludeblocked=true`; )}_base_&limit=20&includemetadata=false&offset=${offset}&reverse=false&excludeblocked=true`;
const response = await fetch(url, { const response = await fetch(url, {
@ -218,11 +221,10 @@ export const CommentSection = ({ postId, postName }: CommentSectionProps) => {
return ( return (
<> <>
<Panel> <Panel>
<CrowdfundSubTitleRow > <CrowdfundSubTitleRow>
<CrowdfundSubTitle>Comments</CrowdfundSubTitle> <CrowdfundSubTitle>Comments</CrowdfundSubTitle>
</CrowdfundSubTitleRow> </CrowdfundSubTitleRow>
<CommentsContainer> <CommentsContainer>
{loadingComments ? ( {loadingComments ? (
<NoCommentsRow> <NoCommentsRow>

View File

@ -0,0 +1,47 @@
import { Box, Button } from "@mui/material";
import { styled } from "@mui/system";
import AddPhotoAlternateIcon from "@mui/icons-material/AddPhotoAlternate";
import { TimesSVG } from "./TimesSVG.tsx";
export const AddCoverImageButton = styled(Button)(({ theme }) => ({
display: "flex",
alignItems: "center",
fontFamily: "Montserrat",
fontSize: "16px",
fontWeight: 400,
letterSpacing: "0.2px",
color: "white",
gap: "5px",
}));
export const AddLogoIcon = styled(AddPhotoAlternateIcon)(({ theme }) => ({
color: "#fff",
height: "25px",
width: "auto",
}));
export const LogoPreviewRow = styled(Box)(({ theme }) => ({
display: "flex",
alignItems: "center",
gap: "10px",
}));
export const CoverImagePreview = styled("img")(({ theme }) => ({
width: "100px",
height: "100px",
objectFit: "contain",
userSelect: "none",
borderRadius: "3px",
marginBottom: "10px",
}));
export const TimesIcon = styled(TimesSVG)(({ theme }) => ({
backgroundColor: theme.palette.background.paper,
borderRadius: "50%",
padding: "5px",
transition: "all 0.2s ease-in-out",
"&:hover": {
cursor: "pointer",
scale: "1.1",
},
}));

View File

@ -0,0 +1,62 @@
import ImageUploader from "./ImageUploader.tsx";
import React, { useImperativeHandle, useState } from "react";
import {
AddCoverImageButton,
AddLogoIcon,
CoverImagePreview,
LogoPreviewRow,
TimesIcon,
} from "./ImagePublisher-styles.tsx";
import { useTheme } from "@mui/material";
export type ImagePublisherRef = {
getImageArray: () => string[];
};
interface ImagePublisherProps {
initialImages?: string[];
}
export const ImagePublisher = React.forwardRef<
ImagePublisherRef,
ImagePublisherProps
>(({ initialImages }: ImagePublisherProps, ref) => {
const theme = useTheme();
const [imageArray, setImageArray] = useState<string[]>(initialImages || []);
useImperativeHandle(ref, () => ({
getImageArray: () => {
return imageArray;
},
}));
return (
<>
{imageArray.length === 0 ? (
<ImageUploader onPick={(img: string[]) => setImageArray(img)}>
<AddCoverImageButton variant="contained">
Add Images
<AddLogoIcon
sx={{
height: "25px",
width: "auto",
}}
></AddLogoIcon>
</AddCoverImageButton>
</ImageUploader>
) : (
<LogoPreviewRow>
{imageArray.map(
image =>
image && <CoverImagePreview src={image} alt="logo" key={image} />
)}
<TimesIcon
color={theme.palette.text.primary}
onClickFunc={() => setImageArray([])}
height={"32"}
width={"32"}
></TimesIcon>
</LogoPreviewRow>
)}
</>
);
});

View File

@ -0,0 +1,109 @@
import React, { useCallback } from "react";
import { Box } from "@mui/material";
import {
DropzoneInputProps,
DropzoneRootProps,
useDropzone,
} from "react-dropzone";
import Compressor from "compressorjs";
import { setNotification } from "../../../state/features/notificationsSlice.ts";
import { useDispatch } from "react-redux";
const toBase64 = (file: File): Promise<string | ArrayBuffer | null> =>
new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => resolve(reader.result);
reader.onerror = error => {
reject(error);
};
});
interface ImageUploaderProps {
children: React.ReactNode;
onPick: (base64Img: string[]) => void;
}
export const ImageUploader: React.FC<ImageUploaderProps> = ({
children,
onPick,
}) => {
const dispatch = useDispatch();
const imageLimit = 3;
const compressImages = async (images: File[]) => {
const promises = images.map(image => {
return new Promise<File | Blob>(resolve => {
new Compressor(image, {
quality: 0.6,
maxWidth: 1200,
mimeType: "image/webp",
success(result) {
const file = new File([result], "name", {
type: "image/webp",
});
resolve(result);
},
error(err) {},
});
});
});
return await Promise.all(promises);
};
const onDrop = useCallback(
async (acceptedFiles: File[]) => {
if (acceptedFiles.length > imageLimit) {
const notificationObj = {
msg: `Only ${imageLimit} images can be published`,
alertType: "error",
};
dispatch(setNotification(notificationObj));
return;
}
try {
const compressedImages = await compressImages(acceptedFiles);
if (!compressedImages) return;
const base64Iamges = await Promise.all(
compressedImages.map(image => toBase64(image as File))
);
onPick(base64Iamges as string[]);
} catch (error) {
console.error(error);
}
},
[onPick]
);
const {
getRootProps,
getInputProps,
isDragActive,
}: {
getRootProps: () => DropzoneRootProps;
getInputProps: () => DropzoneInputProps;
isDragActive: boolean;
} = useDropzone({
onDrop,
accept: {
"image/*": [],
},
});
return (
<Box
{...getRootProps()}
sx={{
display: "flex",
}}
>
<input {...getInputProps()} />
{children}
</Box>
);
};
export default ImageUploader;

View File

@ -0,0 +1,28 @@
export interface IconTypes {
color?: string;
height: string;
width: string;
className?: string;
onClickFunc?: (e?: any) => void;
}
export const TimesSVG: React.FC<IconTypes> = ({
color,
height,
width,
className,
onClickFunc,
}) => {
return (
<svg
onClick={onClickFunc}
className={className}
fill={color}
xmlns="http://www.w3.org/2000/svg"
height={height}
viewBox="0 -960 960 960"
width={width}
>
<path d="m249-207-42-42 231-231-231-231 42-42 231 231 231-231 42 42-231 231 231 231-42 42-231-231-231 231Z" />
</svg>
);
};

View File

@ -1,89 +0,0 @@
import React, { useCallback } from 'react'
import { Box, Button, TextField, Typography, Modal } from '@mui/material'
import {
useDropzone,
DropzoneRootProps,
DropzoneInputProps
} from 'react-dropzone'
import Compressor from 'compressorjs'
const toBase64 = (file: File): Promise<string | ArrayBuffer | null> =>
new Promise((resolve, reject) => {
const reader = new FileReader()
reader.readAsDataURL(file)
reader.onload = () => resolve(reader.result)
reader.onerror = (error) => {
reject(error)
}
})
interface ImageUploaderProps {
children: React.ReactNode
onPick: (base64Img: string) => void
}
const ImageUploader: React.FC<ImageUploaderProps> = ({ children, onPick }) => {
const onDrop = useCallback(
async (acceptedFiles: File[]) => {
if (acceptedFiles.length > 1) {
return
}
let compressedFile: File | undefined
try {
const image = acceptedFiles[0]
await new Promise<void>((resolve) => {
new Compressor(image, {
quality: 0.6,
maxWidth: 1200,
mimeType: 'image/webp',
success(result) {
const file = new File([result], 'name', {
type: 'image/webp'
})
compressedFile = file
resolve()
},
error(err) {}
})
})
if (!compressedFile) return
const base64Img = await toBase64(compressedFile)
onPick(base64Img as string)
} catch (error) {
console.error(error)
}
},
[onPick]
)
const {
getRootProps,
getInputProps,
isDragActive
}: {
getRootProps: () => DropzoneRootProps
getInputProps: () => DropzoneInputProps
isDragActive: boolean
} = useDropzone({
onDrop,
accept: {
'image/*': []
}
})
return (
<Box
{...getRootProps()}
sx={{
display: 'flex'
}}
>
<input {...getInputProps()} />
{children}
</Box>
)
}
export default ImageUploader

View File

@ -14,26 +14,22 @@ export const CustomAppBar = styled(AppBar)(({ theme }) => ({
borderBottom: `1px solid ${theme.palette.primary.light}`, borderBottom: `1px solid ${theme.palette.primary.light}`,
backgroundColor: theme.palette.background.default, backgroundColor: theme.palette.background.default,
[theme.breakpoints.only("xs")]: { [theme.breakpoints.only("xs")]: {
gap: "15px" gap: "15px",
}, },
height: '55px' height: "100px",
})); }));
export const LogoContainer = styled("div")({ export const LogoContainer = styled("div")({
cursor: 'pointer', cursor: "pointer",
height: '100%', height: "100%",
display: 'flex', display: "flex",
alignItems: 'center' alignItems: "center",
}); });
export const CustomTitle = styled(Typography)({ export const CustomTitle = styled(Typography)({
fontWeight: 600, fontWeight: 600,
color: "#000000" color: "#000000",
}); });
export const AuthenticateButton = styled(Button)(({ theme }) => ({ export const AuthenticateButton = styled(Button)(({ theme }) => ({
display: "flex", display: "flex",
flexDirection: "row", flexDirection: "row",
@ -50,8 +46,8 @@ export const AuthenticateButton = styled(Button)(({ theme }) => ({
cursor: "pointer", cursor: "pointer",
boxShadow: "rgba(0, 0, 0, 0.15) 1.95px 1.95px 2.6px;", boxShadow: "rgba(0, 0, 0, 0.15) 1.95px 1.95px 2.6px;",
backgroundColor: theme.palette.secondary.dark, backgroundColor: theme.palette.secondary.dark,
filter: "brightness(1.1)" filter: "brightness(1.1)",
} },
})); }));
export const AvatarContainer = styled(Box)({ export const AvatarContainer = styled(Box)({
@ -61,9 +57,9 @@ export const AvatarContainer = styled(Box)({
cursor: "pointer", cursor: "pointer",
"& #expand-icon": { "& #expand-icon": {
transition: "all 0.3s ease-in-out", transition: "all 0.3s ease-in-out",
filter: "brightness(0.7)" filter: "brightness(0.7)",
} },
} },
}); });
export const DropdownContainer = styled(Box)(({ theme }) => ({ export const DropdownContainer = styled(Box)(({ theme }) => ({
@ -76,22 +72,22 @@ export const DropdownContainer = styled(Box)(({ theme }) => ({
"&:hover": { "&:hover": {
cursor: "pointer", cursor: "pointer",
filter: filter:
theme.palette.mode === "light" ? "brightness(0.95)" : "brightness(1.1)" theme.palette.mode === "light" ? "brightness(0.95)" : "brightness(1.1)",
} },
})); }));
export const DropdownText = styled(Typography)(({ theme }) => ({ export const DropdownText = styled(Typography)(({ theme }) => ({
fontFamily: "Raleway", fontFamily: "Raleway",
fontSize: "16px", fontSize: "16px",
color: theme.palette.text.primary, color: theme.palette.text.primary,
userSelect: "none" userSelect: "none",
})); }));
export const NavbarName = styled(Typography)(({ theme }) => ({ export const NavbarName = styled(Typography)(({ theme }) => ({
fontFamily: "Raleway", fontFamily: "Raleway",
fontSize: "18px", fontSize: "18px",
color: theme.palette.text.primary, color: theme.palette.text.primary,
margin: "0 10px" margin: "0 10px",
})); }));
export const ThemeSelectRow = styled(Box)({ export const ThemeSelectRow = styled(Box)({
@ -99,7 +95,7 @@ export const ThemeSelectRow = styled(Box)({
alignItems: "center", alignItems: "center",
gap: "5px", gap: "5px",
flexBasis: 0, flexBasis: 0,
height: '100%' height: "100%",
}); });
export const LightModeIcon = styled(LightModeSVG)(({ theme }) => ({ export const LightModeIcon = styled(LightModeSVG)(({ theme }) => ({
@ -109,8 +105,8 @@ export const LightModeIcon = styled(LightModeSVG)(({ theme }) => ({
filter: filter:
theme.palette.mode === "dark" theme.palette.mode === "dark"
? "drop-shadow(0px 4px 6px rgba(255, 255, 255, 0.6))" ? "drop-shadow(0px 4px 6px rgba(255, 255, 255, 0.6))"
: "drop-shadow(0px 4px 6px rgba(99, 88, 88, 0.1))" : "drop-shadow(0px 4px 6px rgba(99, 88, 88, 0.1))",
} },
})); }));
export const DarkModeIcon = styled(DarkModeSVG)(({ theme }) => ({ export const DarkModeIcon = styled(DarkModeSVG)(({ theme }) => ({
@ -120,6 +116,6 @@ export const DarkModeIcon = styled(DarkModeSVG)(({ theme }) => ({
filter: filter:
theme.palette.mode === "dark" theme.palette.mode === "dark"
? "drop-shadow(0px 4px 6px rgba(255, 255, 255, 0.6))" ? "drop-shadow(0px 4px 6px rgba(255, 255, 255, 0.6))"
: "drop-shadow(0px 4px 6px rgba(99, 88, 88, 0.1))" : "drop-shadow(0px 4px 6px rgba(99, 88, 88, 0.1))",
} },
})); }));

View File

@ -1,27 +1,15 @@
import React, { useState, useRef } from "react"; import React, { useRef, useState } from "react";
import { import { Box, Input, Popover, Typography, useTheme } from "@mui/material";
Box,
Button,
Input,
Popover,
Typography,
useTheme,
} from "@mui/material";
import ExitToAppIcon from "@mui/icons-material/ExitToApp";
import { BlockedNamesModal } from "../../common/BlockedNamesModal/BlockedNamesModal"; import { BlockedNamesModal } from "../../common/BlockedNamesModal/BlockedNamesModal";
import AddBoxIcon from "@mui/icons-material/AddBox";
import { import {
AvatarContainer, AvatarContainer,
CustomAppBar, CustomAppBar,
DropdownContainer, DropdownContainer,
DropdownText, DropdownText,
AuthenticateButton,
NavbarName,
LightModeIcon,
DarkModeIcon,
ThemeSelectRow,
LogoContainer, LogoContainer,
NavbarName,
ThemeSelectRow,
} from "./Navbar-styles"; } from "./Navbar-styles";
import { AccountCircleSVG } from "../../../assets/svgs/AccountCircleSVG"; import { AccountCircleSVG } from "../../../assets/svgs/AccountCircleSVG";
import BackspaceIcon from "@mui/icons-material/Backspace"; import BackspaceIcon from "@mui/icons-material/Backspace";
@ -32,18 +20,17 @@ import { useNavigate } from "react-router-dom";
import SearchIcon from "@mui/icons-material/Search"; import SearchIcon from "@mui/icons-material/Search";
import { DownloadTaskManager } from "../../common/DownloadTaskManager"; import { DownloadTaskManager } from "../../common/DownloadTaskManager";
import QShareLogo from "../../../assets/img/q-share-icon.webp"; import QSupportLogo from "../../../assets/img/Q-SupportIcon.webp";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { import {
addFilteredFiles, addFilteredFiles,
setEditPlaylist,
setFilterValue, setFilterValue,
setIsFiltering, setIsFiltering,
} from "../../../state/features/fileSlice.ts"; } from "../../../state/features/fileSlice.ts";
import { RootState } from "../../../state/store"; import { RootState } from "../../../state/store";
import { useWindowSize } from "../../../hooks/useWindowSize"; import { useWindowSize } from "../../../hooks/useWindowSize";
import { PublishFile } from "../../PublishFile/PublishFile.tsx"; import { PublishIssue } from "../../PublishIssue/PublishIssue.tsx";
import { StyledButton } from "../../PublishFile/Upload-styles.tsx";
interface Props { interface Props {
isAuthenticated: boolean; isAuthenticated: boolean;
userName: string | null; userName: string | null;
@ -125,21 +112,21 @@ const NavBar: React.FC<Props> = ({
}} }}
> >
<img <img
src={QShareLogo} src={QSupportLogo}
style={{ style={{
width: "auto", width: "auto",
height: "55px", height: "100px",
padding: "2px", padding: "2px",
}} }}
/> />
</LogoContainer> </LogoContainer>
<Typography <Typography
sx={{ sx={{
fontSize: "16px", fontSize: "30px",
whiteSpace: "nowrap", whiteSpace: "nowrap",
}} }}
> >
Sharing is caring Welcome to Q-Support
</Typography> </Typography>
</Box> </Box>
</ThemeSelectRow> </ThemeSelectRow>
@ -150,135 +137,6 @@ const NavBar: React.FC<Props> = ({
gap: "10px", gap: "10px",
}} }}
> >
{/* {windowSize.width <= 600 ? (
<Box
sx={{
display: 'flex',
alignItems: 'center',
gap: 1
}}
className="myClassOver600"
>
<Box onClick={openNotificationPopover}>
<SearchIcon
sx={{
cursor: 'pointer',
display: 'flex'
}}
/>
</Box>
{filterValue && (
<BackspaceIcon
sx={{
cursor: 'pointer'
}}
onClick={() => {
dispatch(setIsFiltering(false))
dispatch(setFilterValue(''))
dispatch(addFilteredVideos([]))
searchValRef.current = ''
if (!inputRef.current) return
inputRef.current.value = ''
}}
/>
)}
</Box>
): (
<Box
sx={{
display: 'flex',
alignItems: 'center',
gap: 1
}}
className="myClassUnder600"
>
<Input
id="standard-adornment-name"
inputRef={inputRef}
onChange={(e) => {
searchValRef.current = e.target.value
}}
onKeyDown={(event) => {
if (event.key === 'Enter' || event.keyCode === 13) {
if (!searchValRef.current) {
dispatch(setIsFiltering(false))
dispatch(setFilterValue(''))
dispatch(addFilteredVideos([]))
searchValRef.current = ''
if (!inputRef.current) return
inputRef.current.value = ''
return
}
navigate('/')
dispatch(setIsFiltering(true))
dispatch(addFilteredVideos([]))
dispatch(setFilterValue(searchValRef.current))
}
}}
placeholder="Search"
sx={{
'&&:before': {
borderBottom: 'none'
},
'&&:after': {
borderBottom: 'none'
},
'&&:hover:before': {
borderBottom: 'none'
},
'&&.Mui-focused:before': {
borderBottom: 'none'
},
'&&.Mui-focused': {
outline: 'none'
},
fontSize: '18px'
}}
/>
<SearchIcon
sx={{
cursor: 'pointer'
}}
onClick={() => {
if (!searchValRef.current) {
dispatch(setIsFiltering(false))
dispatch(setFilterValue(''))
dispatch(addFilteredVideos([]))
searchValRef.current = ''
if (!inputRef.current) return
inputRef.current.value = ''
return
}
navigate('/')
dispatch(setIsFiltering(true))
dispatch(addFilteredVideos([]))
dispatch(setFilterValue(searchValRef.current))
}}
/>
{filterValue && (
<BackspaceIcon
sx={{
cursor: 'pointer'
}}
onClick={() => {
dispatch(setIsFiltering(false))
dispatch(setFilterValue(''))
dispatch(addFilteredVideos([]))
searchValRef.current = ''
if (!inputRef.current) return
inputRef.current.value = ''
}}
/>
)}
</Box>
)} */}
<Popover <Popover
id={idNotification} id={idNotification}
open={openPopover} open={openPopover}
@ -411,7 +269,7 @@ const NavBar: React.FC<Props> = ({
<AvatarContainer> <AvatarContainer>
{isAuthenticated && userName && ( {isAuthenticated && userName && (
<> <>
<PublishFile /> <PublishIssue />
</> </>
)} )}
</AvatarContainer> </AvatarContainer>

View File

@ -7,15 +7,6 @@ import softwareIcon from "../../assets/icons/software.webp";
import unknownIcon from "../../assets/icons/unknown.webp"; import unknownIcon from "../../assets/icons/unknown.webp";
import videoIcon from "../../assets/icons/video.webp"; import videoIcon from "../../assets/icons/video.webp";
import {
audioSubCategories,
bookSubCategories,
documentSubCategories,
imageSubCategories,
softwareSubCategories,
videoSubCategories,
} from "./2ndCategories.ts";
import { musicSubCategories } from "./3rdCategories.ts";
import { import {
Categories, Categories,
Category, Category,
@ -25,30 +16,30 @@ import {
getAllCategoriesWithIcons, getAllCategoriesWithIcons,
sortCategory, sortCategory,
} from "./CategoryFunctions.ts"; } from "./CategoryFunctions.ts";
import { QappCategories, SupportState } from "./2ndCategories.ts";
export const firstCategories: Category[] = [ export const firstCategories: Category[] = [
{ id: 1, name: "Software", icon: softwareIcon }, { id: 1, name: "Core" },
{ id: 2, name: "Gaming", icon: gamingIcon }, { id: 2, name: "UI" },
{ id: 3, name: "Audio", icon: audioIcon }, { id: 3, name: "Q-Apps" },
{ id: 4, name: "Video", icon: videoIcon }, { id: 4, name: "Website" },
{ id: 5, name: "Image", icon: imageIcon }, { id: 5, name: "Marketing" },
{ id: 6, name: "Document", icon: documentIcon }, { id: 99, name: "Other" },
{ id: 7, name: "Book", icon: bookIcon }, ];
{ id: 99, name: "Other", icon: unknownIcon },
].sort(sortCategory);
export const secondCategories: Categories = { export const secondCategories: Categories = {
1: softwareSubCategories.sort(sortCategory), 1: SupportState,
3: audioSubCategories.sort(sortCategory), 2: SupportState,
4: videoSubCategories.sort(sortCategory), 3: QappCategories,
5: imageSubCategories.sort(sortCategory), 4: SupportState,
6: documentSubCategories.sort(sortCategory), 5: SupportState,
7: bookSubCategories.sort(sortCategory), 99: SupportState,
};
export const thirdCategories: Categories = {
301: musicSubCategories,
}; };
export let thirdCategories: Categories = {};
QappCategories.map(
supportStateCategory =>
(thirdCategories[supportStateCategory.id] = SupportState)
);
export const allCategoryData: CategoryData = { export const allCategoryData: CategoryData = {
category: firstCategories, category: firstCategories,
subCategories: [secondCategories, thirdCategories], subCategories: [secondCategories, thirdCategories],

View File

@ -1,88 +1,23 @@
export const softwareSubCategories = [ import OpenIcon from "../../assets/icons/OpenIcon.png";
{ id: 101, name: "OS" }, import ClosedIcon from "../../assets/icons/ClosedIcon.png";
{ id: 102, name: "Application" }, import InProgressIcon from "../../assets/icons/InProgressIcon.png";
{ id: 103, name: "Source Code" }, import CompleteIcon from "../../assets/icons/CompleteIcon.png";
{ id: 104, name: "Plugin" },
{ id: 199, name: "Other" }, export const SupportState = [
{ id: 101, name: "Open", icon: OpenIcon },
{ id: 102, name: "Closed", icon: ClosedIcon },
{ id: 103, name: "In Progress", icon: InProgressIcon },
{ id: 104, name: "Complete", icon: CompleteIcon },
]; ];
export const audioSubCategories = [ export const QappCategories = [
{ id: 301, name: "Music" }, { id: 301, name: "Q-Blog" },
{ id: 302, name: "Podcast" }, { id: 302, name: "Q-Mail" },
{ id: 303, name: "Audiobook" }, { id: 303, name: "Q-Shop" },
{ id: 304, name: "Sound Effect" }, { id: 304, name: "Q-Fund" },
{ id: 305, name: "Lecture or Speech" }, { id: 305, name: "Ear-Bump" },
{ id: 306, name: "Radio Show" }, { id: 306, name: "Q-Tube" },
{ id: 307, name: "Ambient Sound" }, { id: 307, name: "Q-Share" },
{ id: 308, name: "Language Learning Material" }, { id: 308, name: "Q-Support" },
{ id: 309, name: "Comedy & Satire" },
{ id: 310, name: "Documentary" },
{ id: 311, name: "Guided Meditation & Yoga" },
{ id: 312, name: "Live Performance" },
{ id: 313, name: "Nature Sound" },
{ id: 314, name: "Soundtrack" },
{ id: 315, name: "Interview" },
{ id: 399, name: "Other" }, { id: 399, name: "Other" },
]; ];
export const videoSubCategories = [
{ id: 404, name: "Education" },
{ id: 405, name: "Lifestyle" },
{ id: 406, name: "Gaming" },
{ id: 407, name: "Technology" },
{ id: 408, name: "Sports" },
{ id: 409, name: "News & Politics" },
{ id: 410, name: "Cooking & Food" },
{ id: 411, name: "Animation" },
{ id: 412, name: "Science" },
{ id: 413, name: "Health & Wellness" },
{ id: 414, name: "DIY & Crafts" },
{ id: 415, name: "Kids & Family" },
{ id: 416, name: "Comedy" },
{ id: 417, name: "Travel & Adventure" },
{ id: 418, name: "Art & Design" },
{ id: 419, name: "Nature & Environment" },
{ id: 420, name: "Business & Finance" },
{ id: 421, name: "Personal Development" },
{ id: 423, name: "History" },
{ id: 499, name: "Other" },
];
export const imageSubCategories = [
{ id: 501, name: "Nature" },
{ id: 502, name: "Urban & Cityscapes" },
{ id: 503, name: "People & Portraits" },
{ id: 504, name: "Art & Abstract" },
{ id: 505, name: "Travel & Adventure" },
{ id: 506, name: "Animals & Wildlife" },
{ id: 507, name: "Sports & Action" },
{ id: 508, name: "Food & Cuisine" },
{ id: 509, name: "Fashion & Beauty" },
{ id: 510, name: "Technology & Science" },
{ id: 511, name: "Historical & Cultural" },
{ id: 512, name: "Aerial & Drone" },
{ id: 513, name: "Black & White" },
{ id: 514, name: "Events & Celebrations" },
{ id: 515, name: "Business & Corporate" },
{ id: 516, name: "Health & Wellness" },
{ id: 517, name: "Transportation & Vehicles" },
{ id: 518, name: "Still Life & Objects" },
{ id: 519, name: "Architecture & Buildings" },
{ id: 520, name: "Landscapes & Seascapes" },
{ id: 599, name: "Other" },
];
export const documentSubCategories = [
{ id: 601, name: "PDF" },
{ id: 602, name: "Word Document" },
{ id: 603, name: "Spreadsheet" },
{ id: 604, name: "Powerpoint" },
{ id: 699, name: "Other" },
];
export const bookSubCategories = [
{ id: 701, name: "Audiobook" },
{ id: 702, name: "Comic" },
{ id: 703, name: "Magazine" },
{ id: 799, name: "Other" },
];

View File

@ -1,23 +0,0 @@
export const musicSubCategories = [
{ id: 30101, name: "Rock" },
{ id: 30102, name: "Pop" },
{ id: 30103, name: "Classical" },
{ id: 30104, name: "Jazz" },
{ id: 30105, name: "Electronic" },
{ id: 30106, name: "Country" },
{ id: 30107, name: "Hip Hop/Rap" },
{ id: 30108, name: "Blues" },
{ id: 30109, name: "R&B/Soul" },
{ id: 30110, name: "Reggae" },
{ id: 30111, name: "Folk" },
{ id: 30112, name: "Metal" },
{ id: 30113, name: "World Music" },
{ id: 30114, name: "Latin" },
{ id: 30115, name: "Indie" },
{ id: 30116, name: "Punk" },
{ id: 30117, name: "Soundtracks" },
{ id: 30118, name: "Children's Music" },
{ id: 30119, name: "New Age" },
{ id: 30120, name: "Classical Crossover" },
{ id: 30199, name: "Other" },
];

View File

@ -1,13 +1,13 @@
const useTestIdentifiers = false; const useTestIdentifiers = false;
export const QSHARE_FILE_BASE = useTestIdentifiers export const QSUPPORT_FILE_BASE = useTestIdentifiers
? "MYTEST_share_vid_" ? "MYTEST_support_issue_"
: "qshare_file_"; : "q_support_issue_";
export const QSHARE_PLAYLIST_BASE = useTestIdentifiers export const QSUPPORT_PLAYLIST_BASE = useTestIdentifiers
? "MYTEST_share_playlist_" ? "MYTEST_support_playlist_"
: "qshare_playlist_"; : "q_support_playlist_";
export const QSHARE_COMMENT_BASE = useTestIdentifiers export const QSUPPORT_COMMENT_BASE = useTestIdentifiers
? "qcomment_v1_MYTEST_" ? "qcomment_v1_MYTEST_support_"
: "qcomment_v1_qshare_"; : "qcomment_v1_q_support_";

View File

@ -1,3 +1,5 @@
export const minPriceSuperlike = 10; export const minPriceSuperlike = 10;
export const titleFormatter = /[^a-zA-Z0-9\s-_!?()&'",.;:|—~@#$%^*+=<>]/g; export const titleFormatter = /[^a-zA-Z0-9\s-_!?()&'",.;:|—~@#$%^*+=<>]/g;
export const titleFormatterOnSave = /[^a-zA-Z0-9\s-_!()&',.;—~@#$%^+=]/g; export const titleFormatterOnSave = /[^a-zA-Z0-9\s-_!()&',.;—~@#$%^+=]/g;
export const log = false;

View File

@ -19,8 +19,8 @@ import {
import { RootState } from "../state/store"; import { RootState } from "../state/store";
import { fetchAndEvaluateVideos } from "../utils/fetchVideos"; import { fetchAndEvaluateVideos } from "../utils/fetchVideos";
import { import {
QSHARE_PLAYLIST_BASE, QSUPPORT_PLAYLIST_BASE,
QSHARE_FILE_BASE, QSUPPORT_FILE_BASE,
} from "../constants/Identifiers.ts"; } from "../constants/Identifiers.ts";
import { RequestQueue } from "../utils/queue"; import { RequestQueue } from "../utils/queue";
import { queue } from "../wrappers/GlobalWrapper"; import { queue } from "../wrappers/GlobalWrapper";
@ -114,7 +114,7 @@ export const useFetchFiles = () => {
try { try {
dispatch(setIsLoadingGlobal(true)); dispatch(setIsLoadingGlobal(true));
const url = `/arbitrary/resources/search?mode=ALL&service=DOCUMENT&query=${QSHARE_FILE_BASE}&limit=20&includemetadata=false&reverse=true&excludeblocked=true&exactmatchnames=true`; const url = `/arbitrary/resources/search?mode=ALL&service=DOCUMENT&query=${QSUPPORT_FILE_BASE}&limit=20&includemetadata=false&reverse=true&excludeblocked=true&exactmatchnames=true`;
const response = await fetch(url, { const response = await fetch(url, {
method: "GET", method: "GET",
headers: { headers: {
@ -218,10 +218,10 @@ export const useFetchFiles = () => {
} }
if (type === "playlists") { if (type === "playlists") {
defaultUrl = defaultUrl + `&service=PLAYLIST`; defaultUrl = defaultUrl + `&service=PLAYLIST`;
defaultUrl = defaultUrl + `&identifier=${QSHARE_PLAYLIST_BASE}`; defaultUrl = defaultUrl + `&identifier=${QSUPPORT_PLAYLIST_BASE}`;
} else { } else {
defaultUrl = defaultUrl + `&service=DOCUMENT`; defaultUrl = defaultUrl + `&service=DOCUMENT`;
defaultUrl = defaultUrl + `&identifier=${QSHARE_FILE_BASE}`; defaultUrl = defaultUrl + `&identifier=${QSUPPORT_FILE_BASE}`;
} }
// const url = `/arbitrary/resources/search?mode=ALL&service=DOCUMENT&query=${QTUBE_VIDEO_BASE}&limit=${videoLimit}&includemetadata=false&reverse=true&excludeblocked=true&exactmatchnames=true&offset=${offset}` // const url = `/arbitrary/resources/search?mode=ALL&service=DOCUMENT&query=${QTUBE_VIDEO_BASE}&limit=${videoLimit}&includemetadata=false&reverse=true&excludeblocked=true&exactmatchnames=true&offset=${offset}`
@ -289,7 +289,7 @@ export const useFetchFiles = () => {
const offset = filteredVideos.length; const offset = filteredVideos.length;
const replaceSpacesWithUnderscore = filterValue.replace(/ /g, "_"); const replaceSpacesWithUnderscore = filterValue.replace(/ /g, "_");
const url = `/arbitrary/resources/search?mode=ALL&service=DOCUMENT&query=${replaceSpacesWithUnderscore}&identifier=${QSHARE_FILE_BASE}&limit=10&includemetadata=false&reverse=true&excludeblocked=true&exactmatchnames=true&offset=${offset}`; const url = `/arbitrary/resources/search?mode=ALL&service=DOCUMENT&query=${replaceSpacesWithUnderscore}&identifier=${QSUPPORT_FILE_BASE}&limit=10&includemetadata=false&reverse=true&excludeblocked=true&exactmatchnames=true&offset=${offset}`;
const response = await fetch(url, { const response = await fetch(url, {
method: "GET", method: "GET",
headers: { headers: {
@ -345,7 +345,7 @@ export const useFetchFiles = () => {
const checkNewFiles = React.useCallback(async () => { const checkNewFiles = React.useCallback(async () => {
try { try {
const url = `/arbitrary/resources/search?mode=ALL&service=DOCUMENT&query=${QSHARE_FILE_BASE}&limit=20&includemetadata=false&reverse=true&excludeblocked=true&exactmatchnames=true`; const url = `/arbitrary/resources/search?mode=ALL&service=DOCUMENT&query=${QSUPPORT_FILE_BASE}&limit=20&includemetadata=false&reverse=true&excludeblocked=true&exactmatchnames=true`;
const response = await fetch(url, { const response = await fetch(url, {
method: "GET", method: "GET",
headers: { headers: {
@ -382,7 +382,7 @@ export const useFetchFiles = () => {
const getFilesCount = React.useCallback(async () => { const getFilesCount = React.useCallback(async () => {
try { try {
let url = `/arbitrary/resources/search?mode=ALL&includemetadata=false&limit=0&service=DOCUMENT&identifier=${QSHARE_FILE_BASE}`; let url = `/arbitrary/resources/search?mode=ALL&includemetadata=false&limit=0&service=DOCUMENT&identifier=${QSUPPORT_FILE_BASE}`;
const response = await fetch(url, { const response = await fetch(url, {
method: "GET", method: "GET",

View File

@ -2,12 +2,12 @@ import { Avatar, Box, Skeleton, Tooltip } from "@mui/material";
import { import {
BlockIconContainer, BlockIconContainer,
BottomParent, BottomParent,
FileContainer,
IconsBox, IconsBox,
NameContainer, NameContainer,
VideoCard, VideoCard,
VideoCardName, VideoCardName,
VideoCardTitle, VideoCardTitle,
FileContainer,
VideoUploadDate, VideoUploadDate,
} from "./FileList-styles.tsx"; } from "./FileList-styles.tsx";
import EditIcon from "@mui/icons-material/Edit"; import EditIcon from "@mui/icons-material/Edit";
@ -18,7 +18,7 @@ import {
} from "../../state/features/fileSlice.ts"; } from "../../state/features/fileSlice.ts";
import BlockIcon from "@mui/icons-material/Block"; import BlockIcon from "@mui/icons-material/Block";
import AttachFileIcon from "@mui/icons-material/AttachFile"; import AttachFileIcon from "@mui/icons-material/AttachFile";
import { formatBytes } from "../FileContent/FileContent.tsx"; import { formatBytes } from "../IssueContent/IssueContent.tsx";
import { formatDate } from "../../utils/time.ts"; import { formatDate } from "../../utils/time.ts";
import React, { useState } from "react"; import React, { useState } from "react";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
@ -88,7 +88,7 @@ export const FileList = ({ files }: FileListProps) => {
}} }}
> >
{fileObj?.user === username && ( {fileObj?.user === username && (
<Tooltip title="Edit video properties" placement="top"> <Tooltip title="Edit Issue Properties" placement="top">
<BlockIconContainer> <BlockIconContainer>
<EditIcon <EditIcon
onClick={() => { onClick={() => {

View File

@ -19,8 +19,8 @@ import {
import { formatDate } from "../../utils/time"; import { formatDate } from "../../utils/time";
import { Video } from "../../state/features/fileSlice.ts"; import { Video } from "../../state/features/fileSlice.ts";
import { queue } from "../../wrappers/GlobalWrapper"; import { queue } from "../../wrappers/GlobalWrapper";
import { QSHARE_FILE_BASE } from "../../constants/Identifiers.ts"; import { QSUPPORT_FILE_BASE } from "../../constants/Identifiers.ts";
import { formatBytes } from "../FileContent/FileContent.tsx"; import { formatBytes } from "../IssueContent/IssueContent.tsx";
import { getIconsFromObject } from "../../constants/Categories/CategoryFunctions.ts"; import { getIconsFromObject } from "../../constants/Categories/CategoryFunctions.ts";
interface VideoListProps { interface VideoListProps {
@ -46,7 +46,7 @@ export const FileListComponentLevel = ({ mode }: VideoListProps) => {
const getVideos = React.useCallback(async () => { const getVideos = React.useCallback(async () => {
try { try {
const offset = videos.length; const offset = videos.length;
const url = `/arbitrary/resources/search?mode=ALL&service=DOCUMENT&query=${QSHARE_FILE_BASE}_&limit=50&includemetadata=false&reverse=true&excludeblocked=true&name=${paramName}&exactmatchnames=true&offset=${offset}`; const url = `/arbitrary/resources/search?mode=ALL&service=DOCUMENT&query=${QSUPPORT_FILE_BASE}_&limit=50&includemetadata=false&reverse=true&excludeblocked=true&name=${paramName}&exactmatchnames=true&offset=${offset}`;
const response = await fetch(url, { const response = await fetch(url, {
method: "GET", method: "GET",
headers: { headers: {

View File

@ -275,6 +275,8 @@ export const Home = ({ mode }: HomeProps) => {
}} }}
sx={{ sx={{
marginTop: "20px", marginTop: "20px",
fontWeight: 1000,
color: "white",
}} }}
variant="contained" variant="contained"
> >
@ -286,6 +288,8 @@ export const Home = ({ mode }: HomeProps) => {
}} }}
sx={{ sx={{
marginTop: "20px", marginTop: "20px",
fontWeight: 1000,
color: "white",
}} }}
variant="contained" variant="contained"
> >

View File

@ -5,11 +5,10 @@ import {
AuthorTextComment, AuthorTextComment,
StyledCardColComment, StyledCardColComment,
StyledCardHeaderComment, StyledCardHeaderComment,
} from "../FileContent/FileContent-styles.tsx"; } from "../IssueContent/IssueContent-styles.tsx";
import { Avatar, Box, useTheme } from "@mui/material"; import { Avatar, Box, useTheme } from "@mui/material";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import { setUserAvatarHash } from "../../state/features/globalSlice";
import { RootState } from "../../state/store"; import { RootState } from "../../state/store";
export const IndividualProfile = () => { export const IndividualProfile = () => {

View File

@ -1,5 +1,5 @@
import { styled } from "@mui/system"; import { styled } from "@mui/system";
import { Box, Grid, Typography, Checkbox } from "@mui/material"; import { Box, Typography } from "@mui/material";
export const FilePlayerContainer = styled(Box)(({ theme }) => ({ export const FilePlayerContainer = styled(Box)(({ theme }) => ({
maxWidth: "95%", maxWidth: "95%",
@ -25,6 +25,10 @@ export const FileDescription = styled(Typography)(({ theme }) => ({
wordBreak: "break-word", wordBreak: "break-word",
})); }));
export const ImageContainer = styled(Box)(({ theme }) => ({
display: "flex",
}));
export const Spacer = ({ height }: any) => { export const Spacer = ({ height }: any) => {
return ( return (
<Box <Box

View File

@ -14,29 +14,22 @@ import {
FileDescription, FileDescription,
FilePlayerContainer, FilePlayerContainer,
FileTitle, FileTitle,
ImageContainer,
Spacer, Spacer,
StyledCardColComment, StyledCardColComment,
StyledCardHeaderComment, StyledCardHeaderComment,
} from "./FileContent-styles.tsx"; } from "./IssueContent-styles.tsx";
import { formatDate } from "../../utils/time"; import { formatDate } from "../../utils/time";
import { CommentSection } from "../../components/common/Comments/CommentSection"; import { CommentSection } from "../../components/common/Comments/CommentSection";
import { QSHARE_FILE_BASE } from "../../constants/Identifiers.ts"; import { QSUPPORT_FILE_BASE } from "../../constants/Identifiers.ts";
import { DisplayHtml } from "../../components/common/TextEditor/DisplayHtml"; import { DisplayHtml } from "../../components/common/TextEditor/DisplayHtml";
import FileElement from "../../components/common/FileElement"; import FileElement from "../../components/common/FileElement";
import { import { allCategoryData } from "../../constants/Categories/1stCategories.ts";
allCategoryData,
iconCategories,
} from "../../constants/Categories/1stCategories.ts";
import { import {
Category, Category,
getCategoriesFromObject, getCategoriesFromObject,
} from "../../components/common/CategoryList/CategoryList.tsx"; } from "../../components/common/CategoryList/CategoryList.tsx";
import { import { getIconsFromObject } from "../../constants/Categories/CategoryFunctions.ts";
findAllCategoryData,
findCategoryData,
getCategoriesWithIcons,
getIconsFromObject,
} from "../../constants/Categories/CategoryFunctions.ts";
export function formatBytes(bytes, decimals = 2) { export function formatBytes(bytes, decimals = 2) {
if (bytes === 0) return "0 Bytes"; if (bytes === 0) return "0 Bytes";
@ -50,7 +43,7 @@ export function formatBytes(bytes, decimals = 2) {
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i]; return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i];
} }
export const FileContent = () => { export const IssueContent = () => {
const { name, id } = useParams(); const { name, id } = useParams();
const [isExpandedDescription, setIsExpandedDescription] = const [isExpandedDescription, setIsExpandedDescription] =
useState<boolean>(false); useState<boolean>(false);
@ -106,7 +99,7 @@ export const FileContent = () => {
if (!name || !id) return; if (!name || !id) return;
dispatch(setIsLoadingGlobal(true)); dispatch(setIsLoadingGlobal(true));
const url = `/arbitrary/resources/search?mode=ALL&service=DOCUMENT&query=${QSHARE_FILE_BASE}&limit=1&includemetadata=true&reverse=true&excludeblocked=true&name=${name}&exactmatchnames=true&offset=0&identifier=${id}`; const url = `/arbitrary/resources/search?mode=ALL&service=DOCUMENT&query=${QSUPPORT_FILE_BASE}&limit=1&includemetadata=true&reverse=true&excludeblocked=true&name=${name}&exactmatchnames=true&offset=0&identifier=${id}`;
const response = await fetch(url, { const response = await fetch(url, {
method: "GET", method: "GET",
headers: { headers: {
@ -272,8 +265,6 @@ export const FileContent = () => {
} }
} }
if (fileData) { if (fileData) {
//const icon = getIconsFromObject(fileData)[0]?.icon || null;
const icon = getIconsFromObject(fileData); const icon = getIconsFromObject(fileData);
setIcon(icon); setIcon(icon);
} }
@ -415,6 +406,19 @@ export const FileContent = () => {
{categoriesDisplay} {categoriesDisplay}
</Typography> </Typography>
</Box> </Box>
<ImageContainer>
{fileData?.images &&
fileData.images.map(image => {
return (
<img
src={image}
width={`${1280 / fileData.images.length}px`}
height={"480px"}
style={{ marginRight: "10px", marginBottom: "10px" }}
/>
);
})}
</ImageContainer>
<Spacer height="15px" /> <Spacer height="15px" />
<Box <Box
sx={{ sx={{

View File

@ -11,38 +11,38 @@ const commonThemeOptions = {
"Oxygen", "Oxygen",
"Catamaran", "Catamaran",
"Cairo", "Cairo",
"Arial" "Arial",
].join(","), ].join(","),
h1: { h1: {
fontSize: "2rem", fontSize: "2rem",
fontWeight: 600 fontWeight: 600,
}, },
h2: { h2: {
fontSize: "1.75rem", fontSize: "1.75rem",
fontWeight: 500 fontWeight: 500,
}, },
h3: { h3: {
fontSize: "1.5rem", fontSize: "1.5rem",
fontWeight: 500 fontWeight: 500,
}, },
h4: { h4: {
fontSize: "1.25rem", fontSize: "1.25rem",
fontWeight: 500 fontWeight: 500,
}, },
h5: { h5: {
fontSize: "1rem", fontSize: "1rem",
fontWeight: 500 fontWeight: 500,
}, },
h6: { h6: {
fontSize: "0.875rem", fontSize: "0.875rem",
fontWeight: 500 fontWeight: 500,
}, },
body1: { body1: {
fontSize: "23px", fontSize: "23px",
fontFamily: "Raleway", fontFamily: "Raleway",
fontWeight: 400, fontWeight: 400,
lineHeight: 1.5, lineHeight: 1.5,
letterSpacing: "0.5px" letterSpacing: "0.5px",
}, },
body2: { body2: {
@ -50,12 +50,12 @@ const commonThemeOptions = {
fontFamily: "Raleway, Arial", fontFamily: "Raleway, Arial",
fontWeight: 400, fontWeight: 400,
lineHeight: 1.4, lineHeight: 1.4,
letterSpacing: "0.2px" letterSpacing: "0.2px",
} },
}, },
spacing: 8, spacing: 8,
shape: { shape: {
borderRadius: 4 borderRadius: 4,
}, },
breakpoints: { breakpoints: {
values: { values: {
@ -63,8 +63,8 @@ const commonThemeOptions = {
sm: 600, sm: 600,
md: 900, md: 900,
lg: 1200, lg: 1200,
xl: 1536 xl: 1536,
} },
}, },
components: { components: {
MuiButton: { MuiButton: {
@ -73,16 +73,16 @@ const commonThemeOptions = {
backgroundColor: "inherit", backgroundColor: "inherit",
transition: "filter 0.3s ease-in-out", transition: "filter 0.3s ease-in-out",
"&:hover": { "&:hover": {
filter: "brightness(1.1)" filter: "brightness(1.1)",
} },
} },
}, },
defaultProps: { defaultProps: {
disableElevation: true, disableElevation: true,
disableRipple: true disableRipple: true,
} },
} },
} },
}; };
const lightTheme = createTheme({ const lightTheme = createTheme({
@ -92,20 +92,20 @@ const lightTheme = createTheme({
primary: { primary: {
main: "#ffffff", main: "#ffffff",
dark: "#F5F5F5", dark: "#F5F5F5",
light: "#FCFCFC" light: "#FCFCFC",
}, },
secondary: { secondary: {
main: "#417Ed4", main: "#417Ed4",
dark: "#3e74c1" dark: "#3e74c1",
}, },
background: { background: {
default: "#fcfcfc", default: "#fcfcfc",
paper: "#F5F5F5" paper: "#F5F5F5",
}, },
text: { text: {
primary: "#000000", primary: "#000000",
secondary: "#525252" secondary: "#525252",
} },
}, },
components: { components: {
MuiCard: { MuiCard: {
@ -118,19 +118,19 @@ const lightTheme = createTheme({
"&:hover": { "&:hover": {
cursor: "pointer", cursor: "pointer",
boxShadow: boxShadow:
"rgba(0, 0, 0, 0.1) 0px 4px 6px -1px, rgba(0, 0, 0, 0.06) 0px 2px 4px -1px;" "rgba(0, 0, 0, 0.1) 0px 4px 6px -1px, rgba(0, 0, 0, 0.06) 0px 2px 4px -1px;",
} },
} },
} },
}, },
MuiIcon: { MuiIcon: {
defaultProps: { defaultProps: {
style: { style: {
color: "#000000" color: "#000000",
} },
} },
} },
} },
}); });
const darkTheme = createTheme({ const darkTheme = createTheme({
@ -138,23 +138,23 @@ const darkTheme = createTheme({
palette: { palette: {
mode: "dark", mode: "dark",
primary: { primary: {
main: "#FF1493", // Neon pink main: "#01a9e9", //
dark: "#C6127A", // Darker shade of neon pink dark: "#008fcd", //
light: "#FF5EC4" // Lighter shade of neon pink light: "#44c4ff", //
}, },
secondary: { secondary: {
main: "#007FFF", // Electric blue main: "#007FFF", // Electric blue
dark: "#0059B2", // Darker shade of electric blue dark: "#0059B2", // Darker shade of electric blue
light: "#3399FF" // Lighter shade of electric blue light: "#3399FF", // Lighter shade of electric blue
}, },
background: { background: {
default: "#1C1C1C", // Deep space black default: "#1C1C1C", // Deep space black
paper: "#342F41" // Dark cyberpunk-style purple paper: "#342F41", // Dark cyberpunk-style purple
}, },
text: { text: {
primary: "#ffffff", primary: "#ffffff",
secondary: "#b3b3b3" secondary: "#b3b3b3",
} },
}, },
components: { components: {
MuiCard: { MuiCard: {
@ -165,20 +165,20 @@ const darkTheme = createTheme({
transition: "all 0.3s ease-in-out", transition: "all 0.3s ease-in-out",
"&:hover": { "&:hover": {
cursor: "pointer", cursor: "pointer",
boxShadow: "0px 3px 4px 0px hsla(0,0%,0%,0.14), 0px 3px 3px -2px hsla(0,0%,0%,0.12), 0px 1px 8px 0px hsla(0,0%,0%,0.2);" boxShadow:
} "0px 3px 4px 0px hsla(0,0%,0%,0.14), 0px 3px 3px -2px hsla(0,0%,0%,0.12), 0px 1px 8px 0px hsla(0,0%,0%,0.2);",
} },
} },
},
}, },
MuiIcon: { MuiIcon: {
defaultProps: { defaultProps: {
style: { style: {
color: "#ffffff" color: "#ffffff",
} },
} },
} },
} },
}); });
export { lightTheme, darkTheme }; export { lightTheme, darkTheme };

View File

@ -1,9 +1,9 @@
import React, { import React, {
useEffect,
useState,
useCallback, useCallback,
useRef, useEffect,
useMemo, useMemo,
useRef,
useState,
} from "react"; } from "react";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
@ -15,7 +15,7 @@ import { setUserAvatarHash } from "../state/features/globalSlice";
import { VideoPlayerGlobal } from "../components/common/VideoPlayerGlobal"; import { VideoPlayerGlobal } from "../components/common/VideoPlayerGlobal";
import { Rnd } from "react-rnd"; import { Rnd } from "react-rnd";
import { RequestQueue } from "../utils/queue"; import { RequestQueue } from "../utils/queue";
import { EditFile } from "../components/EditFile/EditFile.tsx"; import { EditIssue } from "../components/EditIssue/EditIssue.tsx";
import { EditPlaylist } from "../components/EditPlaylist/EditPlaylist"; import { EditPlaylist } from "../components/EditPlaylist/EditPlaylist";
import ConsentModal from "../components/common/ConsentModal"; import ConsentModal from "../components/common/ConsentModal";
@ -138,7 +138,7 @@ const GlobalWrapper: React.FC<Props> = ({ children, setTheme }) => {
userAvatar={userAvatar} userAvatar={userAvatar}
authenticate={askForAccountInformation} authenticate={askForAccountInformation}
/> />
<EditFile /> <EditIssue />
<EditPlaylist /> <EditPlaylist />
<Rnd <Rnd
onDragStart={onDragStart} onDragStart={onDragStart}