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
854
package-lock.json
generated
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "qtube",
|
||||
"name": "qsupport",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@ -43,6 +43,6 @@
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-react-refresh": "^0.3.4",
|
||||
"typescript": "^5.0.2",
|
||||
"vite": "^4.3.2"
|
||||
"vite": "6.0.0-alpha.1"
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,14 @@
|
||||
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 { CssBaseline } from "@mui/material";
|
||||
import { lightTheme, darkTheme } from "./styles/theme";
|
||||
import { darkTheme, lightTheme } from "./styles/theme";
|
||||
import { store } from "./state/store";
|
||||
import { Provider } from "react-redux";
|
||||
import GlobalWrapper from "./wrappers/GlobalWrapper";
|
||||
import Notification from "./components/common/Notification/Notification";
|
||||
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 { IndividualProfile } from "./pages/IndividualProfile/IndividualProfile";
|
||||
|
||||
@ -26,7 +26,7 @@ function App() {
|
||||
<CssBaseline />
|
||||
<Routes>
|
||||
<Route path="/" element={<Home />} />
|
||||
<Route path="/share/:name/:id" element={<FileContent />} />
|
||||
<Route path="/share/:name/:id" element={<IssueContent />} />
|
||||
<Route path="/channel/:name" element={<IndividualProfile />} />
|
||||
</Routes>
|
||||
</GlobalWrapper>
|
||||
|
BIN
src/assets/icons/ClosedIcon.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
src/assets/icons/CompleteIcon.png
Normal file
After Width: | Height: | Size: 132 KiB |
BIN
src/assets/icons/InProgressIcon.png
Normal file
After Width: | Height: | Size: 7.1 KiB |
BIN
src/assets/icons/OpenIcon.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
src/assets/img/Q-SupportIcon.webp
Normal file
After Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 9.3 KiB |
Before Width: | Height: | Size: 13 KiB |
@ -21,7 +21,7 @@ import {
|
||||
updateFile,
|
||||
updateInHashMap,
|
||||
} 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 { TextEditor } from "../common/TextEditor/TextEditor";
|
||||
import { extractTextFromHTML } from "../common/TextEditor/utils";
|
||||
@ -32,6 +32,10 @@ import {
|
||||
CategoryListRef,
|
||||
getCategoriesFromObject,
|
||||
} from "../common/CategoryList/CategoryList.tsx";
|
||||
import {
|
||||
ImagePublisher,
|
||||
ImagePublisherRef,
|
||||
} from "../common/ImagePublisher/ImagePublisher.tsx";
|
||||
|
||||
const uid = new ShortUniqueId();
|
||||
const shortuid = new ShortUniqueId({ length: 5 });
|
||||
@ -53,7 +57,7 @@ interface VideoFile {
|
||||
identifier?: string;
|
||||
filename?: string;
|
||||
}
|
||||
export const EditFile = () => {
|
||||
export const EditIssue = () => {
|
||||
const theme = useTheme();
|
||||
const dispatch = useDispatch();
|
||||
const username = useSelector((state: RootState) => state.auth?.user?.name);
|
||||
@ -75,6 +79,7 @@ export const EditFile = () => {
|
||||
const [files, setFiles] = useState<VideoFile[]>([]);
|
||||
const [editCategories, setEditCategories] = useState<string[]>([]);
|
||||
const categoryListRef = useRef<CategoryListRef>(null);
|
||||
const imagePublisherRef = useRef<ImagePublisherRef>(null);
|
||||
|
||||
const { getRootProps, getInputProps } = useDropzone({
|
||||
maxFiles: 10,
|
||||
@ -121,7 +126,10 @@ export const EditFile = () => {
|
||||
const paragraph = `<p>${editFileProperties?.fullDescription}</p>`;
|
||||
setDescription(paragraph);
|
||||
}
|
||||
setEditCategories(getCategoriesFromObject(editFileProperties));
|
||||
|
||||
const categoriesFromEditFile =
|
||||
getCategoriesFromObject(editFileProperties);
|
||||
setEditCategories(categoriesFromEditFile);
|
||||
}
|
||||
}, [editFileProperties]);
|
||||
const onClose = () => {
|
||||
@ -141,7 +149,6 @@ export const EditFile = () => {
|
||||
if (!categoryList[0]) throw new Error("Please select a category");
|
||||
if (!editFileProperties) return;
|
||||
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 name = "";
|
||||
@ -186,7 +193,7 @@ export const EditFile = () => {
|
||||
const file = publish.file;
|
||||
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 = "";
|
||||
const fileExtensionSplit = file?.name?.split(".");
|
||||
@ -227,7 +234,7 @@ export const EditFile = () => {
|
||||
description: metadescription,
|
||||
identifier,
|
||||
filename,
|
||||
tag1: QSHARE_FILE_BASE,
|
||||
tag1: QSUPPORT_FILE_BASE,
|
||||
};
|
||||
listOfPublishes.push(requestBodyVideo);
|
||||
fileReferences.push({
|
||||
@ -248,24 +255,25 @@ export const EditFile = () => {
|
||||
commentsId: editFileProperties.commentsId,
|
||||
...categoryListRef.current?.categoriesToObject(),
|
||||
files: fileReferences,
|
||||
images: imagePublisherRef?.current?.getImageArray(),
|
||||
};
|
||||
|
||||
let metadescription =
|
||||
`**${categoryListRef.current?.getCategoriesFetchString()}**` +
|
||||
fullDescription.slice(0, 150);
|
||||
|
||||
const crowdfundObjectToBase64 = await objectToBase64(fileObject);
|
||||
const fileObjectToBase64 = await objectToBase64(fileObject);
|
||||
// Description is obtained from raw data
|
||||
|
||||
const requestBodyJson: any = {
|
||||
action: "PUBLISH_QDN_RESOURCE",
|
||||
name: name,
|
||||
service: "DOCUMENT",
|
||||
data64: crowdfundObjectToBase64,
|
||||
data64: fileObjectToBase64,
|
||||
title: title.slice(0, 50),
|
||||
description: metadescription,
|
||||
identifier: editFileProperties.id,
|
||||
tag1: QSHARE_FILE_BASE,
|
||||
tag1: QSUPPORT_FILE_BASE,
|
||||
filename: `video_metadata.json`,
|
||||
};
|
||||
listOfPublishes.push(requestBodyJson);
|
||||
@ -336,7 +344,7 @@ export const EditFile = () => {
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<NewCrowdfundTitle>Update share</NewCrowdfundTitle>
|
||||
<NewCrowdfundTitle>Update Issue</NewCrowdfundTitle>
|
||||
</Box>
|
||||
<>
|
||||
<Box
|
||||
@ -390,56 +398,52 @@ export const EditFile = () => {
|
||||
alignItems: "flex-start",
|
||||
}}
|
||||
>
|
||||
{files?.length > 0 && (
|
||||
<>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "20px",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<CategoryList
|
||||
categoryData={allCategoryData}
|
||||
initialCategories={editCategories}
|
||||
columns={3}
|
||||
ref={categoryListRef}
|
||||
/>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "20px",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<CategoryList
|
||||
categoryData={allCategoryData}
|
||||
initialCategories={editCategories}
|
||||
columns={3}
|
||||
ref={categoryListRef}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
{files?.length > 0 && (
|
||||
<>
|
||||
<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
|
||||
sx={{
|
||||
fontSize: "18px",
|
||||
}}
|
||||
>
|
||||
Description of share
|
||||
</Typography>
|
||||
<TextEditor
|
||||
inlineContent={description}
|
||||
setInlineContent={value => {
|
||||
setDescription(value);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<ImagePublisher
|
||||
ref={imagePublisherRef}
|
||||
initialImages={editFileProperties?.images}
|
||||
/>
|
||||
<CustomInputField
|
||||
name="title"
|
||||
label="Title of Issue"
|
||||
variant="filled"
|
||||
value={title}
|
||||
onChange={e => {
|
||||
const value = e.target.value;
|
||||
const formattedValue = value.replace(titleFormatter, "");
|
||||
setTitle(formattedValue);
|
||||
}}
|
||||
inputProps={{ maxLength: 180 }}
|
||||
required
|
||||
/>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "18px",
|
||||
}}
|
||||
>
|
||||
Description of Issue
|
||||
</Typography>
|
||||
<TextEditor
|
||||
inlineContent={description}
|
||||
setInlineContent={value => {
|
||||
setDescription(value);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
|
||||
<CrowdfundActionButtonRow>
|
@ -6,11 +6,9 @@ import {
|
||||
CrowdfundActionButton,
|
||||
CrowdfundActionButtonRow,
|
||||
CustomInputField,
|
||||
CustomSelect,
|
||||
LogoPreviewRow,
|
||||
ModalBody,
|
||||
NewCrowdfundTitle,
|
||||
StyledButton,
|
||||
TimesIcon,
|
||||
} from "./Upload-styles.tsx";
|
||||
import {
|
||||
@ -27,27 +25,20 @@ import {
|
||||
} from "@mui/material";
|
||||
import ShortUniqueId from "short-unique-id";
|
||||
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 { objectToBase64, uint8ArrayToBase64 } from "../../utils/toBase64";
|
||||
import { objectToBase64 } from "../../utils/toBase64";
|
||||
import { RootState } from "../../state/store";
|
||||
import {
|
||||
upsertFilesBeginning,
|
||||
addToHashMap,
|
||||
upsertFiles,
|
||||
setEditFile,
|
||||
setEditPlaylist,
|
||||
updateFile,
|
||||
updateInHashMap,
|
||||
setEditPlaylist,
|
||||
} from "../../state/features/fileSlice.ts";
|
||||
import ImageUploader from "../common/ImageUploader";
|
||||
import ImageUploader from "../common/ImagePublisher/ImageUploader.tsx";
|
||||
import {
|
||||
QSHARE_PLAYLIST_BASE,
|
||||
QSHARE_FILE_BASE,
|
||||
QSUPPORT_FILE_BASE,
|
||||
QSUPPORT_PLAYLIST_BASE,
|
||||
} from "../../constants/Identifiers.ts";
|
||||
import { Playlists } from "../Playlists/Playlists";
|
||||
import { PlaylistListEdit } from "../PlaylistListEdit/PlaylistListEdit";
|
||||
import { TextEditor } from "../common/TextEditor/TextEditor";
|
||||
import { extractTextFromHTML } from "../common/TextEditor/utils";
|
||||
@ -87,7 +78,7 @@ export const EditPlaylist = () => {
|
||||
const [playlistData, setPlaylistData] = useState<any>(null);
|
||||
const [title, setTitle] = useState<string>("");
|
||||
const [description, setDescription] = useState<string>("");
|
||||
const [coverImage, setCoverImage] = useState<string>("");
|
||||
const [coverImage, setCoverImage] = useState<string[]>([]);
|
||||
const [videos, setVideos] = useState([]);
|
||||
const [selectedCategoryVideos, setSelectedCategoryVideos] =
|
||||
useState<any>(null);
|
||||
@ -222,7 +213,7 @@ export const EditPlaylist = () => {
|
||||
setPlaylistData(null);
|
||||
setSelectedCategoryVideos(null);
|
||||
setSelectedSubCategoryVideos(null);
|
||||
setCoverImage("");
|
||||
setCoverImage([]);
|
||||
dispatch(setEditPlaylist(null));
|
||||
};
|
||||
|
||||
@ -292,7 +283,7 @@ export const EditPlaylist = () => {
|
||||
let commentsId = editVideoProperties?.id;
|
||||
|
||||
if (isNew) {
|
||||
commentsId = `${QSHARE_PLAYLIST_BASE}_cm_${id}`;
|
||||
commentsId = `${QSUPPORT_PLAYLIST_BASE}_cm_${id}`;
|
||||
}
|
||||
const stringDescription = extractTextFromHTML(description);
|
||||
|
||||
@ -324,7 +315,7 @@ export const EditPlaylist = () => {
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
if (isNew) {
|
||||
identifier = `${QSHARE_PLAYLIST_BASE}${sanitizeTitle.slice(0, 30)}_${id}`;
|
||||
identifier = `${QSUPPORT_PLAYLIST_BASE}${sanitizeTitle.slice(0, 30)}_${id}`;
|
||||
}
|
||||
const requestBodyJson: any = {
|
||||
action: "PUBLISH_QDN_RESOURCE",
|
||||
@ -334,7 +325,7 @@ export const EditPlaylist = () => {
|
||||
title: title.slice(0, 50),
|
||||
description: metadescription,
|
||||
identifier: identifier,
|
||||
tag1: QSHARE_FILE_BASE,
|
||||
tag1: QSUPPORT_FILE_BASE,
|
||||
};
|
||||
|
||||
await qortalRequest(requestBodyJson);
|
||||
@ -519,7 +510,7 @@ export const EditPlaylist = () => {
|
||||
</Box>
|
||||
<React.Fragment>
|
||||
{!coverImage ? (
|
||||
<ImageUploader onPick={(img: string) => setCoverImage(img)}>
|
||||
<ImageUploader onPick={(img: string[]) => setCoverImage(img)}>
|
||||
<AddCoverImageButton variant="contained">
|
||||
Add Cover Image
|
||||
<AddLogoIcon
|
||||
@ -532,10 +523,15 @@ export const EditPlaylist = () => {
|
||||
</ImageUploader>
|
||||
) : (
|
||||
<LogoPreviewRow>
|
||||
<CoverImagePreview src={coverImage} alt="logo" />
|
||||
{coverImage.map(
|
||||
image =>
|
||||
image && (
|
||||
<CoverImagePreview src={image} alt="logo" key={image} />
|
||||
)
|
||||
)}
|
||||
<TimesIcon
|
||||
color={theme.palette.text.primary}
|
||||
onClickFunc={() => setCoverImage("")}
|
||||
onClickFunc={() => setCoverImage([])}
|
||||
height={"32"}
|
||||
width={"32"}
|
||||
></TimesIcon>
|
||||
|
@ -3,15 +3,15 @@ import { CardContentContainerComment } from "../common/Comments/Comments-styles"
|
||||
import {
|
||||
CrowdfundSubTitle,
|
||||
CrowdfundSubTitleRow,
|
||||
} from "../PublishFile/Upload-styles.tsx";
|
||||
} from "../PublishIssue/Upload-styles.tsx";
|
||||
import { Box, Button, Input, Typography, useTheme } from "@mui/material";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import DeleteOutlineIcon from "@mui/icons-material/DeleteOutline";
|
||||
import { removeFile } from "../../state/features/fileSlice.ts";
|
||||
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 { RootState } from "../../state/store";
|
||||
|
||||
export const PlaylistListEdit = ({ playlistData, removeVideo, addVideo }) => {
|
||||
const theme = useTheme();
|
||||
const navigate = useNavigate();
|
||||
@ -20,7 +20,7 @@ export const PlaylistListEdit = ({ playlistData, removeVideo, addVideo }) => {
|
||||
const [searchResults, setSearchResults] = useState([]);
|
||||
const [filterSearch, setFilterSearch] = useState("");
|
||||
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, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
|
@ -1,66 +1,79 @@
|
||||
import React from 'react'
|
||||
import { CardContentContainerComment } from '../common/Comments/Comments-styles'
|
||||
import { CrowdfundSubTitle, CrowdfundSubTitleRow } from '../PublishFile/Upload-styles.tsx'
|
||||
import { Box, Typography, useTheme } from '@mui/material'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
|
||||
export const Playlists = ({playlistData, currentVideoIdentifier}) => {
|
||||
const theme = useTheme();
|
||||
const navigate = useNavigate()
|
||||
import React from "react";
|
||||
import { CardContentContainerComment } from "../common/Comments/Comments-styles";
|
||||
import {
|
||||
CrowdfundSubTitle,
|
||||
CrowdfundSubTitleRow,
|
||||
} from "../PublishIssue/Upload-styles.tsx";
|
||||
import { Box, Typography, useTheme } from "@mui/material";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
export const Playlists = ({ playlistData, currentVideoIdentifier }) => {
|
||||
const theme = useTheme();
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
|
||||
maxWidth: '400px',
|
||||
width: '100%'
|
||||
}}>
|
||||
<CrowdfundSubTitleRow >
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
|
||||
maxWidth: "400px",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<CrowdfundSubTitleRow>
|
||||
<CrowdfundSubTitle>Playlist</CrowdfundSubTitle>
|
||||
</CrowdfundSubTitleRow>
|
||||
<CardContentContainerComment sx={{
|
||||
marginTop: '25px',
|
||||
height: '450px',
|
||||
overflow: 'auto'
|
||||
}}>
|
||||
{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
|
||||
<CardContentContainerComment
|
||||
sx={{
|
||||
marginTop: "25px",
|
||||
height: "450px",
|
||||
overflow: "auto",
|
||||
}}
|
||||
>
|
||||
{playlistData?.videos?.map((vid, index) => {
|
||||
const isCurrentVidPlayling =
|
||||
vid?.identifier === currentVideoIdentifier;
|
||||
|
||||
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={{
|
||||
fontSize: '14px'
|
||||
}}>{index + 1}</Typography>
|
||||
<Typography sx={{
|
||||
fontSize: '18px',
|
||||
wordBreak: 'break-word'
|
||||
}}>{vid?.metadata?.title}</Typography>
|
||||
|
||||
</Box>
|
||||
)
|
||||
>
|
||||
{index + 1}
|
||||
</Typography>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "18px",
|
||||
wordBreak: "break-word",
|
||||
}}
|
||||
>
|
||||
{vid?.metadata?.title}
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
</CardContentContainerComment>
|
||||
</CardContentContainerComment>
|
||||
</Box>
|
||||
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
CrowdfundActionButton,
|
||||
CrowdfundActionButtonRow,
|
||||
ActionButton,
|
||||
ActionButtonRow,
|
||||
CustomInputField,
|
||||
ModalBody,
|
||||
NewCrowdfundTitle,
|
||||
@ -17,16 +17,22 @@ import { useDropzone } from "react-dropzone";
|
||||
import { setNotification } from "../../state/features/notificationsSlice";
|
||||
import { objectToBase64 } from "../../utils/toBase64";
|
||||
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 { TextEditor } from "../common/TextEditor/TextEditor";
|
||||
import { extractTextFromHTML } from "../common/TextEditor/utils";
|
||||
import { allCategoryData } from "../../constants/Categories/1stCategories.ts";
|
||||
import { titleFormatter } from "../../constants/Misc.ts";
|
||||
import {
|
||||
appendCategoryToList,
|
||||
CategoryList,
|
||||
CategoryListRef,
|
||||
} 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 shortuid = new ShortUniqueId({ length: 5 });
|
||||
@ -46,7 +52,7 @@ interface VideoFile {
|
||||
description: string;
|
||||
coverImage?: string;
|
||||
}
|
||||
export const PublishFile = ({ editId, editContent }: NewCrowdfundProps) => {
|
||||
export const PublishIssue = ({ editId, editContent }: NewCrowdfundProps) => {
|
||||
const theme = useTheme();
|
||||
const dispatch = useDispatch();
|
||||
const [isOpenMultiplePublish, setIsOpenMultiplePublish] = useState(false);
|
||||
@ -73,7 +79,7 @@ export const PublishFile = ({ editId, editContent }: NewCrowdfundProps) => {
|
||||
const [playlistSetting, setPlaylistSetting] = useState<null | string>(null);
|
||||
const [publishes, setPublishes] = useState<any>(null);
|
||||
const categoryListRef = useRef<CategoryListRef>(null);
|
||||
|
||||
const imagePublisherRef = useRef<ImagePublisherRef>(null);
|
||||
const { getRootProps, getInputProps } = useDropzone({
|
||||
maxFiles: 10,
|
||||
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 (!categoryListRef.current?.getSelectedCategories()[0])
|
||||
throw new Error("Please select a category");
|
||||
if (files.length === 0) throw new Error("Add at least one file");
|
||||
let errorMsg = "";
|
||||
let name = "";
|
||||
if (username) {
|
||||
@ -169,7 +174,7 @@ export const PublishFile = ({ editId, editContent }: NewCrowdfundProps) => {
|
||||
const file = publish.file;
|
||||
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 = "";
|
||||
const fileExtensionSplit = file?.name?.split(".");
|
||||
@ -197,9 +202,12 @@ export const PublishFile = ({ editId, editContent }: NewCrowdfundProps) => {
|
||||
filename = alphanumericString;
|
||||
}
|
||||
|
||||
let metadescription =
|
||||
`**${categoryListRef.current?.getCategoriesFetchString()}**` +
|
||||
fullDescription.slice(0, 150);
|
||||
const categoryList = appendCategoryToList(
|
||||
categoryListRef.current?.getSelectedCategories(),
|
||||
"101"
|
||||
);
|
||||
const categoryString = `**${categoryListRef.current?.getCategoriesFetchString(categoryList)}**`;
|
||||
let metadescription = categoryString + fullDescription.slice(0, 150);
|
||||
|
||||
const requestBodyVideo: any = {
|
||||
action: "PUBLISH_QDN_RESOURCE",
|
||||
@ -210,7 +218,7 @@ export const PublishFile = ({ editId, editContent }: NewCrowdfundProps) => {
|
||||
description: metadescription,
|
||||
identifier,
|
||||
filename,
|
||||
tag1: QSHARE_FILE_BASE,
|
||||
tag1: QSUPPORT_FILE_BASE,
|
||||
};
|
||||
listOfPublishes.push(requestBodyVideo);
|
||||
fileReferences.push({
|
||||
@ -224,32 +232,38 @@ export const PublishFile = ({ editId, editContent }: NewCrowdfundProps) => {
|
||||
}
|
||||
|
||||
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 = {
|
||||
title,
|
||||
version: 1,
|
||||
fullDescription,
|
||||
htmlDescription: description,
|
||||
commentsId: `${QSHARE_FILE_BASE}_cm_${idMeta}`,
|
||||
...categoryListRef.current?.categoriesToObject(),
|
||||
commentsId: `${QSUPPORT_FILE_BASE}_cm_${idMeta}`,
|
||||
...categoryListRef.current?.categoriesToObject(categoryList),
|
||||
files: fileReferences,
|
||||
images: imagePublisherRef?.current?.getImageArray(),
|
||||
};
|
||||
|
||||
let metadescription =
|
||||
`**${categoryListRef.current?.getCategoriesFetchString()}**` +
|
||||
fullDescription.slice(0, 150);
|
||||
const categoryString = `**${categoryListRef.current?.getCategoriesFetchString(categoryList)}**`;
|
||||
let metadescription = categoryString + fullDescription.slice(0, 150);
|
||||
|
||||
const crowdfundObjectToBase64 = await objectToBase64(fileObject);
|
||||
const fileObjectToBase64 = await objectToBase64(fileObject);
|
||||
// Description is obtained from raw data
|
||||
const requestBodyJson: any = {
|
||||
action: "PUBLISH_QDN_RESOURCE",
|
||||
name: name,
|
||||
service: "DOCUMENT",
|
||||
data64: crowdfundObjectToBase64,
|
||||
data64: fileObjectToBase64,
|
||||
title: title.slice(0, 50),
|
||||
description: metadescription,
|
||||
identifier: identifier + "_metadata",
|
||||
tag1: QSHARE_FILE_BASE,
|
||||
tag1: QSUPPORT_FILE_BASE,
|
||||
filename: `video_metadata.json`,
|
||||
};
|
||||
listOfPublishes.push(requestBodyJson);
|
||||
@ -264,17 +278,17 @@ export const PublishFile = ({ editId, editContent }: NewCrowdfundProps) => {
|
||||
let notificationObj: any = null;
|
||||
if (typeof error === "string") {
|
||||
notificationObj = {
|
||||
msg: error || "Failed to publish share",
|
||||
msg: error || "Failed to publish issue",
|
||||
alertType: "error",
|
||||
};
|
||||
} else if (typeof error?.error === "string") {
|
||||
notificationObj = {
|
||||
msg: error?.error || "Failed to publish share",
|
||||
msg: error?.error || "Failed to publish issue",
|
||||
alertType: "error",
|
||||
};
|
||||
} else {
|
||||
notificationObj = {
|
||||
msg: error?.message || "Failed to publish share",
|
||||
msg: error?.message || "Failed to publish issue",
|
||||
alertType: "error",
|
||||
};
|
||||
}
|
||||
@ -295,7 +309,7 @@ export const PublishFile = ({ editId, editContent }: NewCrowdfundProps) => {
|
||||
setIsOpen(true);
|
||||
}}
|
||||
>
|
||||
share
|
||||
Open an Issue
|
||||
</StyledButton>
|
||||
)}
|
||||
</>
|
||||
@ -314,7 +328,7 @@ export const PublishFile = ({ editId, editContent }: NewCrowdfundProps) => {
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<NewCrowdfundTitle>Share</NewCrowdfundTitle>
|
||||
<NewCrowdfundTitle>Issue</NewCrowdfundTitle>
|
||||
</Box>
|
||||
|
||||
{step === "videos" && (
|
||||
@ -331,7 +345,7 @@ export const PublishFile = ({ editId, editContent }: NewCrowdfundProps) => {
|
||||
>
|
||||
<input {...getInputProps()} />
|
||||
<Typography>
|
||||
Drag and drop files here or click to select files
|
||||
Publish files related to issue (Optional)
|
||||
</Typography>
|
||||
</Box>
|
||||
{files.map((file, index) => {
|
||||
@ -362,53 +376,53 @@ export const PublishFile = ({ editId, editContent }: NewCrowdfundProps) => {
|
||||
);
|
||||
})}
|
||||
|
||||
{files?.length > 0 && (
|
||||
<>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
gap: "20px",
|
||||
alignItems: "flex-start",
|
||||
}}
|
||||
>
|
||||
<CategoryList
|
||||
categoryData={allCategoryData}
|
||||
ref={categoryListRef}
|
||||
columns={3}
|
||||
/>
|
||||
</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
|
||||
<>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
gap: "20px",
|
||||
alignItems: "flex-start",
|
||||
}}
|
||||
>
|
||||
<CategoryList
|
||||
categoryData={allCategoryData}
|
||||
ref={categoryListRef}
|
||||
columns={3}
|
||||
excludeCategories={SupportState}
|
||||
/>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "18px",
|
||||
}}
|
||||
>
|
||||
Description of share
|
||||
</Typography>
|
||||
<TextEditor
|
||||
inlineContent={description}
|
||||
setInlineContent={value => {
|
||||
setDescription(value);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
<ImagePublisher ref={imagePublisherRef} />
|
||||
<CustomInputField
|
||||
name="title"
|
||||
label="Title of Issue"
|
||||
variant="filled"
|
||||
value={title}
|
||||
onChange={e => {
|
||||
const value = e.target.value;
|
||||
const formattedValue = value.replace(titleFormatter, "");
|
||||
setTitle(formattedValue);
|
||||
}}
|
||||
inputProps={{ maxLength: 180 }}
|
||||
required
|
||||
/>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "18px",
|
||||
}}
|
||||
>
|
||||
Description of Issue
|
||||
</Typography>
|
||||
<TextEditor
|
||||
inlineContent={description}
|
||||
setInlineContent={value => {
|
||||
setDescription(value);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
</>
|
||||
)}
|
||||
<CrowdfundActionButtonRow>
|
||||
<CrowdfundActionButton
|
||||
<ActionButtonRow>
|
||||
<ActionButton
|
||||
onClick={() => {
|
||||
onClose();
|
||||
}}
|
||||
@ -416,7 +430,7 @@ export const PublishFile = ({ editId, editContent }: NewCrowdfundProps) => {
|
||||
color="error"
|
||||
>
|
||||
Cancel
|
||||
</CrowdfundActionButton>
|
||||
</ActionButton>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
@ -424,16 +438,16 @@ export const PublishFile = ({ editId, editContent }: NewCrowdfundProps) => {
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<CrowdfundActionButton
|
||||
<ActionButton
|
||||
variant="contained"
|
||||
onClick={() => {
|
||||
publishQDNResource();
|
||||
}}
|
||||
>
|
||||
Publish
|
||||
</CrowdfundActionButton>
|
||||
</ActionButton>
|
||||
</Box>
|
||||
</CrowdfundActionButtonRow>
|
||||
</ActionButtonRow>
|
||||
</ModalBody>
|
||||
</Modal>
|
||||
|
||||
@ -466,7 +480,7 @@ export const PublishFile = ({ editId, editContent }: NewCrowdfundProps) => {
|
||||
categoryListRef.current?.clearCategories();
|
||||
dispatch(
|
||||
setNotification({
|
||||
msg: "Files published",
|
||||
msg: "Issue published",
|
||||
alertType: "success",
|
||||
})
|
||||
);
|
@ -7,9 +7,9 @@ import {
|
||||
Button,
|
||||
Grid,
|
||||
Rating,
|
||||
Select,
|
||||
TextField,
|
||||
Typography,
|
||||
Select
|
||||
} from "@mui/material";
|
||||
import AddPhotoAlternateIcon from "@mui/icons-material/AddPhotoAlternate";
|
||||
import { TimesSVG } from "../../assets/svgs/TimesSVG";
|
||||
@ -67,9 +67,9 @@ export const ModalBody = styled(Box)(({ theme }) => ({
|
||||
overflowY: "auto",
|
||||
maxHeight: "95vh",
|
||||
boxShadow:
|
||||
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)"
|
||||
: "rgba(99, 99, 99, 0.2) 0px 2px 8px 0px",
|
||||
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)"
|
||||
: "rgba(99, 99, 99, 0.2) 0px 2px 8px 0px",
|
||||
"&::-webkit-scrollbar-track": {
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
},
|
||||
@ -159,8 +159,6 @@ export const CustomInputField = styled(TextField)(({ theme }) => ({
|
||||
},
|
||||
}));
|
||||
|
||||
|
||||
|
||||
export const CrowdfundTitle = styled(Typography)(({ theme }) => ({
|
||||
fontFamily: "Copse",
|
||||
letterSpacing: "1px",
|
||||
@ -203,11 +201,11 @@ export const CrowdfundDescription = styled(Typography)(({ theme }) => ({
|
||||
|
||||
export const Spacer = ({ height }: any) => {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
height: height,
|
||||
}}
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
height: height,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@ -314,14 +312,14 @@ export const AddCrowdFundButton = styled(Button)(({ theme }) => ({
|
||||
gap: "8px",
|
||||
color: "#ffffff",
|
||||
backgroundColor:
|
||||
theme.palette.mode === "dark" ? theme.palette.primary.main : "#2a9a86",
|
||||
theme.palette.mode === "dark" ? theme.palette.primary.main : "#2a9a86",
|
||||
border: "none",
|
||||
borderRadius: "5px",
|
||||
transition: "all 0.3s ease-in-out",
|
||||
"&:hover": {
|
||||
cursor: "pointer",
|
||||
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",
|
||||
color: "#ffffff",
|
||||
backgroundColor:
|
||||
theme.palette.mode === "dark" ? theme.palette.primary.main : "#2a9a86",
|
||||
theme.palette.mode === "dark" ? theme.palette.primary.main : "#2a9a86",
|
||||
border: "none",
|
||||
borderRadius: "5px",
|
||||
transition: "all 0.3s ease-in-out",
|
||||
"&:hover": {
|
||||
cursor: "pointer",
|
||||
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",
|
||||
});
|
||||
|
||||
export const CrowdfundActionButtonRow = styled(Box)({
|
||||
export const ActionButtonRow = styled(Box)({
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
width: "100%",
|
||||
});
|
||||
|
||||
export const CrowdfundActionButton = styled(Button)(({ theme }) => ({
|
||||
export const ActionButton = styled(Button)(({ theme }) => ({
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
fontFamily: "Montserrat",
|
||||
@ -540,8 +538,8 @@ export const NoReviewsFont = styled(Typography)(({ theme }) => ({
|
||||
export const StyledButton = styled(Button)(({ theme }) => ({
|
||||
fontWeight: 600,
|
||||
color: theme.palette.text.primary,
|
||||
fontFamily: "Cairo"
|
||||
}))
|
||||
fontFamily: "Cairo",
|
||||
}));
|
||||
|
||||
export const CustomSelect = styled(Select)(({ theme }) => ({
|
||||
fontFamily: "Mulish",
|
||||
@ -550,38 +548,38 @@ export const CustomSelect = styled(Select)(({ theme }) => ({
|
||||
fontWeight: 400,
|
||||
color: theme.palette.text.primary,
|
||||
backgroundColor: theme.palette.background.default,
|
||||
'& .MuiSelect-select': {
|
||||
padding: '12px',
|
||||
"& .MuiSelect-select": {
|
||||
padding: "12px",
|
||||
fontFamily: "Mulish",
|
||||
fontSize: "19px",
|
||||
letterSpacing: "0px",
|
||||
fontWeight: 400,
|
||||
borderRadius: theme.shape.borderRadius, // Match border radius
|
||||
},
|
||||
'&:before': {
|
||||
"&:before": {
|
||||
// Underline style
|
||||
borderBottomColor: theme.palette.mode === "light" ? "#B2BAC2" : "#c9cccf",
|
||||
},
|
||||
'&:after': {
|
||||
"&:after": {
|
||||
// Underline style when focused
|
||||
borderBottomColor: theme.palette.secondary.main,
|
||||
},
|
||||
'& .MuiOutlinedInput-root': {
|
||||
'& fieldset': {
|
||||
"& .MuiOutlinedInput-root": {
|
||||
"& fieldset": {
|
||||
borderColor: "#E0E3E7",
|
||||
},
|
||||
'&:hover fieldset': {
|
||||
"&:hover fieldset": {
|
||||
borderColor: "#B2BAC2",
|
||||
},
|
||||
'&.Mui-focused fieldset': {
|
||||
"&.Mui-focused fieldset": {
|
||||
borderColor: "#6F7E8C",
|
||||
},
|
||||
},
|
||||
'& .MuiInputBase-root': {
|
||||
"& .MuiInputBase-root": {
|
||||
fontFamily: "Mulish",
|
||||
fontSize: "19px",
|
||||
letterSpacing: "0px",
|
||||
fontWeight: 400,
|
||||
color: theme.palette.text.primary,
|
||||
},
|
||||
}));
|
||||
}));
|
@ -25,13 +25,13 @@ export const StatsData = () => {
|
||||
getFilesCount,
|
||||
} = useFetchFiles();
|
||||
|
||||
const totalVideosPublished = useSelector(
|
||||
const totalIssuesPublished = useSelector(
|
||||
(state: RootState) => state.global.totalFilesPublished
|
||||
);
|
||||
const totalNamesPublished = useSelector(
|
||||
(state: RootState) => state.global.totalNamesPublished
|
||||
);
|
||||
const videosPerNamePublished = useSelector(
|
||||
const issuesPerNamePublished = useSelector(
|
||||
(state: RootState) => state.global.filesPerNamePublished
|
||||
);
|
||||
|
||||
@ -40,22 +40,28 @@ export const StatsData = () => {
|
||||
}, [getFilesCount]);
|
||||
|
||||
return (
|
||||
<StatsCol>
|
||||
<div>
|
||||
Shares:{" "}
|
||||
<span style={{ fontWeight: "bold" }}>{totalVideosPublished}</span>
|
||||
</div>
|
||||
<div>
|
||||
Publishers:{" "}
|
||||
<span style={{ fontWeight: "bold" }}>{totalNamesPublished}</span>
|
||||
</div>
|
||||
<div>
|
||||
Average:{" "}
|
||||
<span style={{ fontWeight: "bold" }}>
|
||||
{videosPerNamePublished > 0 &&
|
||||
Number(videosPerNamePublished).toFixed(0)}
|
||||
</span>
|
||||
</div>
|
||||
</StatsCol>
|
||||
totalIssuesPublished > 0 && (
|
||||
<StatsCol>
|
||||
<div>
|
||||
Issues Published:{" "}
|
||||
<span style={{ fontWeight: "bold" }}>
|
||||
{totalIssuesPublished || ""}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
Publishers:{" "}
|
||||
<span style={{ fontWeight: "bold" }}>
|
||||
{totalNamesPublished || ""}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
Average:{" "}
|
||||
<span style={{ fontWeight: "bold" }}>
|
||||
{issuesPerNamePublished > 0 &&
|
||||
Number(issuesPerNamePublished).toFixed(0)}
|
||||
</span>
|
||||
</div>
|
||||
</StatsCol>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
@ -10,9 +10,10 @@ import {
|
||||
Theme,
|
||||
} from "@mui/material";
|
||||
|
||||
import React, { forwardRef, useImperativeHandle, useState } from "react";
|
||||
import React, { useEffect, useImperativeHandle, useState } from "react";
|
||||
import { CategoryContainer } from "./CategoryList-styles.tsx";
|
||||
import { allCategoryData } from "../../../constants/Categories/1stCategories.ts";
|
||||
import { log } from "../../../constants/Misc.ts";
|
||||
|
||||
export interface Category {
|
||||
id: number;
|
||||
@ -29,19 +30,22 @@ export interface CategoryData {
|
||||
}
|
||||
|
||||
type ListDirection = "column" | "row";
|
||||
|
||||
interface CategoryListProps {
|
||||
sx?: SxProps<Theme>;
|
||||
categoryData: CategoryData;
|
||||
initialCategories?: string[];
|
||||
columns?: number;
|
||||
afterChange?: (categories: string[]) => void;
|
||||
excludeCategories?: Category[];
|
||||
}
|
||||
|
||||
export type CategoryListRef = {
|
||||
getSelectedCategories: () => string[];
|
||||
setSelectedCategories: (arr: string[]) => void;
|
||||
clearCategories: () => void;
|
||||
getCategoriesFetchString: () => string;
|
||||
categoriesToObject: () => object;
|
||||
getCategoriesFetchString: (categories?: string[]) => string;
|
||||
categoriesToObject: (categories?: string[]) => object;
|
||||
};
|
||||
|
||||
export const CategoryList = React.forwardRef<
|
||||
@ -49,7 +53,14 @@ export const CategoryList = React.forwardRef<
|
||||
CategoryListProps
|
||||
>(
|
||||
(
|
||||
{ sx, categoryData, initialCategories, columns = 1 }: CategoryListProps,
|
||||
{
|
||||
sx,
|
||||
categoryData,
|
||||
initialCategories,
|
||||
columns = 1,
|
||||
afterChange,
|
||||
excludeCategories,
|
||||
}: CategoryListProps,
|
||||
ref
|
||||
) => {
|
||||
const categoriesLength = categoryData.subCategories.length + 1;
|
||||
@ -60,20 +71,27 @@ export const CategoryList = React.forwardRef<
|
||||
const [selectedCategories, setSelectedCategories] = useState<string[]>(
|
||||
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 = {};
|
||||
selectedCategories.map((category, index) => {
|
||||
categories.map((category, index) => {
|
||||
if (index === 0) categoriesObject["category"] = category;
|
||||
else if (index === 1) categoriesObject["subcategory"] = category;
|
||||
else categoriesObject[`subcategory${index}`] = category;
|
||||
});
|
||||
console.log("categoriesObject is: ", categoriesObject);
|
||||
if (log) console.log("categoriesObject is: ", categoriesObject);
|
||||
return categoriesObject;
|
||||
};
|
||||
|
||||
const clearCategories = () => {
|
||||
setSelectedCategories(emptyCategories);
|
||||
updateCategories(emptyCategories);
|
||||
};
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
@ -81,26 +99,31 @@ export const CategoryList = React.forwardRef<
|
||||
return selectedCategories;
|
||||
},
|
||||
setSelectedCategories: categories => {
|
||||
console.log("setSelectedCategories: ", categories);
|
||||
//categories.map((category, index) => selectCategory(category, index));
|
||||
setSelectedCategories(categories);
|
||||
if (log) console.log("setSelectedCategories: ", categories);
|
||||
updateCategories(categories);
|
||||
},
|
||||
clearCategories,
|
||||
getCategoriesFetchString: () =>
|
||||
getCategoriesFetchString(selectedCategories),
|
||||
categoriesToObject,
|
||||
getCategoriesFetchString: (categories?: string[]) =>
|
||||
getCategoriesFetchString(categories || selectedCategories),
|
||||
categoriesToObject: (categories?: string[]) =>
|
||||
categoriesToObject(categories || selectedCategories),
|
||||
}));
|
||||
|
||||
const selectCategory = (optionId: string, index: number) => {
|
||||
const isMainCategory = index === 0;
|
||||
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
|
||||
? categoryData.category.find(option => option.id === +optionId)
|
||||
: categoryData.subCategories[subCategoryIndex][
|
||||
selectedCategories[subCategoryIndex]
|
||||
].find(option => option.id === +optionId);
|
||||
|
||||
selectedOption = subCategory.find(option => option.id === +optionId);
|
||||
}
|
||||
const newSelectedCategories: string[] = selectedCategories.map(
|
||||
(category, categoryIndex) => {
|
||||
if (index > categoryIndex) return category;
|
||||
@ -108,7 +131,7 @@ export const CategoryList = React.forwardRef<
|
||||
else return "";
|
||||
}
|
||||
);
|
||||
setSelectedCategories(newSelectedCategories);
|
||||
updateCategories(newSelectedCategories);
|
||||
};
|
||||
|
||||
const selectCategoryEvent = (event: SelectChangeEvent, index: number) => {
|
||||
@ -136,15 +159,16 @@ export const CategoryList = React.forwardRef<
|
||||
|
||||
const fillMenu = (category: Categories, index: number) => {
|
||||
const subCategoryIndex = selectedCategories[index];
|
||||
console.log("selected categories: ", selectedCategories);
|
||||
console.log("index is: ", index);
|
||||
console.log("subCategoryIndex is: ", subCategoryIndex);
|
||||
console.log("category is: ", category);
|
||||
console.log(
|
||||
"subCategoryIndex within category: ",
|
||||
selectedCategories[subCategoryIndex]
|
||||
);
|
||||
console.log("categoryData: ", categoryData);
|
||||
if (log) console.log("selected categories: ", selectedCategories);
|
||||
if (log) console.log("index is: ", index);
|
||||
if (log) console.log("subCategoryIndex is: ", subCategoryIndex);
|
||||
if (log) console.log("category is: ", category);
|
||||
if (log)
|
||||
console.log(
|
||||
"subCategoryIndex within category: ",
|
||||
selectedCategories[subCategoryIndex]
|
||||
);
|
||||
if (log) console.log("categoryData: ", categoryData);
|
||||
|
||||
const menuToFill = category[subCategoryIndex];
|
||||
if (menuToFill)
|
||||
@ -158,6 +182,7 @@ export const CategoryList = React.forwardRef<
|
||||
const hasSubCategory = (category: Categories, index: number) => {
|
||||
const subCategoryIndex = selectedCategories[index];
|
||||
const subCategory = category[subCategoryIndex];
|
||||
if (excludeCategories && subCategory === excludeCategories) return false;
|
||||
return subCategory && subCategoryIndex;
|
||||
};
|
||||
|
||||
@ -265,10 +290,21 @@ export const getCategoriesFetchString = (categories: string[]) => {
|
||||
else fetchString += `;sub${index}:${category}`;
|
||||
}
|
||||
});
|
||||
console.log("categoriesAsDescription: ", fetchString);
|
||||
if (log) console.log("categoriesAsDescription: ", 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) => {
|
||||
const categoryList: string[] = [];
|
||||
const categoryCount = allCategoryData.subCategories.length + 1;
|
||||
|
@ -11,7 +11,7 @@ import {
|
||||
CommentInputContainer,
|
||||
SubmitCommentButton,
|
||||
} from "./Comments-styles";
|
||||
import { QSHARE_COMMENT_BASE } from "../../../constants/Identifiers.ts";
|
||||
import { QSUPPORT_COMMENT_BASE } from "../../../constants/Identifiers.ts";
|
||||
const uid = new ShortUniqueId();
|
||||
|
||||
const notification = localforage.createInstance({
|
||||
@ -201,13 +201,13 @@ export const CommentEditor = ({
|
||||
try {
|
||||
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;
|
||||
|
||||
if (isReply && commentId) {
|
||||
const removeBaseCommentId = commentId;
|
||||
removeBaseCommentId.replace("_base_", "");
|
||||
identifier = `${QSHARE_COMMENT_BASE}${postId.slice(
|
||||
identifier = `${QSUPPORT_COMMENT_BASE}${postId.slice(
|
||||
-12
|
||||
)}_reply_${removeBaseCommentId.slice(-6)}_${id}`;
|
||||
idForNotification = commentId;
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { CommentEditor } from "./CommentEditor";
|
||||
import { Comment } from "./Comment";
|
||||
import { Box, Button, CircularProgress, useTheme } from "@mui/material";
|
||||
import { CircularProgress } from "@mui/material";
|
||||
import { styled } from "@mui/system";
|
||||
import { useSelector } from "react-redux";
|
||||
import { RootState } from "../../../state/store";
|
||||
import { useNavigate, useLocation } from "react-router-dom";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
import {
|
||||
CommentContainer,
|
||||
CommentEditorContainer,
|
||||
@ -14,8 +14,11 @@ import {
|
||||
LoadMoreCommentsButtonRow,
|
||||
NoCommentsRow,
|
||||
} from "./Comments-styles";
|
||||
import { QSHARE_COMMENT_BASE } from "../../../constants/Identifiers.ts";
|
||||
import { CrowdfundSubTitle, CrowdfundSubTitleRow } from "../../PublishFile/Upload-styles.tsx";
|
||||
import { QSUPPORT_COMMENT_BASE } from "../../../constants/Identifiers.ts";
|
||||
import {
|
||||
CrowdfundSubTitle,
|
||||
CrowdfundSubTitleRow,
|
||||
} from "../../PublishIssue/Upload-styles.tsx";
|
||||
|
||||
interface CommentSectionProps {
|
||||
postId: string;
|
||||
@ -105,7 +108,7 @@ export const CommentSection = ({ postId, postName }: CommentSectionProps) => {
|
||||
const offset = 0;
|
||||
|
||||
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
|
||||
)}_reply_${removeBaseCommentId.slice(
|
||||
-6
|
||||
@ -150,7 +153,7 @@ export const CommentSection = ({ postId, postName }: CommentSectionProps) => {
|
||||
if (isNewMessages && 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
|
||||
)}_base_&limit=20&includemetadata=false&offset=${offset}&reverse=false&excludeblocked=true`;
|
||||
const response = await fetch(url, {
|
||||
@ -218,11 +221,10 @@ export const CommentSection = ({ postId, postName }: CommentSectionProps) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
<Panel>
|
||||
<CrowdfundSubTitleRow >
|
||||
<CrowdfundSubTitle>Comments</CrowdfundSubTitle>
|
||||
</CrowdfundSubTitleRow>
|
||||
<CrowdfundSubTitleRow>
|
||||
<CrowdfundSubTitle>Comments</CrowdfundSubTitle>
|
||||
</CrowdfundSubTitleRow>
|
||||
<CommentsContainer>
|
||||
{loadingComments ? (
|
||||
<NoCommentsRow>
|
||||
|
@ -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",
|
||||
},
|
||||
}));
|
62
src/components/common/ImagePublisher/ImagePublisher.tsx
Normal 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>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
109
src/components/common/ImagePublisher/ImageUploader.tsx
Normal 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;
|
28
src/components/common/ImagePublisher/TimesSVG.tsx
Normal 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>
|
||||
);
|
||||
};
|
@ -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
|
@ -14,26 +14,22 @@ export const CustomAppBar = styled(AppBar)(({ theme }) => ({
|
||||
borderBottom: `1px solid ${theme.palette.primary.light}`,
|
||||
backgroundColor: theme.palette.background.default,
|
||||
[theme.breakpoints.only("xs")]: {
|
||||
gap: "15px"
|
||||
gap: "15px",
|
||||
},
|
||||
height: '55px'
|
||||
height: "100px",
|
||||
}));
|
||||
export const LogoContainer = styled("div")({
|
||||
cursor: 'pointer',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
alignItems: 'center'
|
||||
cursor: "pointer",
|
||||
height: "100%",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
export const CustomTitle = styled(Typography)({
|
||||
fontWeight: 600,
|
||||
color: "#000000"
|
||||
color: "#000000",
|
||||
});
|
||||
|
||||
|
||||
export const AuthenticateButton = styled(Button)(({ theme }) => ({
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
@ -50,8 +46,8 @@ export const AuthenticateButton = styled(Button)(({ theme }) => ({
|
||||
cursor: "pointer",
|
||||
boxShadow: "rgba(0, 0, 0, 0.15) 1.95px 1.95px 2.6px;",
|
||||
backgroundColor: theme.palette.secondary.dark,
|
||||
filter: "brightness(1.1)"
|
||||
}
|
||||
filter: "brightness(1.1)",
|
||||
},
|
||||
}));
|
||||
|
||||
export const AvatarContainer = styled(Box)({
|
||||
@ -61,9 +57,9 @@ export const AvatarContainer = styled(Box)({
|
||||
cursor: "pointer",
|
||||
"& #expand-icon": {
|
||||
transition: "all 0.3s ease-in-out",
|
||||
filter: "brightness(0.7)"
|
||||
}
|
||||
}
|
||||
filter: "brightness(0.7)",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const DropdownContainer = styled(Box)(({ theme }) => ({
|
||||
@ -76,22 +72,22 @@ export const DropdownContainer = styled(Box)(({ theme }) => ({
|
||||
"&:hover": {
|
||||
cursor: "pointer",
|
||||
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 }) => ({
|
||||
fontFamily: "Raleway",
|
||||
fontSize: "16px",
|
||||
color: theme.palette.text.primary,
|
||||
userSelect: "none"
|
||||
userSelect: "none",
|
||||
}));
|
||||
|
||||
export const NavbarName = styled(Typography)(({ theme }) => ({
|
||||
fontFamily: "Raleway",
|
||||
fontSize: "18px",
|
||||
color: theme.palette.text.primary,
|
||||
margin: "0 10px"
|
||||
margin: "0 10px",
|
||||
}));
|
||||
|
||||
export const ThemeSelectRow = styled(Box)({
|
||||
@ -99,7 +95,7 @@ export const ThemeSelectRow = styled(Box)({
|
||||
alignItems: "center",
|
||||
gap: "5px",
|
||||
flexBasis: 0,
|
||||
height: '100%'
|
||||
height: "100%",
|
||||
});
|
||||
|
||||
export const LightModeIcon = styled(LightModeSVG)(({ theme }) => ({
|
||||
@ -109,8 +105,8 @@ export const LightModeIcon = styled(LightModeSVG)(({ theme }) => ({
|
||||
filter:
|
||||
theme.palette.mode === "dark"
|
||||
? "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 }) => ({
|
||||
@ -120,6 +116,6 @@ export const DarkModeIcon = styled(DarkModeSVG)(({ theme }) => ({
|
||||
filter:
|
||||
theme.palette.mode === "dark"
|
||||
? "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))",
|
||||
},
|
||||
}));
|
||||
|
@ -1,27 +1,15 @@
|
||||
import React, { useState, useRef } from "react";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Input,
|
||||
Popover,
|
||||
Typography,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import ExitToAppIcon from "@mui/icons-material/ExitToApp";
|
||||
import React, { useRef, useState } from "react";
|
||||
import { Box, Input, Popover, Typography, useTheme } from "@mui/material";
|
||||
import { BlockedNamesModal } from "../../common/BlockedNamesModal/BlockedNamesModal";
|
||||
import AddBoxIcon from "@mui/icons-material/AddBox";
|
||||
|
||||
import {
|
||||
AvatarContainer,
|
||||
CustomAppBar,
|
||||
DropdownContainer,
|
||||
DropdownText,
|
||||
AuthenticateButton,
|
||||
NavbarName,
|
||||
LightModeIcon,
|
||||
DarkModeIcon,
|
||||
ThemeSelectRow,
|
||||
LogoContainer,
|
||||
NavbarName,
|
||||
ThemeSelectRow,
|
||||
} from "./Navbar-styles";
|
||||
import { AccountCircleSVG } from "../../../assets/svgs/AccountCircleSVG";
|
||||
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 { 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 {
|
||||
addFilteredFiles,
|
||||
setEditPlaylist,
|
||||
setFilterValue,
|
||||
setIsFiltering,
|
||||
} from "../../../state/features/fileSlice.ts";
|
||||
import { RootState } from "../../../state/store";
|
||||
import { useWindowSize } from "../../../hooks/useWindowSize";
|
||||
import { PublishFile } from "../../PublishFile/PublishFile.tsx";
|
||||
import { StyledButton } from "../../PublishFile/Upload-styles.tsx";
|
||||
import { PublishIssue } from "../../PublishIssue/PublishIssue.tsx";
|
||||
|
||||
interface Props {
|
||||
isAuthenticated: boolean;
|
||||
userName: string | null;
|
||||
@ -125,21 +112,21 @@ const NavBar: React.FC<Props> = ({
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={QShareLogo}
|
||||
src={QSupportLogo}
|
||||
style={{
|
||||
width: "auto",
|
||||
height: "55px",
|
||||
height: "100px",
|
||||
padding: "2px",
|
||||
}}
|
||||
/>
|
||||
</LogoContainer>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "16px",
|
||||
fontSize: "30px",
|
||||
whiteSpace: "nowrap",
|
||||
}}
|
||||
>
|
||||
Sharing is caring
|
||||
Welcome to Q-Support
|
||||
</Typography>
|
||||
</Box>
|
||||
</ThemeSelectRow>
|
||||
@ -150,135 +137,6 @@ const NavBar: React.FC<Props> = ({
|
||||
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
|
||||
id={idNotification}
|
||||
open={openPopover}
|
||||
@ -411,7 +269,7 @@ const NavBar: React.FC<Props> = ({
|
||||
<AvatarContainer>
|
||||
{isAuthenticated && userName && (
|
||||
<>
|
||||
<PublishFile />
|
||||
<PublishIssue />
|
||||
</>
|
||||
)}
|
||||
</AvatarContainer>
|
||||
|
@ -7,15 +7,6 @@ import softwareIcon from "../../assets/icons/software.webp";
|
||||
import unknownIcon from "../../assets/icons/unknown.webp";
|
||||
import videoIcon from "../../assets/icons/video.webp";
|
||||
|
||||
import {
|
||||
audioSubCategories,
|
||||
bookSubCategories,
|
||||
documentSubCategories,
|
||||
imageSubCategories,
|
||||
softwareSubCategories,
|
||||
videoSubCategories,
|
||||
} from "./2ndCategories.ts";
|
||||
import { musicSubCategories } from "./3rdCategories.ts";
|
||||
import {
|
||||
Categories,
|
||||
Category,
|
||||
@ -25,30 +16,30 @@ import {
|
||||
getAllCategoriesWithIcons,
|
||||
sortCategory,
|
||||
} from "./CategoryFunctions.ts";
|
||||
import { QappCategories, SupportState } from "./2ndCategories.ts";
|
||||
|
||||
export const firstCategories: Category[] = [
|
||||
{ id: 1, name: "Software", icon: softwareIcon },
|
||||
{ id: 2, name: "Gaming", icon: gamingIcon },
|
||||
{ id: 3, name: "Audio", icon: audioIcon },
|
||||
{ id: 4, name: "Video", icon: videoIcon },
|
||||
{ id: 5, name: "Image", icon: imageIcon },
|
||||
{ id: 6, name: "Document", icon: documentIcon },
|
||||
{ id: 7, name: "Book", icon: bookIcon },
|
||||
{ id: 99, name: "Other", icon: unknownIcon },
|
||||
].sort(sortCategory);
|
||||
{ id: 1, name: "Core" },
|
||||
{ id: 2, name: "UI" },
|
||||
{ id: 3, name: "Q-Apps" },
|
||||
{ id: 4, name: "Website" },
|
||||
{ id: 5, name: "Marketing" },
|
||||
{ id: 99, name: "Other" },
|
||||
];
|
||||
export const secondCategories: Categories = {
|
||||
1: softwareSubCategories.sort(sortCategory),
|
||||
3: audioSubCategories.sort(sortCategory),
|
||||
4: videoSubCategories.sort(sortCategory),
|
||||
5: imageSubCategories.sort(sortCategory),
|
||||
6: documentSubCategories.sort(sortCategory),
|
||||
7: bookSubCategories.sort(sortCategory),
|
||||
};
|
||||
|
||||
export const thirdCategories: Categories = {
|
||||
301: musicSubCategories,
|
||||
1: SupportState,
|
||||
2: SupportState,
|
||||
3: QappCategories,
|
||||
4: SupportState,
|
||||
5: SupportState,
|
||||
99: SupportState,
|
||||
};
|
||||
|
||||
export let thirdCategories: Categories = {};
|
||||
QappCategories.map(
|
||||
supportStateCategory =>
|
||||
(thirdCategories[supportStateCategory.id] = SupportState)
|
||||
);
|
||||
export const allCategoryData: CategoryData = {
|
||||
category: firstCategories,
|
||||
subCategories: [secondCategories, thirdCategories],
|
||||
|
@ -1,88 +1,23 @@
|
||||
export const softwareSubCategories = [
|
||||
{ id: 101, name: "OS" },
|
||||
{ id: 102, name: "Application" },
|
||||
{ id: 103, name: "Source Code" },
|
||||
{ id: 104, name: "Plugin" },
|
||||
{ id: 199, name: "Other" },
|
||||
import OpenIcon from "../../assets/icons/OpenIcon.png";
|
||||
import ClosedIcon from "../../assets/icons/ClosedIcon.png";
|
||||
import InProgressIcon from "../../assets/icons/InProgressIcon.png";
|
||||
import CompleteIcon from "../../assets/icons/CompleteIcon.png";
|
||||
|
||||
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 = [
|
||||
{ id: 301, name: "Music" },
|
||||
{ id: 302, name: "Podcast" },
|
||||
{ id: 303, name: "Audiobook" },
|
||||
{ id: 304, name: "Sound Effect" },
|
||||
{ id: 305, name: "Lecture or Speech" },
|
||||
{ id: 306, name: "Radio Show" },
|
||||
{ id: 307, name: "Ambient Sound" },
|
||||
{ id: 308, name: "Language Learning Material" },
|
||||
{ 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" },
|
||||
export const QappCategories = [
|
||||
{ id: 301, name: "Q-Blog" },
|
||||
{ id: 302, name: "Q-Mail" },
|
||||
{ id: 303, name: "Q-Shop" },
|
||||
{ id: 304, name: "Q-Fund" },
|
||||
{ id: 305, name: "Ear-Bump" },
|
||||
{ id: 306, name: "Q-Tube" },
|
||||
{ id: 307, name: "Q-Share" },
|
||||
{ id: 308, name: "Q-Support" },
|
||||
{ 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" },
|
||||
];
|
||||
|
@ -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" },
|
||||
];
|
@ -1,13 +1,13 @@
|
||||
const useTestIdentifiers = false;
|
||||
|
||||
export const QSHARE_FILE_BASE = useTestIdentifiers
|
||||
? "MYTEST_share_vid_"
|
||||
: "qshare_file_";
|
||||
export const QSUPPORT_FILE_BASE = useTestIdentifiers
|
||||
? "MYTEST_support_issue_"
|
||||
: "q_support_issue_";
|
||||
|
||||
export const QSHARE_PLAYLIST_BASE = useTestIdentifiers
|
||||
? "MYTEST_share_playlist_"
|
||||
: "qshare_playlist_";
|
||||
export const QSUPPORT_PLAYLIST_BASE = useTestIdentifiers
|
||||
? "MYTEST_support_playlist_"
|
||||
: "q_support_playlist_";
|
||||
|
||||
export const QSHARE_COMMENT_BASE = useTestIdentifiers
|
||||
? "qcomment_v1_MYTEST_"
|
||||
: "qcomment_v1_qshare_";
|
||||
export const QSUPPORT_COMMENT_BASE = useTestIdentifiers
|
||||
? "qcomment_v1_MYTEST_support_"
|
||||
: "qcomment_v1_q_support_";
|
||||
|
@ -1,3 +1,5 @@
|
||||
export const minPriceSuperlike = 10;
|
||||
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;
|
||||
|
@ -19,8 +19,8 @@ import {
|
||||
import { RootState } from "../state/store";
|
||||
import { fetchAndEvaluateVideos } from "../utils/fetchVideos";
|
||||
import {
|
||||
QSHARE_PLAYLIST_BASE,
|
||||
QSHARE_FILE_BASE,
|
||||
QSUPPORT_PLAYLIST_BASE,
|
||||
QSUPPORT_FILE_BASE,
|
||||
} from "../constants/Identifiers.ts";
|
||||
import { RequestQueue } from "../utils/queue";
|
||||
import { queue } from "../wrappers/GlobalWrapper";
|
||||
@ -114,7 +114,7 @@ export const useFetchFiles = () => {
|
||||
try {
|
||||
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, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
@ -218,10 +218,10 @@ export const useFetchFiles = () => {
|
||||
}
|
||||
if (type === "playlists") {
|
||||
defaultUrl = defaultUrl + `&service=PLAYLIST`;
|
||||
defaultUrl = defaultUrl + `&identifier=${QSHARE_PLAYLIST_BASE}`;
|
||||
defaultUrl = defaultUrl + `&identifier=${QSUPPORT_PLAYLIST_BASE}`;
|
||||
} else {
|
||||
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}`
|
||||
@ -289,7 +289,7 @@ export const useFetchFiles = () => {
|
||||
const offset = filteredVideos.length;
|
||||
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, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
@ -345,7 +345,7 @@ export const useFetchFiles = () => {
|
||||
|
||||
const checkNewFiles = React.useCallback(async () => {
|
||||
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, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
@ -382,7 +382,7 @@ export const useFetchFiles = () => {
|
||||
|
||||
const getFilesCount = React.useCallback(async () => {
|
||||
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, {
|
||||
method: "GET",
|
||||
|
@ -2,12 +2,12 @@ import { Avatar, Box, Skeleton, Tooltip } from "@mui/material";
|
||||
import {
|
||||
BlockIconContainer,
|
||||
BottomParent,
|
||||
FileContainer,
|
||||
IconsBox,
|
||||
NameContainer,
|
||||
VideoCard,
|
||||
VideoCardName,
|
||||
VideoCardTitle,
|
||||
FileContainer,
|
||||
VideoUploadDate,
|
||||
} from "./FileList-styles.tsx";
|
||||
import EditIcon from "@mui/icons-material/Edit";
|
||||
@ -18,7 +18,7 @@ import {
|
||||
} from "../../state/features/fileSlice.ts";
|
||||
import BlockIcon from "@mui/icons-material/Block";
|
||||
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 React, { useState } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
@ -88,7 +88,7 @@ export const FileList = ({ files }: FileListProps) => {
|
||||
}}
|
||||
>
|
||||
{fileObj?.user === username && (
|
||||
<Tooltip title="Edit video properties" placement="top">
|
||||
<Tooltip title="Edit Issue Properties" placement="top">
|
||||
<BlockIconContainer>
|
||||
<EditIcon
|
||||
onClick={() => {
|
||||
|
@ -19,8 +19,8 @@ import {
|
||||
import { formatDate } from "../../utils/time";
|
||||
import { Video } from "../../state/features/fileSlice.ts";
|
||||
import { queue } from "../../wrappers/GlobalWrapper";
|
||||
import { QSHARE_FILE_BASE } from "../../constants/Identifiers.ts";
|
||||
import { formatBytes } from "../FileContent/FileContent.tsx";
|
||||
import { QSUPPORT_FILE_BASE } from "../../constants/Identifiers.ts";
|
||||
import { formatBytes } from "../IssueContent/IssueContent.tsx";
|
||||
import { getIconsFromObject } from "../../constants/Categories/CategoryFunctions.ts";
|
||||
|
||||
interface VideoListProps {
|
||||
@ -46,7 +46,7 @@ export const FileListComponentLevel = ({ mode }: VideoListProps) => {
|
||||
const getVideos = React.useCallback(async () => {
|
||||
try {
|
||||
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, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
|
@ -275,6 +275,8 @@ export const Home = ({ mode }: HomeProps) => {
|
||||
}}
|
||||
sx={{
|
||||
marginTop: "20px",
|
||||
fontWeight: 1000,
|
||||
color: "white",
|
||||
}}
|
||||
variant="contained"
|
||||
>
|
||||
@ -286,6 +288,8 @@ export const Home = ({ mode }: HomeProps) => {
|
||||
}}
|
||||
sx={{
|
||||
marginTop: "20px",
|
||||
fontWeight: 1000,
|
||||
color: "white",
|
||||
}}
|
||||
variant="contained"
|
||||
>
|
||||
|
@ -5,11 +5,10 @@ import {
|
||||
AuthorTextComment,
|
||||
StyledCardColComment,
|
||||
StyledCardHeaderComment,
|
||||
} from "../FileContent/FileContent-styles.tsx";
|
||||
} from "../IssueContent/IssueContent-styles.tsx";
|
||||
import { Avatar, Box, useTheme } from "@mui/material";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useSelector } from "react-redux";
|
||||
import { setUserAvatarHash } from "../../state/features/globalSlice";
|
||||
import { RootState } from "../../state/store";
|
||||
|
||||
export const IndividualProfile = () => {
|
||||
|
@ -1,5 +1,5 @@
|
||||
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 }) => ({
|
||||
maxWidth: "95%",
|
||||
@ -25,6 +25,10 @@ export const FileDescription = styled(Typography)(({ theme }) => ({
|
||||
wordBreak: "break-word",
|
||||
}));
|
||||
|
||||
export const ImageContainer = styled(Box)(({ theme }) => ({
|
||||
display: "flex",
|
||||
}));
|
||||
|
||||
export const Spacer = ({ height }: any) => {
|
||||
return (
|
||||
<Box
|
@ -14,29 +14,22 @@ import {
|
||||
FileDescription,
|
||||
FilePlayerContainer,
|
||||
FileTitle,
|
||||
ImageContainer,
|
||||
Spacer,
|
||||
StyledCardColComment,
|
||||
StyledCardHeaderComment,
|
||||
} from "./FileContent-styles.tsx";
|
||||
} from "./IssueContent-styles.tsx";
|
||||
import { formatDate } from "../../utils/time";
|
||||
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 FileElement from "../../components/common/FileElement";
|
||||
import {
|
||||
allCategoryData,
|
||||
iconCategories,
|
||||
} from "../../constants/Categories/1stCategories.ts";
|
||||
import { allCategoryData } from "../../constants/Categories/1stCategories.ts";
|
||||
import {
|
||||
Category,
|
||||
getCategoriesFromObject,
|
||||
} from "../../components/common/CategoryList/CategoryList.tsx";
|
||||
import {
|
||||
findAllCategoryData,
|
||||
findCategoryData,
|
||||
getCategoriesWithIcons,
|
||||
getIconsFromObject,
|
||||
} from "../../constants/Categories/CategoryFunctions.ts";
|
||||
import { getIconsFromObject } from "../../constants/Categories/CategoryFunctions.ts";
|
||||
|
||||
export function formatBytes(bytes, decimals = 2) {
|
||||
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];
|
||||
}
|
||||
|
||||
export const FileContent = () => {
|
||||
export const IssueContent = () => {
|
||||
const { name, id } = useParams();
|
||||
const [isExpandedDescription, setIsExpandedDescription] =
|
||||
useState<boolean>(false);
|
||||
@ -106,7 +99,7 @@ export const FileContent = () => {
|
||||
if (!name || !id) return;
|
||||
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, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
@ -272,8 +265,6 @@ export const FileContent = () => {
|
||||
}
|
||||
}
|
||||
if (fileData) {
|
||||
//const icon = getIconsFromObject(fileData)[0]?.icon || null;
|
||||
|
||||
const icon = getIconsFromObject(fileData);
|
||||
setIcon(icon);
|
||||
}
|
||||
@ -415,6 +406,19 @@ export const FileContent = () => {
|
||||
{categoriesDisplay}
|
||||
</Typography>
|
||||
</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" />
|
||||
<Box
|
||||
sx={{
|
@ -11,38 +11,38 @@ const commonThemeOptions = {
|
||||
"Oxygen",
|
||||
"Catamaran",
|
||||
"Cairo",
|
||||
"Arial"
|
||||
"Arial",
|
||||
].join(","),
|
||||
h1: {
|
||||
fontSize: "2rem",
|
||||
fontWeight: 600
|
||||
fontWeight: 600,
|
||||
},
|
||||
h2: {
|
||||
fontSize: "1.75rem",
|
||||
fontWeight: 500
|
||||
fontWeight: 500,
|
||||
},
|
||||
h3: {
|
||||
fontSize: "1.5rem",
|
||||
fontWeight: 500
|
||||
fontWeight: 500,
|
||||
},
|
||||
h4: {
|
||||
fontSize: "1.25rem",
|
||||
fontWeight: 500
|
||||
fontWeight: 500,
|
||||
},
|
||||
h5: {
|
||||
fontSize: "1rem",
|
||||
fontWeight: 500
|
||||
fontWeight: 500,
|
||||
},
|
||||
h6: {
|
||||
fontSize: "0.875rem",
|
||||
fontWeight: 500
|
||||
fontWeight: 500,
|
||||
},
|
||||
body1: {
|
||||
fontSize: "23px",
|
||||
fontFamily: "Raleway",
|
||||
fontWeight: 400,
|
||||
lineHeight: 1.5,
|
||||
letterSpacing: "0.5px"
|
||||
letterSpacing: "0.5px",
|
||||
},
|
||||
|
||||
body2: {
|
||||
@ -50,12 +50,12 @@ const commonThemeOptions = {
|
||||
fontFamily: "Raleway, Arial",
|
||||
fontWeight: 400,
|
||||
lineHeight: 1.4,
|
||||
letterSpacing: "0.2px"
|
||||
}
|
||||
letterSpacing: "0.2px",
|
||||
},
|
||||
},
|
||||
spacing: 8,
|
||||
shape: {
|
||||
borderRadius: 4
|
||||
borderRadius: 4,
|
||||
},
|
||||
breakpoints: {
|
||||
values: {
|
||||
@ -63,8 +63,8 @@ const commonThemeOptions = {
|
||||
sm: 600,
|
||||
md: 900,
|
||||
lg: 1200,
|
||||
xl: 1536
|
||||
}
|
||||
xl: 1536,
|
||||
},
|
||||
},
|
||||
components: {
|
||||
MuiButton: {
|
||||
@ -73,16 +73,16 @@ const commonThemeOptions = {
|
||||
backgroundColor: "inherit",
|
||||
transition: "filter 0.3s ease-in-out",
|
||||
"&:hover": {
|
||||
filter: "brightness(1.1)"
|
||||
}
|
||||
}
|
||||
filter: "brightness(1.1)",
|
||||
},
|
||||
},
|
||||
},
|
||||
defaultProps: {
|
||||
disableElevation: true,
|
||||
disableRipple: true
|
||||
}
|
||||
}
|
||||
}
|
||||
disableRipple: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const lightTheme = createTheme({
|
||||
@ -92,20 +92,20 @@ const lightTheme = createTheme({
|
||||
primary: {
|
||||
main: "#ffffff",
|
||||
dark: "#F5F5F5",
|
||||
light: "#FCFCFC"
|
||||
light: "#FCFCFC",
|
||||
},
|
||||
secondary: {
|
||||
main: "#417Ed4",
|
||||
dark: "#3e74c1"
|
||||
dark: "#3e74c1",
|
||||
},
|
||||
background: {
|
||||
default: "#fcfcfc",
|
||||
paper: "#F5F5F5"
|
||||
paper: "#F5F5F5",
|
||||
},
|
||||
text: {
|
||||
primary: "#000000",
|
||||
secondary: "#525252"
|
||||
}
|
||||
secondary: "#525252",
|
||||
},
|
||||
},
|
||||
components: {
|
||||
MuiCard: {
|
||||
@ -118,19 +118,19 @@ const lightTheme = createTheme({
|
||||
"&:hover": {
|
||||
cursor: "pointer",
|
||||
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: {
|
||||
defaultProps: {
|
||||
style: {
|
||||
color: "#000000"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
color: "#000000",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const darkTheme = createTheme({
|
||||
@ -138,23 +138,23 @@ const darkTheme = createTheme({
|
||||
palette: {
|
||||
mode: "dark",
|
||||
primary: {
|
||||
main: "#FF1493", // Neon pink
|
||||
dark: "#C6127A", // Darker shade of neon pink
|
||||
light: "#FF5EC4" // Lighter shade of neon pink
|
||||
main: "#01a9e9", //
|
||||
dark: "#008fcd", //
|
||||
light: "#44c4ff", //
|
||||
},
|
||||
secondary: {
|
||||
main: "#007FFF", // Electric blue
|
||||
dark: "#0059B2", // Darker shade of electric blue
|
||||
light: "#3399FF" // Lighter shade of electric blue
|
||||
light: "#3399FF", // Lighter shade of electric blue
|
||||
},
|
||||
background: {
|
||||
default: "#1C1C1C", // Deep space black
|
||||
paper: "#342F41" // Dark cyberpunk-style purple
|
||||
paper: "#342F41", // Dark cyberpunk-style purple
|
||||
},
|
||||
text: {
|
||||
primary: "#ffffff",
|
||||
secondary: "#b3b3b3"
|
||||
}
|
||||
secondary: "#b3b3b3",
|
||||
},
|
||||
},
|
||||
components: {
|
||||
MuiCard: {
|
||||
@ -165,20 +165,20 @@ const darkTheme = createTheme({
|
||||
transition: "all 0.3s ease-in-out",
|
||||
"&:hover": {
|
||||
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: {
|
||||
defaultProps: {
|
||||
style: {
|
||||
color: "#ffffff"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
color: "#ffffff",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
export { lightTheme, darkTheme };
|
||||
|
@ -1,9 +1,9 @@
|
||||
import React, {
|
||||
useEffect,
|
||||
useState,
|
||||
useCallback,
|
||||
useRef,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
|
||||
@ -15,7 +15,7 @@ import { setUserAvatarHash } from "../state/features/globalSlice";
|
||||
import { VideoPlayerGlobal } from "../components/common/VideoPlayerGlobal";
|
||||
import { Rnd } from "react-rnd";
|
||||
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 ConsentModal from "../components/common/ConsentModal";
|
||||
|
||||
@ -138,7 +138,7 @@ const GlobalWrapper: React.FC<Props> = ({ children, setTheme }) => {
|
||||
userAvatar={userAvatar}
|
||||
authenticate={askForAccountInformation}
|
||||
/>
|
||||
<EditFile />
|
||||
<EditIssue />
|
||||
<EditPlaylist />
|
||||
<Rnd
|
||||
onDragStart={onDragStart}
|
||||
|