Copy button added to FileContent.tsx

Share button is bigger and Icon is primary app color

Minor fixes to imports
This commit is contained in:
2025-06-10 13:35:31 -06:00
parent 30fdc43b48
commit 14d73a111e
10 changed files with 134 additions and 1255 deletions

View File

@@ -1,7 +1,7 @@
{
"name": "qtube",
"name": "qshare",
"private": true,
"version": "0.0.0",
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,624 +0,0 @@
import React, { useEffect, useMemo, useState } from "react";
import {
AddCoverImageButton,
AddLogoIcon,
CoverImagePreview,
CrowdfundActionButton,
CrowdfundActionButtonRow,
CustomInputField,
CustomSelect,
LogoPreviewRow,
ModalBody,
NewCrowdfundTitle,
StyledButton,
TimesIcon,
} from "./Upload-styles.tsx";
import {
Box,
FormControl,
InputLabel,
MenuItem,
Modal,
OutlinedInput,
Select,
SelectChangeEvent,
Typography,
useTheme,
} 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 { RootState } from "../../state/store";
import {
upsertFilesBeginning,
addToHashMap,
upsertFiles,
setEditFile,
updateFile,
updateInHashMap,
setEditPlaylist,
} from "../../state/features/fileSlice.ts";
import ImageUploader from "../common/ImageUploader";
import {
QSHARE_PLAYLIST_BASE,
QSHARE_FILE_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";
import {
firstCategories,
secondCategories,
} from "../../constants/Categories/1stCategories.ts";
const uid = new ShortUniqueId();
const shortuid = new ShortUniqueId({ length: 5 });
interface NewCrowdfundProps {
editId?: string;
editContent?: null | {
title: string;
user: string;
coverImage: string | null;
};
}
interface VideoFile {
file: File;
title: string;
description: string;
coverImage?: string;
}
export const EditPlaylist = () => {
const theme = useTheme();
const dispatch = useDispatch();
const username = useSelector((state: RootState) => state.auth?.user?.name);
const userAddress = useSelector(
(state: RootState) => state.auth?.user?.address
);
const editVideoProperties = useSelector(
(state: RootState) => state.file.editPlaylistProperties
);
const [playlistData, setPlaylistData] = useState<any>(null);
const [title, setTitle] = useState<string>("");
const [description, setDescription] = useState<string>("");
const [coverImage, setCoverImage] = useState<string>("");
const [videos, setVideos] = useState([]);
const [selectedCategoryVideos, setSelectedCategoryVideos] =
useState<any>(null);
const [selectedSubCategoryVideos, setSelectedSubCategoryVideos] =
useState<any>(null);
const isNew = useMemo(() => {
return editVideoProperties?.mode === "new";
}, [editVideoProperties]);
useEffect(() => {
if (isNew) {
setPlaylistData({
videos: [],
});
}
}, [isNew]);
// useEffect(() => {
// if (editVideoProperties) {
// const descriptionString = editVideoProperties?.description || "";
// // Splitting the string at the asterisks
// const parts = descriptionString.split("**");
// // The part within the asterisks
// const extractedString = parts[1];
// // The part after the last asterisks
// const description = parts[2] || ""; // Using '|| '' to handle cases where there is no text after the last **
// setTitle(editVideoProperties?.title || "");
// setDescription(editVideoProperties?.fullDescription || "");
// setCoverImage(editVideoProperties?.videoImage || "");
// // Split the extracted string into key-value pairs
// const keyValuePairs = extractedString.split(";");
// // Initialize variables to hold the category and subcategory values
// let category, subcategory;
// // Loop through each key-value pair
// keyValuePairs.forEach((pair) => {
// const [key, value] = pair.split(":");
// // Check the key and assign the value to the appropriate variable
// if (key === "category") {
// category = value;
// } else if (key === "subcategory") {
// subcategory = value;
// }
// });
// if(category){
// const selectedOption = categories.find((option) => option.id === +category);
// setSelectedCategoryVideos(selectedOption || null);
// }
// if(subcategory){
// const selectedOption = categories.find((option) => option.id === +subcategory);
// setSelectedCategoryVideos(selectedOption || null);
// }
// }
// }, [editVideoProperties]);
const checkforPlaylist = React.useCallback(async videoList => {
try {
const combinedData: any = {};
const videos = [];
if (videoList) {
for (const vid of videoList) {
const url = `/arbitrary/resources/search?mode=ALL&service=DOCUMENT&identifier=${vid.identifier}&limit=1&includemetadata=true&reverse=true&name=${vid.name}&exactmatchnames=true&offset=0`;
const response = await fetch(url, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
const responseDataSearchVid = await response.json();
if (responseDataSearchVid?.length > 0) {
let resourceData2 = responseDataSearchVid[0];
videos.push(resourceData2);
}
}
}
combinedData.videos = videos;
setPlaylistData(combinedData);
} catch (error) {}
}, []);
useEffect(() => {
if (editVideoProperties) {
setTitle(editVideoProperties?.title || "");
if (editVideoProperties?.htmlDescription) {
setDescription(editVideoProperties?.htmlDescription);
} else if (editVideoProperties?.description) {
const paragraph = `<p>${editVideoProperties?.description}</p>`;
setDescription(paragraph);
}
setCoverImage(editVideoProperties?.image || "");
setVideos(editVideoProperties?.videos || []);
if (editVideoProperties?.category) {
const selectedOption = firstCategories.find(
option => option.id === +editVideoProperties.category
);
setSelectedCategoryVideos(selectedOption || null);
}
if (
editVideoProperties?.category &&
editVideoProperties?.subcategory &&
secondCategories[+editVideoProperties?.category]
) {
const selectedOption = secondCategories[
+editVideoProperties?.category
]?.find(option => option.id === +editVideoProperties.subcategory);
setSelectedSubCategoryVideos(selectedOption || null);
}
if (editVideoProperties?.videos) {
checkforPlaylist(editVideoProperties?.videos);
}
}
}, [editVideoProperties]);
const onClose = () => {
setTitle("");
setDescription("");
setVideos([]);
setPlaylistData(null);
setSelectedCategoryVideos(null);
setSelectedSubCategoryVideos(null);
setCoverImage("");
dispatch(setEditPlaylist(null));
};
async function publishQDNResource() {
try {
if (!title) throw new Error("Please enter a title");
if (!description) throw new Error("Please enter a description");
if (!coverImage) throw new Error("Please select cover image");
if (!selectedCategoryVideos) throw new Error("Please select a category");
if (!editVideoProperties) return;
if (!userAddress) throw new Error("Unable to locate user address");
let errorMsg = "";
let name = "";
if (username) {
name = username;
}
if (!name) {
errorMsg =
"Cannot publish without access to your name. Please authenticate.";
}
if (!isNew && editVideoProperties?.user !== username) {
errorMsg = "Cannot publish another user's resource";
}
if (errorMsg) {
dispatch(
setNotification({
msg: errorMsg,
alertType: "error",
})
);
return;
}
const category = selectedCategoryVideos.id;
const subcategory = selectedSubCategoryVideos?.id || "";
const videoStructured = playlistData.videos.map(item => {
const descriptionVid = item?.metadata?.description;
if (!descriptionVid) throw new Error("cannot find video code");
// Split the string by ';'
let parts = descriptionVid.split(";");
// Initialize a variable to hold the code value
let codeValue = "";
// Loop through the parts to find the one that starts with 'code:'
for (let part of parts) {
if (part.startsWith("code:")) {
codeValue = part.split(":")[1];
break;
}
}
if (!codeValue) throw new Error("cannot find video code");
return {
identifier: item.identifier,
name: item.name,
service: item.service,
code: codeValue,
};
});
const id = uid();
let commentsId = editVideoProperties?.id;
if (isNew) {
commentsId = `${QSHARE_PLAYLIST_BASE}_cm_${id}`;
}
const stringDescription = extractTextFromHTML(description);
const playlistObject: any = {
title,
version: 1,
description: stringDescription,
htmlDescription: description,
image: coverImage,
videos: videoStructured,
commentsId: commentsId,
category,
subcategory,
};
const codes = videoStructured.map(item => `c:${item.code};`).join("");
let metadescription =
`**category:${category};subcategory:${subcategory};${codes}**` +
stringDescription.slice(0, 120);
const crowdfundObjectToBase64 = await objectToBase64(playlistObject);
// Description is obtained from raw data
let identifier = editVideoProperties?.id;
const sanitizeTitle = title
.replace(/[^a-zA-Z0-9\s-]/g, "")
.replace(/\s+/g, "-")
.replace(/-+/g, "-")
.trim()
.toLowerCase();
if (isNew) {
identifier = `${QSHARE_PLAYLIST_BASE}${sanitizeTitle.slice(0, 30)}_${id}`;
}
const requestBodyJson: any = {
action: "PUBLISH_QDN_RESOURCE",
name: username,
service: "PLAYLIST",
data64: crowdfundObjectToBase64,
title: title.slice(0, 50),
description: metadescription,
identifier: identifier,
tag1: QSHARE_FILE_BASE,
};
await qortalRequest(requestBodyJson);
if (isNew) {
const objectToStore = {
title: title.slice(0, 50),
description: metadescription,
id: identifier,
service: "PLAYLIST",
name: username,
...playlistObject,
};
dispatch(updateFile(objectToStore));
dispatch(updateInHashMap(objectToStore));
} else {
dispatch(
updateFile({
...editVideoProperties,
...playlistObject,
})
);
dispatch(
updateInHashMap({
...editVideoProperties,
...playlistObject,
})
);
}
onClose();
} catch (error: any) {
let notificationObj: any = null;
if (typeof error === "string") {
notificationObj = {
msg: error || "Failed to publish update",
alertType: "error",
};
} else if (typeof error?.error === "string") {
notificationObj = {
msg: error?.error || "Failed to publish update",
alertType: "error",
};
} else {
notificationObj = {
msg: error?.message || "Failed to publish update",
alertType: "error",
};
}
if (!notificationObj) return;
dispatch(setNotification(notificationObj));
throw new Error("Failed to publish update");
}
}
const handleOnchange = (index: number, type: string, value: string) => {
// setFiles((prev) => {
// let formattedValue = value
// console.log({type})
// if(type === 'title'){
// formattedValue = value.replace(/[^a-zA-Z0-9\s]/g, "")
// }
// const copyFiles = [...prev];
// copyFiles[index] = {
// ...copyFiles[index],
// [type]: formattedValue,
// };
// return copyFiles;
// });
};
const handleOptionCategoryChangeVideos = (
event: SelectChangeEvent<string>
) => {
const optionId = event.target.value;
const selectedOption = firstCategories.find(
option => option.id === +optionId
);
setSelectedCategoryVideos(selectedOption || null);
};
const handleOptionSubCategoryChangeVideos = (
event: SelectChangeEvent<string>,
subcategories: any[]
) => {
const optionId = event.target.value;
const selectedOption = subcategories.find(
option => option.id === +optionId
);
setSelectedSubCategoryVideos(selectedOption || null);
};
const removeVideo = index => {
const copyData = structuredClone(playlistData);
copyData.videos.splice(index, 1);
setPlaylistData(copyData);
};
const addVideo = data => {
if (playlistData?.videos?.length > 9) {
dispatch(
setNotification({
msg: "Max 10 videos per playlist",
alertType: "error",
})
);
return;
}
const copyData = structuredClone(playlistData);
copyData.videos = [...copyData.videos, { ...data }];
setPlaylistData(copyData);
};
return (
<>
<Modal
open={!!editVideoProperties}
aria-labelledby="modal-title"
aria-describedby="modal-description"
>
<ModalBody>
<Box
sx={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
}}
>
{isNew ? (
<NewCrowdfundTitle>Create new playlist</NewCrowdfundTitle>
) : (
<NewCrowdfundTitle>Update Playlist properties</NewCrowdfundTitle>
)}
</Box>
<>
<Box
sx={{
display: "flex",
gap: "20px",
alignItems: "center",
}}
>
<FormControl fullWidth sx={{ marginBottom: 2 }}>
<InputLabel id="Category">Select a Category</InputLabel>
<Select
labelId="Category"
input={<OutlinedInput label="Select a Category" />}
value={selectedCategoryVideos?.id || ""}
onChange={handleOptionCategoryChangeVideos}
>
{firstCategories.map(option => (
<MenuItem key={option.id} value={option.id}>
{option.name}
</MenuItem>
))}
</Select>
</FormControl>
{selectedCategoryVideos &&
secondCategories[selectedCategoryVideos?.id] && (
<FormControl fullWidth sx={{ marginBottom: 2 }}>
<InputLabel id="Category">Select a Sub-Category</InputLabel>
<Select
labelId="Sub-Category"
input={<OutlinedInput label="Select a Sub-Category" />}
value={selectedSubCategoryVideos?.id || ""}
onChange={e =>
handleOptionSubCategoryChangeVideos(
e,
secondCategories[selectedCategoryVideos?.id]
)
}
>
{secondCategories[selectedCategoryVideos.id].map(
option => (
<MenuItem key={option.id} value={option.id}>
{option.name}
</MenuItem>
)
)}
</Select>
</FormControl>
)}
</Box>
<React.Fragment>
{!coverImage ? (
<ImageUploader onPick={(img: string) => setCoverImage(img)}>
<AddCoverImageButton variant="contained">
Add Cover Image
<AddLogoIcon
sx={{
height: "25px",
width: "auto",
}}
></AddLogoIcon>
</AddCoverImageButton>
</ImageUploader>
) : (
<LogoPreviewRow>
<CoverImagePreview src={coverImage} alt="logo" />
<TimesIcon
color={theme.palette.text.primary}
onClickFunc={() => setCoverImage("")}
height={"32"}
width={"32"}
></TimesIcon>
</LogoPreviewRow>
)}
<CustomInputField
name="title"
label="Title of playlist"
variant="filled"
value={title}
onChange={e => {
const value = e.target.value;
const formattedValue = value.replace(
/[^a-zA-Z0-9\s-_!?]/g,
""
);
setTitle(formattedValue);
}}
inputProps={{ maxLength: 180 }}
required
/>
{/* <CustomInputField
name="description"
label="Describe your playlist in a few words"
variant="filled"
value={description}
onChange={(e) => setDescription(e.target.value)}
inputProps={{ maxLength: 10000 }}
multiline
maxRows={3}
required
/> */}
<Typography
sx={{
fontSize: "18px",
}}
>
Description of playlist
</Typography>
<TextEditor
inlineContent={description}
setInlineContent={value => {
setDescription(value);
}}
/>
</React.Fragment>
<PlaylistListEdit
playlistData={playlistData}
removeVideo={removeVideo}
addVideo={addVideo}
/>
</>
<CrowdfundActionButtonRow>
<CrowdfundActionButton
onClick={() => {
onClose();
}}
variant="contained"
color="error"
>
Cancel
</CrowdfundActionButton>
<Box
sx={{
display: "flex",
gap: "20px",
alignItems: "center",
}}
>
<CrowdfundActionButton
variant="contained"
onClick={() => {
publishQDNResource();
}}
>
Publish
</CrowdfundActionButton>
</Box>
</CrowdfundActionButtonRow>
</ModalBody>
</Modal>
</>
);
};

View File

@@ -1,586 +0,0 @@
import { styled } from "@mui/system";
import {
Accordion,
AccordionDetails,
AccordionSummary,
Box,
Button,
Grid,
Rating,
TextField,
Typography,
Select
} from "@mui/material";
import AddPhotoAlternateIcon from "@mui/icons-material/AddPhotoAlternate";
import { TimesSVG } from "../../assets/svgs/TimesSVG";
export const DoubleLine = styled(Typography)`
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
overflow: hidden;
`;
export const MainContainer = styled(Grid)({
width: "100%",
display: "flex",
alignItems: "flex-start",
justifyContent: "center",
margin: 0,
});
export const MainCol = styled(Grid)(({ theme }) => ({
display: "flex",
flexDirection: "column",
alignItems: "center",
width: "100%",
padding: "20px",
}));
export const CreateContainer = styled(Box)(({ theme }) => ({
position: "fixed",
bottom: "20px",
right: "20px",
cursor: "pointer",
background: theme.palette.background.default,
width: "50px",
height: "50px",
display: "flex",
justifyContent: "center",
alignItems: "center",
borderRadius: "50%",
}));
export const ModalBody = styled(Box)(({ theme }) => ({
position: "absolute",
backgroundColor: theme.palette.background.default,
borderRadius: "4px",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
width: "75%",
maxWidth: "900px",
padding: "15px 35px",
display: "flex",
flexDirection: "column",
gap: "17px",
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",
"&::-webkit-scrollbar-track": {
backgroundColor: theme.palette.background.paper,
},
"&::-webkit-scrollbar-track:hover": {
backgroundColor: theme.palette.background.paper,
},
"&::-webkit-scrollbar": {
width: "16px",
height: "10px",
backgroundColor: theme.palette.mode === "light" ? "#f6f8fa" : "#292d3e",
},
"&::-webkit-scrollbar-thumb": {
backgroundColor: theme.palette.mode === "light" ? "#d3d9e1" : "#575757",
borderRadius: "8px",
backgroundClip: "content-box",
border: "4px solid transparent",
},
"&::-webkit-scrollbar-thumb:hover": {
backgroundColor: theme.palette.mode === "light" ? "#b7bcc4" : "#474646",
},
}));
export const NewCrowdfundTitle = styled(Typography)(({ theme }) => ({
fontWeight: 400,
fontFamily: "Raleway",
fontSize: "25px",
userSelect: "none",
}));
export const NewCrowdFundFont = styled(Typography)(({ theme }) => ({
fontWeight: 400,
fontFamily: "Raleway",
fontSize: "18px",
userSelect: "none",
}));
export const NewCrowdfundTimeDescription = styled(Typography)(({ theme }) => ({
fontWeight: 400,
fontFamily: "Raleway",
fontSize: "18px",
userSelect: "none",
fontStyle: "italic",
textDecoration: "underline",
}));
export const CustomInputField = styled(TextField)(({ theme }) => ({
fontFamily: "Mulish",
fontSize: "19px",
letterSpacing: "0px",
fontWeight: 400,
color: theme.palette.text.primary,
backgroundColor: theme.palette.background.default,
borderColor: theme.palette.background.paper,
"& label": {
color: theme.palette.mode === "light" ? "#808183" : "#edeef0",
fontFamily: "Mulish",
fontSize: "19px",
letterSpacing: "0px",
fontWeight: 400,
},
"& label.Mui-focused": {
color: theme.palette.mode === "light" ? "#A0AAB4" : "#d7d8da",
},
"& .MuiInput-underline:after": {
borderBottomColor: theme.palette.mode === "light" ? "#B2BAC2" : "#c9cccf",
},
"& .MuiOutlinedInput-root": {
"& fieldset": {
borderColor: "#E0E3E7",
},
"&:hover fieldset": {
borderColor: "#B2BAC2",
},
"&.Mui-focused fieldset": {
borderColor: "#6F7E8C",
},
},
"& .MuiInputBase-root": {
fontFamily: "Mulish",
fontSize: "19px",
letterSpacing: "0px",
fontWeight: 400,
},
"& [class$='-MuiFilledInput-root']": {
padding: "30px 12px 8px",
},
"& .MuiFilledInput-root:after": {
borderBottomColor: theme.palette.secondary.main,
},
}));
export const CrowdfundTitle = styled(Typography)(({ theme }) => ({
fontFamily: "Copse",
letterSpacing: "1px",
fontWeight: 400,
fontSize: "20px",
color: theme.palette.text.primary,
userSelect: "none",
wordBreak: "break-word",
}));
export const CrowdfundSubTitleRow = styled(Box)({
display: "flex",
alignItems: "center",
justifyContent: "center",
width: "100%",
flexDirection: "row",
});
export const CrowdfundSubTitle = styled(Typography)(({ theme }) => ({
fontFamily: "Copse",
letterSpacing: "1px",
fontWeight: 400,
fontSize: "17px",
color: theme.palette.text.primary,
userSelect: "none",
wordBreak: "break-word",
borderBottom: `1px solid ${theme.palette.text.primary}`,
paddingBottom: "1.5px",
width: "fit-content",
textDecoration: "none",
}));
export const CrowdfundDescription = styled(Typography)(({ theme }) => ({
fontFamily: "Raleway",
fontSize: "16px",
color: theme.palette.text.primary,
userSelect: "none",
wordBreak: "break-word",
}));
export const Spacer = ({ height }: any) => {
return (
<Box
sx={{
height: height,
}}
/>
);
};
export const StyledCardHeaderComment = styled(Box)({
display: "flex",
alignItems: "center",
justifyContent: "flex-start",
gap: "5px",
padding: "7px 0px",
});
export const StyledCardCol = styled(Box)({
display: "flex",
overflow: "hidden",
flexDirection: "column",
gap: "2px",
alignItems: "flex-start",
width: "100%",
});
export const StyledCardColComment = styled(Box)({
display: "flex",
overflow: "hidden",
flexDirection: "column",
gap: "2px",
alignItems: "flex-start",
width: "100%",
});
export const AuthorTextComment = styled(Typography)({
fontFamily: "Raleway, sans-serif",
fontSize: "16px",
lineHeight: "1.2",
});
export const AddLogoIcon = styled(AddPhotoAlternateIcon)(({ theme }) => ({
color: "#fff",
height: "25px",
width: "auto",
}));
export const CoverImagePreview = styled("img")(({ theme }) => ({
width: "100px",
height: "100px",
objectFit: "contain",
userSelect: "none",
borderRadius: "3px",
marginBottom: "10px",
}));
export const LogoPreviewRow = styled(Box)(({ theme }) => ({
display: "flex",
alignItems: "center",
gap: "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",
},
}));
export const CrowdfundCardTitle = styled(DoubleLine)(({ theme }) => ({
fontFamily: "Montserrat",
fontSize: "24px",
letterSpacing: "-0.3px",
userSelect: "none",
marginBottom: "auto",
textAlign: "center",
"@media (max-width: 650px)": {
fontSize: "18px",
},
}));
export const CrowdfundUploadDate = styled(Typography)(({ theme }) => ({
fontFamily: "Montserrat",
fontSize: "12px",
letterSpacing: "0.2px",
color: theme.palette.text.primary,
userSelect: "none",
}));
export const CATContainer = styled(Box)(({ theme }) => ({
position: "relative",
display: "flex",
padding: "15px",
flexDirection: "column",
gap: "20px",
justifyContent: "center",
width: "100%",
alignItems: "center",
}));
export const AddCrowdFundButton = styled(Button)(({ theme }) => ({
display: "flex",
alignItems: "center",
textTransform: "none",
padding: "10px 25px",
fontSize: "15px",
gap: "8px",
color: "#ffffff",
backgroundColor:
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",
},
}));
export const EditCrowdFundButton = styled(Button)(({ theme }) => ({
display: "flex",
alignItems: "center",
textTransform: "none",
padding: "5px 12px",
gap: "8px",
color: "#ffffff",
backgroundColor:
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",
},
}));
export const CrowdfundListWrapper = styled(Box)(({ theme }) => ({
width: "100%",
display: "flex",
flexDirection: "column",
alignItems: "center",
marginTop: "0px",
background: theme.palette.background.default,
}));
export const CrowdfundTitleRow = styled(Box)(({ theme }) => ({
display: "flex",
alignItems: "center",
justifyContent: "center",
width: "100%",
gap: "10px",
}));
export const CrowdfundPageTitle = styled(Typography)(({ theme }) => ({
fontFamily: "Copse",
fontSize: "35px",
fontWeight: 400,
letterSpacing: "1px",
userSelect: "none",
color: theme.palette.text.primary,
}));
export const CrowdfundStatusRow = styled(Box)(({ theme }) => ({
display: "flex",
alignItems: "center",
justifyContent: "center",
fontFamily: "Mulish",
fontSize: "21px",
fontWeight: 400,
letterSpacing: 0,
border: `1px solid ${theme.palette.text.primary}`,
borderRadius: "8px",
padding: "15px 25px",
userSelect: "none",
}));
export const CrowdfundDescriptionRow = styled(Box)({
display: "flex",
alignItems: "center",
justifyContent: "center",
width: "100%",
fontFamily: "Montserrat",
fontSize: "18px",
fontWeight: 400,
letterSpacing: 0,
});
export const AboutMyCrowdfund = styled(Typography)(({ theme }) => ({
fontFamily: "Copse",
fontSize: "23px",
fontWeight: 400,
letterSpacing: "1px",
userSelect: "none",
color: theme.palette.text.primary,
}));
export const CrowdfundInlineContentRow = styled(Box)({
display: "flex",
alignItems: "center",
justifyContent: "center",
width: "100%",
});
export const CrowdfundInlineContent = styled(Box)(({ theme }) => ({
display: "flex",
fontFamily: "Mulish",
fontSize: "19px",
fontWeight: 400,
letterSpacing: 0,
userSelect: "none",
color: theme.palette.text.primary,
}));
export const CrowdfundAccordion = styled(Accordion)(({ theme }) => ({
backgroundColor: theme.palette.primary.light,
"& .Mui-expanded": {
minHeight: "auto !important",
},
}));
export const CrowdfundAccordionSummary = styled(AccordionSummary)({
height: "50px",
"& .Mui-expanded": {
margin: "0px !important",
},
});
export const CrowdfundAccordionFont = styled(Typography)(({ theme }) => ({
fontFamily: "Mulish",
fontSize: "20px",
fontWeight: 400,
letterSpacing: "0px",
color: theme.palette.text.primary,
userSelect: "none",
}));
export const CrowdfundAccordionDetails = styled(AccordionDetails)({
padding: "0px 16px 16px 16px",
});
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 CoverImage = styled("img")({
width: "100%",
height: "250px",
objectFit: "cover",
objectPosition: "center",
});
export const CrowdfundActionButtonRow = styled(Box)({
display: "flex",
alignItems: "center",
justifyContent: "space-between",
width: "100%",
});
export const CrowdfundActionButton = styled(Button)(({ theme }) => ({
display: "flex",
alignItems: "center",
fontFamily: "Montserrat",
fontSize: "16px",
fontWeight: 400,
letterSpacing: "0.2px",
color: "white",
gap: "5px",
}));
export const BackToHomeButton = styled(Button)(({ theme }) => ({
position: "absolute",
top: "20px",
left: "20px",
display: "flex",
alignItems: "center",
fontFamily: "Montserrat",
fontSize: "13px",
fontWeight: 400,
letterSpacing: "0.2px",
color: "white",
gap: "5px",
padding: "5px 10px",
backgroundColor: theme.palette.secondary.main,
transition: "all 0.3s ease-in-out",
"&:hover": {
backgroundColor: theme.palette.secondary.dark,
cursor: "pointer",
},
}));
export const CrowdfundLoaderRow = styled(Box)({
display: "flex",
alignItems: "center",
justifyContent: "center",
gap: "10px",
padding: "10px",
});
export const RatingContainer = styled(Box)({
display: "flex",
alignItems: "center",
padding: "1px 5px",
borderRadius: "5px",
backgroundColor: "transparent",
transition: "all 0.3s ease-in-out",
"&:hover": {
cursor: "pointer",
backgroundColor: "#e4ddddac",
},
});
export const StyledRating = styled(Rating)({
fontSize: "28px",
});
export const NoReviewsFont = styled(Typography)(({ theme }) => ({
fontFamily: "Mulish",
fontWeight: 400,
letterSpacing: 0,
color: theme.palette.text.primary,
}));
export const StyledButton = styled(Button)(({ theme }) => ({
fontWeight: 600,
color: theme.palette.text.primary
}))
export const CustomSelect = styled(Select)(({ theme }) => ({
fontFamily: "Mulish",
fontSize: "19px",
letterSpacing: "0px",
fontWeight: 400,
color: theme.palette.text.primary,
backgroundColor: theme.palette.background.default,
'& .MuiSelect-select': {
padding: '12px',
fontFamily: "Mulish",
fontSize: "19px",
letterSpacing: "0px",
fontWeight: 400,
borderRadius: theme.shape.borderRadius, // Match border radius
},
'&:before': {
// Underline style
borderBottomColor: theme.palette.mode === "light" ? "#B2BAC2" : "#c9cccf",
},
'&:after': {
// Underline style when focused
borderBottomColor: theme.palette.secondary.main,
},
'& .MuiOutlinedInput-root': {
'& fieldset': {
borderColor: "#E0E3E7",
},
'&:hover fieldset': {
borderColor: "#B2BAC2",
},
'&.Mui-focused fieldset': {
borderColor: "#6F7E8C",
},
},
'& .MuiInputBase-root': {
fontFamily: "Mulish",
fontSize: "19px",
letterSpacing: "0px",
fontWeight: 400,
color: theme.palette.text.primary,
},
}));

View File

@@ -290,7 +290,15 @@ export const PublishFile = ({ editId, editContent }: NewCrowdfundProps) => {
{editId ? null : (
<StyledButton
color="primary"
startIcon={<AddBoxIcon />}
startIcon={
<AddBoxIcon
sx={{
color: "#66C3FE",
width: "40px",
height: "40px",
}}
/>
}
onClick={() => {
setIsOpen(true);
}}

View File

@@ -9,10 +9,11 @@ import {
Rating,
TextField,
Typography,
Select
Select,
} from "@mui/material";
import AddPhotoAlternateIcon from "@mui/icons-material/AddPhotoAlternate";
import { TimesSVG } from "../../assets/svgs/TimesSVG";
import { fontSizeSmall } from "../../constants/Misc.ts";
export const DoubleLine = styled(Typography)`
display: -webkit-box;
@@ -67,9 +68,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 +160,6 @@ export const CustomInputField = styled(TextField)(({ theme }) => ({
},
}));
export const CrowdfundTitle = styled(Typography)(({ theme }) => ({
fontFamily: "Copse",
letterSpacing: "1px",
@@ -203,11 +202,11 @@ export const CrowdfundDescription = styled(Typography)(({ theme }) => ({
export const Spacer = ({ height }: any) => {
return (
<Box
sx={{
height: height,
}}
/>
<Box
sx={{
height: height,
}}
/>
);
};
@@ -314,14 +313,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 +332,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",
},
}));
@@ -540,8 +539,9 @@ export const NoReviewsFont = styled(Typography)(({ theme }) => ({
export const StyledButton = styled(Button)(({ theme }) => ({
fontWeight: 600,
color: theme.palette.text.primary,
fontFamily: "Cairo"
}))
fontFamily: "Cairo",
fontSize: fontSizeSmall,
}));
export const CustomSelect = styled(Select)(({ theme }) => ({
fontFamily: "Mulish",
@@ -550,38 +550,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,
},
}));
}));

View File

@@ -0,0 +1,65 @@
import ShareIcon from "@mui/icons-material/Share";
import {
Box,
ButtonBase,
Tooltip,
tooltipClasses,
TooltipProps,
} from "@mui/material";
import { styled } from "@mui/system";
import { useDispatch } from "react-redux";
import { setNotification } from "../../state/features/notificationsSlice.ts";
export interface CopyLinkButtonProps {
link: string;
tooltipTitle: string;
}
export const TooltipLine = styled("div")(({ theme }) => ({
fontSize: "18px",
}));
const CustomWidthTooltipStyles = styled(
({ className, ...props }: TooltipProps) => (
<Tooltip {...props} classes={{ popper: className }} />
)
)({
[`& .${tooltipClasses.tooltip}`]: {
maxWidth: 600,
},
});
export const CustomTooltip = ({ title, ...props }: TooltipProps) => {
if (typeof title === "string") title = <TooltipLine>{title}</TooltipLine>;
return <CustomWidthTooltipStyles title={title} {...props} />;
};
export const CopyLinkButton = ({ link, tooltipTitle }: CopyLinkButtonProps) => {
const dispatch = useDispatch();
return (
<CustomTooltip title={tooltipTitle} placement={"top"} arrow>
<Box
sx={{
cursor: "pointer",
}}
>
<ButtonBase
onClick={() => {
navigator.clipboard.writeText(link).then(() => {
dispatch(
setNotification({
msg: "Copied to clipboard!",
alertType: "success",
})
);
});
}}
>
<ShareIcon />
</ButtonBase>
</Box>
</CustomTooltip>
);
};

View File

@@ -1,3 +1,13 @@
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 fileMaxSize = 2147; // Size in Megabytes (decimal)
export const maxSize = fileMaxSize * 1024 * 1024;
export const fontSizeExSmall = "70%";
export const fontSizeSmall = "80%";
export const fontSizeMedium = "100%";
export const fontSizeLarge = "120%";
export const fontSizeExLarge = "150%";
export const maxCommentLength = 10_000;

View File

@@ -1,6 +1,8 @@
import React, { useEffect, useMemo, useRef, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { useNavigate, useParams } from "react-router-dom";
import { CopyLinkButton } from "../../components/common/CopyLinkButton.tsx";
import { fontSizeMedium } from "../../constants/Misc.ts";
import { setIsLoadingGlobal } from "../../state/features/globalSlice";
import { Avatar, Box, Typography, useTheme } from "@mui/material";
import { RootState } from "../../state/store";
@@ -309,7 +311,7 @@ export const FileContent = () => {
});
return categoryDisplay;
}
return "no videodata";
return "No file data";
}, [fileData]);
return (
@@ -404,16 +406,21 @@ export const FileContent = () => {
</StyledCardHeaderComment>
</Box>
<Spacer height="15px" />
<Box>
<Typography
sx={{
fontWeight: "bold",
fontSize: "16px",
userSelect: "none",
}}
>
{categoriesDisplay}
</Typography>
<Box
sx={{
display: "flex",
alignItems: "center",
gap: "10px",
fontWeight: "bold",
fontSize: fontSizeMedium,
userSelect: "none",
}}
>
{categoriesDisplay}
<CopyLinkButton
link={`qortal://APP/Q-Share/share/${encodeURIComponent(fileData?.user)}/${encodeURIComponent(fileData?.id)}`}
tooltipTitle={`Copy page link`}
/>
</Box>
<Spacer height="15px" />
<Box

View File

@@ -16,7 +16,6 @@ import { VideoPlayerGlobal } from "../components/common/VideoPlayerGlobal";
import { Rnd } from "react-rnd";
import { RequestQueue } from "../utils/queue";
import { EditFile } from "../components/EditFile/EditFile.tsx";
import { EditPlaylist } from "../components/EditPlaylist/EditPlaylist";
import ConsentModal from "../components/common/ConsentModal";
import { useIframe } from "../hooks/useIframe.tsx";
@@ -30,7 +29,7 @@ let timer: number | null = null;
export const queue = new RequestQueue();
const GlobalWrapper: React.FC<Props> = ({ children, setTheme }) => {
useIframe()
useIframe();
const dispatch = useDispatch();
const isDragging = useRef(false);
const [userAvatar, setUserAvatar] = useState<string>("");
@@ -141,7 +140,7 @@ const GlobalWrapper: React.FC<Props> = ({ children, setTheme }) => {
authenticate={askForAccountInformation}
/>
<EditFile />
<EditPlaylist />
<Rnd
onDragStart={onDragStart}
onDragStop={onDragStop}