Publish Forum button changed to Manage Forums. It handles both publishing and editing forums.
This commit is contained in:
parent
0659433f03
commit
dd500a3ebb
28
src/App.tsx
28
src/App.tsx
@ -1,31 +1,20 @@
|
||||
// @ts-nocheck
|
||||
|
||||
import { signal } from "@preact/signals-react";
|
||||
import { useEffect } from "react";
|
||||
import { Routes, Route } from "react-router-dom";
|
||||
import { CssBaseline } from "@mui/material";
|
||||
|
||||
import { ThemeProvider } from "@mui/material/styles";
|
||||
import { CssBaseline } from "@mui/material";
|
||||
import { ForumData } from "./pages/Forum/ForumModal";
|
||||
import { Home } from "./pages/Home/Home";
|
||||
import { lightTheme, darkTheme } from "./styles/theme";
|
||||
import { store } from "./state/store";
|
||||
import { Provider } from "react-redux";
|
||||
import { fetchForumData } from "./utils/QortalRequests";
|
||||
import GlobalWrapper from "./wrappers/GlobalWrapper";
|
||||
import DownloadWrapper from "./wrappers/DownloadWrapper";
|
||||
import { Route, Routes } from "react-router-dom";
|
||||
import Notification from "./components/common/Notification/Notification";
|
||||
|
||||
export const forums = signal<ForumData[]>([]);
|
||||
import { Home } from "./pages/Home/Home";
|
||||
import { store } from "./state/store";
|
||||
import { darkTheme } from "./styles/theme";
|
||||
import DownloadWrapper from "./wrappers/DownloadWrapper";
|
||||
import GlobalWrapper from "./wrappers/GlobalWrapper";
|
||||
|
||||
function App() {
|
||||
const themeColor = window._qdnTheme;
|
||||
useEffect(() => {
|
||||
fetchForumData().then(data => {
|
||||
if (data) forums.value = data;
|
||||
console.log("forums is : ", forums.value);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Provider store={store}>
|
||||
<ThemeProvider theme={darkTheme}>
|
||||
@ -35,6 +24,7 @@ function App() {
|
||||
<CssBaseline />
|
||||
<Routes>
|
||||
<Route path="/" element={<Home />} />
|
||||
<Route path="/forum/:" element={<Home />} />
|
||||
<Route path="/sponsorshipData" element={<Home />} />
|
||||
</Routes>
|
||||
</GlobalWrapper>
|
||||
|
@ -4,28 +4,38 @@ import {
|
||||
MenuItem,
|
||||
OutlinedInput,
|
||||
Select,
|
||||
SelectChangeEvent,
|
||||
} from "@mui/material";
|
||||
import { SxProps } from "@mui/system";
|
||||
import { signal, Signal } from "@preact/signals-react";
|
||||
import { signal, Signal, useSignal } from "@preact/signals-react";
|
||||
import { useSignals } from "@preact/signals-react/runtime";
|
||||
import React, { useState } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
interface SelectFieldProps {
|
||||
options: string[];
|
||||
value: Signal<string>;
|
||||
initialValue?: string;
|
||||
label: string;
|
||||
sx?: SxProps;
|
||||
afterChange?: (e: string) => void;
|
||||
width?: string;
|
||||
}
|
||||
export const SelectField = ({
|
||||
options,
|
||||
value,
|
||||
initialValue = "",
|
||||
label,
|
||||
sx = {},
|
||||
width = "100%",
|
||||
afterChange,
|
||||
}: SelectFieldProps) => {
|
||||
useSignals();
|
||||
|
||||
const value = useSignal<string>(initialValue);
|
||||
useEffect(() => {
|
||||
value.value = initialValue;
|
||||
}, [initialValue]);
|
||||
|
||||
return (
|
||||
<FormControl fullWidth>
|
||||
<FormControl fullWidth sx={{ width: width }}>
|
||||
<InputLabel
|
||||
sx={{
|
||||
fontSize: "100%",
|
||||
@ -41,7 +51,9 @@ export const SelectField = ({
|
||||
value={value.value || ""}
|
||||
variant={"standard"}
|
||||
onChange={e => {
|
||||
value.value = e.target.value;
|
||||
const eventValue = e.target.value;
|
||||
value.value = eventValue;
|
||||
if (afterChange) afterChange(eventValue);
|
||||
}}
|
||||
sx={{ color: "black", ...sx }}
|
||||
>
|
||||
|
@ -1,28 +0,0 @@
|
||||
import MenuBookIcon from "@mui/icons-material/MenuBook";
|
||||
import React from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { forums } from "../../App";
|
||||
import {
|
||||
ComposeContainer,
|
||||
ComposeP,
|
||||
InstanceContainer,
|
||||
} from "../Home/Home-styles";
|
||||
import { ForumModal } from "./ForumModal";
|
||||
|
||||
export const ActionBar = () => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<InstanceContainer>
|
||||
<ForumModal />
|
||||
<ForumModal forumData={forums.value} />
|
||||
<ComposeContainer
|
||||
sx={{ width: "200px", marginLeft: "auto" }}
|
||||
onClick={() => navigate("/sponsorshipData")}
|
||||
>
|
||||
<MenuBookIcon />
|
||||
<ComposeP sx={{ fontSize: "70%" }}>{"Sponsorship Data"}</ComposeP>
|
||||
</ComposeContainer>
|
||||
</InstanceContainer>
|
||||
);
|
||||
};
|
49
src/pages/Forum/Components/ActionBar.tsx
Normal file
49
src/pages/Forum/Components/ActionBar.tsx
Normal file
@ -0,0 +1,49 @@
|
||||
import MenuBookIcon from "@mui/icons-material/MenuBook";
|
||||
import { Signal, signal } from "@preact/signals-react";
|
||||
import { useSignals } from "@preact/signals-react/runtime";
|
||||
import React, { useEffect } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { fetchForumData } from "../../../utils/QortalRequests";
|
||||
import {
|
||||
ComposeContainer,
|
||||
ComposeP,
|
||||
InstanceContainer,
|
||||
} from "../../Home/Home-styles";
|
||||
import { ForumData, ForumModal } from "../ForumModal";
|
||||
|
||||
export const forums = signal<ForumData[]>([]);
|
||||
export const resetForumIndexes = (forums: Signal<ForumData[]>) => {
|
||||
forums.value = forums.value.map((forum, index) => {
|
||||
forum.listIndex = index;
|
||||
return forum;
|
||||
});
|
||||
};
|
||||
|
||||
export const ActionBar = () => {
|
||||
useSignals();
|
||||
|
||||
useEffect(() => {
|
||||
fetchForumData().then(data => {
|
||||
if (data)
|
||||
forums.value = data.map((forum, index) => {
|
||||
forum.listIndex = index;
|
||||
return forum;
|
||||
});
|
||||
// console.log("forums are : ", forums.value);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const navigate = useNavigate();
|
||||
return (
|
||||
<InstanceContainer>
|
||||
<ForumModal forumData={forums} />
|
||||
<ComposeContainer
|
||||
sx={{ width: "200px", marginLeft: "auto" }}
|
||||
onClick={() => navigate("/sponsorshipData")}
|
||||
>
|
||||
<MenuBookIcon />
|
||||
<ComposeP sx={{ fontSize: "70%" }}>{"Sponsorship Data"}</ComposeP>
|
||||
</ComposeContainer>
|
||||
</InstanceContainer>
|
||||
);
|
||||
};
|
121
src/pages/Forum/Components/ModalButton.tsx
Normal file
121
src/pages/Forum/Components/ModalButton.tsx
Normal file
@ -0,0 +1,121 @@
|
||||
import { Box } from "@mui/material";
|
||||
import { useSignal } from "@preact/signals-react";
|
||||
import React, { PropsWithChildren } from "react";
|
||||
import ComposeIconSVG from "../../../assets/svgs/ComposeIcon.svg";
|
||||
import { CreateThreadIcon } from "../../../assets/svgs/CreateThreadIcon";
|
||||
import ModalCloseSVG from "../../../assets/svgs/ModalClose.svg";
|
||||
import { ReusableModal } from "../../../components/modals/ReusableModal";
|
||||
import {
|
||||
CloseContainer,
|
||||
ComposeContainer,
|
||||
ComposeIcon,
|
||||
ComposeP,
|
||||
InstanceFooter,
|
||||
InstanceListContainer,
|
||||
InstanceListHeader,
|
||||
NewMessageCloseImg,
|
||||
NewMessageHeaderP,
|
||||
NewMessageSendButton,
|
||||
NewMessageSendP,
|
||||
} from "../../Home/Home-styles";
|
||||
import { publishForum } from "../ForumModal-Data";
|
||||
|
||||
export interface ModalButtonProps {
|
||||
onSubmit: () => Promise<boolean>;
|
||||
onClose?: () => void;
|
||||
isRenderModal: boolean;
|
||||
modalLabel: string;
|
||||
buttonLabel: string;
|
||||
}
|
||||
export const ModalButton = ({
|
||||
onClose,
|
||||
onSubmit,
|
||||
isRenderModal,
|
||||
modalLabel,
|
||||
buttonLabel,
|
||||
children,
|
||||
}: PropsWithChildren<ModalButtonProps>) => {
|
||||
const isOpen = useSignal<boolean>(false);
|
||||
const closeModal = () => {
|
||||
isOpen.value = false;
|
||||
if (onClose) onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
}}
|
||||
>
|
||||
{isRenderModal && (
|
||||
<ComposeContainer onClick={e => (isOpen.value = true)}>
|
||||
<ComposeIcon src={ComposeIconSVG} />
|
||||
<ComposeP sx={{ fontSize: "70%" }}>{modalLabel}</ComposeP>
|
||||
</ComposeContainer>
|
||||
)}
|
||||
<ReusableModal
|
||||
open={isOpen.value}
|
||||
customStyles={{
|
||||
maxHeight: "95vh",
|
||||
maxWidth: "950px",
|
||||
height: "700px",
|
||||
borderRadius: "12px 12px 0px 0px",
|
||||
background: "var(--Mail-Background, #313338)",
|
||||
padding: "0px",
|
||||
gap: "0px",
|
||||
}}
|
||||
>
|
||||
<InstanceListHeader
|
||||
sx={{
|
||||
backgroundColor: "unset",
|
||||
height: "50px",
|
||||
padding: "20px 42px",
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<NewMessageHeaderP>{modalLabel}</NewMessageHeaderP>
|
||||
<CloseContainer onClick={closeModal}>
|
||||
<NewMessageCloseImg src={ModalCloseSVG} />
|
||||
</CloseContainer>
|
||||
</InstanceListHeader>
|
||||
<InstanceListContainer
|
||||
sx={{
|
||||
backgroundColor: "rgba(217, 217, 217, 1)",
|
||||
padding: "20px 42px",
|
||||
height: "calc(100% - 150px)",
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</InstanceListContainer>
|
||||
<InstanceFooter
|
||||
sx={{
|
||||
backgroundColor: "rgba(217, 217, 217, 1)",
|
||||
padding: "20px 42px",
|
||||
alignItems: "center",
|
||||
height: "90px",
|
||||
}}
|
||||
>
|
||||
<NewMessageSendButton
|
||||
onClick={() => {
|
||||
onSubmit().then(success => {
|
||||
if (success) closeModal();
|
||||
});
|
||||
}}
|
||||
>
|
||||
<NewMessageSendP>{buttonLabel}</NewMessageSendP>
|
||||
|
||||
<CreateThreadIcon
|
||||
color="red"
|
||||
opacity={1}
|
||||
height="25px"
|
||||
width="25px"
|
||||
/>
|
||||
</NewMessageSendButton>
|
||||
</InstanceFooter>
|
||||
</ReusableModal>
|
||||
</Box>
|
||||
);
|
||||
};
|
@ -1,27 +1,33 @@
|
||||
import { Input } from "@mui/material";
|
||||
import { SxProps } from "@mui/system";
|
||||
import { Signal, useSignalEffect } from "@preact/signals-react";
|
||||
import { Signal, useSignal, useSignalEffect } from "@preact/signals-react";
|
||||
import { useSignals } from "@preact/signals-react/runtime";
|
||||
import React from "react";
|
||||
import React, { useEffect } from "react";
|
||||
|
||||
interface QmailTextFieldProps {
|
||||
value: Signal<string>;
|
||||
label: string;
|
||||
sx?: SxProps;
|
||||
filter?: RegExp;
|
||||
maxLength?: number;
|
||||
initialValue?: string;
|
||||
afterChange?: (s: string) => void;
|
||||
}
|
||||
export const QmailTextField = ({
|
||||
value,
|
||||
label,
|
||||
sx = {},
|
||||
filter,
|
||||
maxLength = 60,
|
||||
initialValue = "",
|
||||
afterChange,
|
||||
}: QmailTextFieldProps) => {
|
||||
useSignals();
|
||||
const value = useSignal<string>(initialValue);
|
||||
useSignalEffect(() => {
|
||||
if (filter) value.value = value.value.replace(filter, "");
|
||||
});
|
||||
useEffect(() => {
|
||||
value.value = initialValue;
|
||||
}, [initialValue]);
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -29,7 +35,9 @@ export const QmailTextField = ({
|
||||
id="standard-adornment-name"
|
||||
value={value.value}
|
||||
onChange={e => {
|
||||
if (e.target.value.length <= maxLength) value.value = e.target.value;
|
||||
const eventValue = e.target.value;
|
||||
if (eventValue.length <= maxLength) value.value = eventValue;
|
||||
if (afterChange) afterChange(value.value);
|
||||
}}
|
||||
placeholder={label}
|
||||
disableUnderline
|
@ -21,13 +21,13 @@ export const Forum = ({
|
||||
}: ForumData) => {
|
||||
const [currentThread, setCurrentThread] = useState<any>(null);
|
||||
|
||||
const openNewThread = () => {
|
||||
if (currentThread) {
|
||||
executeEvent("openNewThreadMessageModal", {});
|
||||
return;
|
||||
}
|
||||
executeEvent("openNewThreadModal", {});
|
||||
};
|
||||
// const openNewThread = () => {
|
||||
// if (currentThread) {
|
||||
// executeEvent("openNewThreadMessageModal", {});
|
||||
// return;
|
||||
// }
|
||||
// executeEvent("openNewThreadModal", {});
|
||||
// };
|
||||
|
||||
const forumWidth = 95;
|
||||
const forumMarginLeft = (100 - forumWidth) / 2;
|
||||
@ -51,8 +51,8 @@ export const Forum = ({
|
||||
}}
|
||||
>
|
||||
<span>{title}</span>
|
||||
<Box>{descriptionText.trim()}</Box>
|
||||
</Box>
|
||||
<Box>{descriptionText.trim()}</Box>
|
||||
</Box>
|
||||
|
||||
// <Box>
|
||||
|
@ -1,30 +1,18 @@
|
||||
import { ReadonlySignal } from "@preact/signals-react";
|
||||
import ShortUniqueId from "short-unique-id";
|
||||
import { forums } from "../../App";
|
||||
import {
|
||||
ATTATCHMENT_BASE,
|
||||
FORUMS_ID,
|
||||
THREAD_BASE,
|
||||
} from "../../constants/Identifiers";
|
||||
import {
|
||||
MAIL_ATTACHMENT_SERVICE_TYPE,
|
||||
MAIL_SERVICE_TYPE,
|
||||
THREAD_SERVICE_TYPE,
|
||||
} from "../../constants/mail";
|
||||
import { appOwner } from "../../constants/Misc";
|
||||
import { FORUMS_ID } from "../../constants/Identifiers";
|
||||
import { setNotification } from "../../state/features/notificationsSlice";
|
||||
import { store } from "../../state/store";
|
||||
import { getGroup } from "../../utils/QortalRequests";
|
||||
import { objectToBase64, toBase64 } from "../../utils/toBase64";
|
||||
import { objectToBase64 } from "../../utils/toBase64";
|
||||
import { ForumData } from "./ForumModal";
|
||||
import { Group } from "./GroupPermissionsForm";
|
||||
import { descriptionMaxLength, ForumData } from "./ForumModal";
|
||||
|
||||
const getGroupNames = async (groups: Group[]) => {
|
||||
const groupPromises = groups.map(group => getGroup(group.id.value));
|
||||
const getGroupsData = async (groups: Group[]) => {
|
||||
const groupPromises = groups.map(group => getGroup(group.id));
|
||||
return await Promise.all(groupPromises);
|
||||
};
|
||||
|
||||
const verifyData = async (formData: ForumData) => {
|
||||
debugger;
|
||||
const userName = store.getState()?.auth?.user?.name;
|
||||
const { title, encryption, groups, descriptionHTML, descriptionText } =
|
||||
formData;
|
||||
@ -38,51 +26,33 @@ const verifyData = async (formData: ForumData) => {
|
||||
if (groups.filter(group => !!group.permissions).length < groups.length)
|
||||
errorMsg = "A group has empty permissions";
|
||||
|
||||
const groupsWithNames = await getGroupNames(groups);
|
||||
const groupsWithNames = await getGroupsData(groups);
|
||||
|
||||
if (
|
||||
groupsWithNames.filter(group => !!group?.groupName).length < groups.length
|
||||
)
|
||||
errorMsg = "A group ID provided doesn't exist";
|
||||
|
||||
if (errorMsg) {
|
||||
store.dispatch(
|
||||
setNotification({
|
||||
msg: errorMsg,
|
||||
alertType: "error",
|
||||
})
|
||||
);
|
||||
}
|
||||
return errorMsg;
|
||||
};
|
||||
|
||||
export const addForum = async (formData: ForumData) => {
|
||||
const errorMsg = await verifyData(formData);
|
||||
if (errorMsg) return;
|
||||
forums.value = [...forums.value, formData];
|
||||
const verifyAllData = async (formData: ForumData[]) => {
|
||||
const errorListAll = await Promise.all(
|
||||
formData.map(data => verifyData(data))
|
||||
);
|
||||
// don't loop through all errors, take the first one and use that.
|
||||
const firstErrorIndex = errorListAll.findIndex(data => !!data);
|
||||
if (firstErrorIndex === -1) return "";
|
||||
return `Forum ${firstErrorIndex + 1} has error: ${
|
||||
errorListAll[firstErrorIndex]
|
||||
}`;
|
||||
};
|
||||
|
||||
export const editForums = async (newForums: ForumData[]) => {
|
||||
const errorMsgPromises = newForums.map(forumData => verifyData(forumData));
|
||||
const errorMsgs = await Promise.all(errorMsgPromises);
|
||||
const errorMsgNum = errorMsgs.filter(msg => !!msg).length;
|
||||
|
||||
if (errorMsgNum > 0) return;
|
||||
|
||||
forums.value = newForums;
|
||||
};
|
||||
|
||||
export const publishForum = async (formData: ForumData) => {
|
||||
let success = false;
|
||||
const errorMsg = await verifyData(formData);
|
||||
if (errorMsg) return success;
|
||||
export const publishForum = async (formData: ForumData[]): Promise<boolean> => {
|
||||
try {
|
||||
await addForum(formData);
|
||||
|
||||
const publishDescription =
|
||||
formData.title +
|
||||
"_" +
|
||||
formData.descriptionText.substring(0, descriptionMaxLength);
|
||||
console.log("formData is: ", formData);
|
||||
const errorMsg = await verifyAllData(formData);
|
||||
if (errorMsg) throw new Error(errorMsg);
|
||||
|
||||
const userName = store.getState()?.auth?.user?.name;
|
||||
await qortalRequest({
|
||||
@ -90,40 +60,51 @@ export const publishForum = async (formData: ForumData) => {
|
||||
name: userName,
|
||||
service: "METADATA",
|
||||
identifier: FORUMS_ID,
|
||||
description: publishDescription,
|
||||
data64: await objectToBase64(forums),
|
||||
data64: await objectToBase64(formData),
|
||||
});
|
||||
success = true;
|
||||
store.dispatch(
|
||||
setNotification({
|
||||
msg: "Forum published",
|
||||
alertType: "success",
|
||||
})
|
||||
);
|
||||
return true;
|
||||
} catch (error: any) {
|
||||
let notificationObj = null;
|
||||
const defaultErrorMessage = "Failed to submit forum data";
|
||||
|
||||
if (typeof error === "string") {
|
||||
notificationObj = {
|
||||
msg: error || defaultErrorMessage,
|
||||
alertType: "error",
|
||||
};
|
||||
} else if (typeof error?.error === "string") {
|
||||
notificationObj = {
|
||||
msg: error?.error || defaultErrorMessage,
|
||||
alertType: "error",
|
||||
};
|
||||
} else {
|
||||
notificationObj = {
|
||||
msg: error?.message || defaultErrorMessage,
|
||||
alertType: "error",
|
||||
};
|
||||
}
|
||||
if (!notificationObj) return;
|
||||
store.dispatch(setNotification(notificationObj));
|
||||
|
||||
throw new Error(defaultErrorMessage);
|
||||
console.log("error is: ", error);
|
||||
formErrorHandler(error);
|
||||
// throw new Error(defaultErrorMessage);
|
||||
return false;
|
||||
}
|
||||
return success;
|
||||
};
|
||||
|
||||
const formErrorHandler = (error: any) => {
|
||||
let notificationObj = null;
|
||||
const defaultErrorMessage = "Failed to submit forum data";
|
||||
|
||||
if (typeof error === "string") {
|
||||
notificationObj = {
|
||||
msg: error || defaultErrorMessage,
|
||||
alertType: "error",
|
||||
};
|
||||
} else if (typeof error?.error === "string") {
|
||||
notificationObj = {
|
||||
msg: error?.error || defaultErrorMessage,
|
||||
alertType: "error",
|
||||
};
|
||||
} else {
|
||||
notificationObj = {
|
||||
msg: error?.message || defaultErrorMessage,
|
||||
alertType: "error",
|
||||
};
|
||||
}
|
||||
if (notificationObj) store.dispatch(setNotification(notificationObj));
|
||||
};
|
||||
|
||||
export const forumToString = (forum: ForumData) =>
|
||||
`${forum?.listIndex !== undefined ? forum.listIndex + 1 : "?"} - ${
|
||||
forum?.title
|
||||
}`;
|
||||
|
||||
export const deepCopyArray = (array: object[]) => {
|
||||
return JSON.parse(JSON.stringify(array));
|
||||
};
|
||||
|
@ -1,45 +1,25 @@
|
||||
import MenuBookIcon from "@mui/icons-material/MenuBook";
|
||||
import { Box } from "@mui/material";
|
||||
import { Box, Button } from "@mui/material";
|
||||
import {
|
||||
ReadonlySignal,
|
||||
Signal,
|
||||
signal,
|
||||
useComputed,
|
||||
useSignal,
|
||||
useSignalEffect,
|
||||
} from "@preact/signals-react";
|
||||
import { useSignals } from "@preact/signals-react/runtime";
|
||||
import React from "react";
|
||||
import React, { useEffect } from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import ComposeIconSVG from "../../assets/svgs/ComposeIcon.svg";
|
||||
|
||||
import { CreateThreadIcon } from "../../assets/svgs/CreateThreadIcon";
|
||||
import ModalCloseSVG from "../../assets/svgs/ModalClose.svg";
|
||||
import { SelectField } from "../../components/common/SelectField";
|
||||
import { Spacer } from "../../components/common/Spacer";
|
||||
import { TextEditor } from "../../components/common/TextEditor/TextEditor";
|
||||
import { ReusableModal } from "../../components/modals/ReusableModal";
|
||||
import { useTestIdentifiers } from "../../constants/Identifiers";
|
||||
import { appOwner } from "../../constants/Misc";
|
||||
import { RootState } from "../../state/store";
|
||||
import {
|
||||
CloseContainer,
|
||||
ComposeContainer,
|
||||
ComposeIcon,
|
||||
ComposeP,
|
||||
InstanceContainer,
|
||||
InstanceFooter,
|
||||
InstanceListContainer,
|
||||
InstanceListHeader,
|
||||
NewMessageCloseImg,
|
||||
NewMessageHeaderP,
|
||||
NewMessageInputRow,
|
||||
NewMessageSendButton,
|
||||
NewMessageSendP,
|
||||
} from "../Home/Home-styles";
|
||||
import { NewMessageInputRow } from "../Home/Home-styles";
|
||||
import { resetForumIndexes } from "./Components/ActionBar";
|
||||
import { ModalButton } from "./Components/ModalButton";
|
||||
import { QmailTextField } from "./Components/QmailTextField";
|
||||
import { deepCopyArray, forumToString, publishForum } from "./ForumModal-Data";
|
||||
import { Group, GroupPermissionsForm } from "./GroupPermissionsForm";
|
||||
import { publishForum } from "./ForumModal-Data";
|
||||
import { QmailTextField } from "./QmailTextField";
|
||||
|
||||
export type EncryptionType = "None" | "Group" | "GroupAdmin" | "";
|
||||
export interface ForumData {
|
||||
@ -48,6 +28,7 @@ export interface ForumData {
|
||||
groups: Group[];
|
||||
descriptionHTML: string;
|
||||
descriptionText: string;
|
||||
listIndex: number;
|
||||
}
|
||||
|
||||
export type GroupPermissionType = "Read" | "Write";
|
||||
@ -60,159 +41,181 @@ export const titleMaxLength = 60;
|
||||
export const descriptionMaxLength = 160;
|
||||
|
||||
interface NewForumModalProps {
|
||||
forumData?: ForumData[];
|
||||
forumData: Signal<ForumData[]>;
|
||||
}
|
||||
|
||||
export const ForumModal = ({ forumData }: NewForumModalProps) => {
|
||||
useSignals();
|
||||
const isOpen = useSignal<boolean>(false);
|
||||
|
||||
const forumTitle = useSignal<string>("");
|
||||
const descriptionHTML = useSignal<string>("");
|
||||
const descriptionText = useSignal<string>("");
|
||||
const groups = useSignal<Group[]>([
|
||||
{
|
||||
id: signal<string>(""),
|
||||
permissions: signal<GroupPermissionType>("Read"),
|
||||
},
|
||||
]);
|
||||
const tempData = useSignal<ForumData[]>([]);
|
||||
|
||||
const selectedEncryptionType = useSignal<EncryptionType | "">("None");
|
||||
|
||||
const { user } = useSelector((state: RootState) => state.auth);
|
||||
|
||||
const formData: ReadonlySignal<ForumData> = useComputed(() => {
|
||||
return {
|
||||
title: forumTitle.value,
|
||||
encryption: selectedEncryptionType.value,
|
||||
groups: groups.value,
|
||||
descriptionHTML: descriptionHTML.value,
|
||||
descriptionText: descriptionText.value,
|
||||
};
|
||||
});
|
||||
|
||||
const closeModal = () => {
|
||||
isOpen.value = false;
|
||||
const emptyForum: ForumData = {
|
||||
title: "",
|
||||
encryption: "None",
|
||||
groups: [],
|
||||
descriptionHTML: "",
|
||||
descriptionText: "",
|
||||
listIndex: 0,
|
||||
};
|
||||
|
||||
const { user } = useSelector((state: RootState) => state.auth);
|
||||
const isRenderModal = user?.name === appOwner || useTestIdentifiers;
|
||||
|
||||
const selectedForumIndex = useSignal<number>(0);
|
||||
const selectedForum = useComputed<ForumData>(() => {
|
||||
return tempData.value[selectedForumIndex.value];
|
||||
});
|
||||
|
||||
const initializeTempData = () => {
|
||||
tempData.value =
|
||||
forumData.value.length === 0
|
||||
? [emptyForum]
|
||||
: deepCopyArray(forumData.value);
|
||||
selectedForumIndex.value = 0;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
initializeTempData();
|
||||
}, [forumData.value]);
|
||||
|
||||
const addForum = () => {
|
||||
const newForum = { ...emptyForum };
|
||||
newForum.listIndex = tempData.value.length;
|
||||
tempData.value = [...tempData.value, newForum];
|
||||
selectedForumIndex.value = newForum.listIndex;
|
||||
};
|
||||
|
||||
const removeForum = () => {
|
||||
if (tempData.value.length <= 1) {
|
||||
tempData.value = [{ ...emptyForum }];
|
||||
selectedForumIndex.value = 0;
|
||||
} else {
|
||||
tempData.value = tempData.value.filter(
|
||||
(group, index) => index !== selectedForumIndex.value
|
||||
);
|
||||
|
||||
if (selectedForumIndex.value > 0) selectedForumIndex.value -= 1;
|
||||
else selectedForumIndex.value = 0;
|
||||
}
|
||||
resetForumIndexes(tempData);
|
||||
};
|
||||
|
||||
const updateForums = () => {
|
||||
tempData.value = [...tempData.value];
|
||||
};
|
||||
|
||||
const forumText = !!forumData ? "Edit Forums" : "Add Forum";
|
||||
const publishText = !!forumData ? "Edit Forums" : "Create Forum";
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
<ModalButton
|
||||
isRenderModal={isRenderModal}
|
||||
onClose={() => initializeTempData()}
|
||||
onSubmit={async () => {
|
||||
updateForums();
|
||||
const pub = await publishForum(tempData.value);
|
||||
|
||||
if (pub) forumData.value = tempData.value;
|
||||
return pub;
|
||||
}}
|
||||
modalLabel={"Manage Forums"}
|
||||
buttonLabel={"Publish Forums"}
|
||||
>
|
||||
{(user?.name === appOwner || useTestIdentifiers) && (
|
||||
<ComposeContainer onClick={e => (isOpen.value = true)}>
|
||||
<ComposeIcon src={ComposeIconSVG} />
|
||||
<ComposeP sx={{ fontSize: "70%" }}>{forumText}</ComposeP>
|
||||
</ComposeContainer>
|
||||
)}
|
||||
<ReusableModal
|
||||
open={isOpen.value}
|
||||
customStyles={{
|
||||
maxHeight: "95vh",
|
||||
maxWidth: "950px",
|
||||
height: "700px",
|
||||
borderRadius: "12px 12px 0px 0px",
|
||||
background: "var(--Mail-Background, #313338)",
|
||||
padding: "0px",
|
||||
gap: "0px",
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
width: "100%",
|
||||
alignItems: "center",
|
||||
gap: "10px",
|
||||
marginBottom: "10px",
|
||||
}}
|
||||
>
|
||||
<InstanceListHeader
|
||||
sx={{
|
||||
backgroundColor: "unset",
|
||||
height: "50px",
|
||||
padding: "20px 42px",
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
<SelectField
|
||||
options={tempData.value.map((forum, index) => forumToString(forum))}
|
||||
label={"Forum #"}
|
||||
initialValue={forumToString(selectedForum.value)}
|
||||
afterChange={s => {
|
||||
//tempData.value[selectedForumIndex.value] = selectedForum.value;
|
||||
tempData.value = [...tempData.value];
|
||||
selectedForumIndex.value = +s.split(" ")[0] - 1;
|
||||
}}
|
||||
>
|
||||
<NewMessageHeaderP>{forumText}</NewMessageHeaderP>
|
||||
<CloseContainer onClick={closeModal}>
|
||||
<NewMessageCloseImg src={ModalCloseSVG} />
|
||||
</CloseContainer>
|
||||
</InstanceListHeader>
|
||||
<InstanceListContainer
|
||||
sx={{
|
||||
backgroundColor: "rgba(217, 217, 217, 1)",
|
||||
padding: "20px 42px",
|
||||
height: "calc(100% - 150px)",
|
||||
flexShrink: 0,
|
||||
"& .MuiSvgIcon-root": {
|
||||
color: "black",
|
||||
},
|
||||
}}
|
||||
width={"50%"}
|
||||
/>
|
||||
<Button
|
||||
variant={"contained"}
|
||||
sx={{ width: "25%", height: "35px", color: "white" }}
|
||||
color={"success"}
|
||||
onClick={addForum}
|
||||
>
|
||||
<NewMessageInputRow sx={{ height: "80px", alignItems: "end" }}>
|
||||
<QmailTextField
|
||||
value={forumTitle}
|
||||
label={"Forum Title"}
|
||||
sx={{
|
||||
height: "60px",
|
||||
borderBottom: "1px solid gray",
|
||||
}}
|
||||
maxLength={titleMaxLength}
|
||||
/>
|
||||
<SelectField
|
||||
options={["None", "Group", "GroupAdmin"]}
|
||||
label={"Encryption Type"}
|
||||
value={selectedEncryptionType}
|
||||
sx={{
|
||||
"& .MuiSvgIcon-root": {
|
||||
color: "gray",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</NewMessageInputRow>
|
||||
<GroupPermissionsForm groups={groups} />
|
||||
<Spacer height="40px" />
|
||||
<Box
|
||||
sx={{
|
||||
maxHeight: "40vh",
|
||||
}}
|
||||
>
|
||||
<p style={{ color: "black" }}> Description </p>
|
||||
<TextEditor
|
||||
inlineContent={descriptionHTML.value}
|
||||
setInlineContent={(
|
||||
value: any,
|
||||
delta: any,
|
||||
source: any,
|
||||
editor: any
|
||||
) => {
|
||||
descriptionHTML.value = value;
|
||||
descriptionText.value = editor.getText(value);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</InstanceListContainer>
|
||||
<InstanceFooter
|
||||
Add Forum
|
||||
</Button>
|
||||
<Button
|
||||
variant={"contained"}
|
||||
sx={{ width: "25%", height: "35px", color: "white" }}
|
||||
color={"error"}
|
||||
onClick={removeForum}
|
||||
>
|
||||
Remove Selected Forum
|
||||
</Button>
|
||||
</Box>
|
||||
<NewMessageInputRow sx={{ height: "80px", alignItems: "end" }}>
|
||||
<QmailTextField
|
||||
initialValue={selectedForum.value?.title}
|
||||
afterChange={s => {
|
||||
tempData.value[selectedForumIndex.value].title = s;
|
||||
updateForums();
|
||||
}}
|
||||
label={"Forum Title"}
|
||||
sx={{
|
||||
backgroundColor: "rgba(217, 217, 217, 1)",
|
||||
padding: "20px 42px",
|
||||
alignItems: "center",
|
||||
height: "90px",
|
||||
height: "60px",
|
||||
borderBottom: "1px solid gray",
|
||||
}}
|
||||
>
|
||||
<NewMessageSendButton
|
||||
onClick={() => {
|
||||
publishForum(formData.value).then(success => {
|
||||
if (success) closeModal();
|
||||
});
|
||||
}}
|
||||
>
|
||||
<NewMessageSendP>{publishText}</NewMessageSendP>
|
||||
|
||||
<CreateThreadIcon
|
||||
color="red"
|
||||
opacity={1}
|
||||
height="25px"
|
||||
width="25px"
|
||||
/>
|
||||
</NewMessageSendButton>
|
||||
</InstanceFooter>
|
||||
</ReusableModal>
|
||||
</Box>
|
||||
maxLength={titleMaxLength}
|
||||
/>
|
||||
<SelectField
|
||||
options={["None", "Group", "GroupAdmin"]}
|
||||
label={"Encryption Type"}
|
||||
initialValue={selectedForum.value?.encryption}
|
||||
afterChange={s =>
|
||||
(selectedForum.value.encryption = s as EncryptionType)
|
||||
}
|
||||
sx={{
|
||||
"& .MuiSvgIcon-root": {
|
||||
color: "gray",
|
||||
height: "60px",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</NewMessageInputRow>
|
||||
<GroupPermissionsForm
|
||||
initialGroups={selectedForum.value?.groups}
|
||||
afterChange={g => {
|
||||
tempData.value[selectedForumIndex.value].groups = g;
|
||||
//updateForums();
|
||||
}}
|
||||
/>
|
||||
<Spacer height="40px" />
|
||||
<Box
|
||||
sx={{
|
||||
maxHeight: "40vh",
|
||||
}}
|
||||
>
|
||||
<p style={{ color: "black" }}> Description </p>
|
||||
<TextEditor
|
||||
inlineContent={selectedForum.value?.descriptionHTML}
|
||||
setInlineContent={(
|
||||
value: any,
|
||||
delta: any,
|
||||
source: any,
|
||||
editor: any
|
||||
) => {
|
||||
selectedForum.value.descriptionHTML = value;
|
||||
selectedForum.value.descriptionText = editor.getText(value);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</ModalButton>
|
||||
);
|
||||
};
|
||||
|
@ -1,3 +1,27 @@
|
||||
export const ForumThreads = () => {
|
||||
return <></>;
|
||||
import { useSignal } from "@preact/signals-react";
|
||||
import { useSignals } from "@preact/signals-react/runtime";
|
||||
import { useState } from "react";
|
||||
import { GroupData } from "../../utils/QortalRequests";
|
||||
import { GroupMail } from "../Mail/GroupMail";
|
||||
import { Thread } from "../Mail/Thread";
|
||||
|
||||
export interface ForumThreadsProps {
|
||||
id: number;
|
||||
}
|
||||
export const ForumThreads = ({ id }: ForumThreadsProps) => {
|
||||
useSignals();
|
||||
const [currentThread, setCurrentThread] = useState<any>(null);
|
||||
const [filterMode, setFilterMode] = useState<string>("Recently active");
|
||||
|
||||
const groupInfo = useSignal<GroupData | undefined>(undefined);
|
||||
|
||||
return (
|
||||
<GroupMail
|
||||
groupInfo={groupInfo.value}
|
||||
currentThread={currentThread}
|
||||
setCurrentThread={setCurrentThread}
|
||||
setFilterMode={setFilterMode}
|
||||
filterMode={filterMode}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -1,41 +1,46 @@
|
||||
import AddIcon from "@mui/icons-material/Add";
|
||||
import RemoveIcon from "@mui/icons-material/Remove";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
IconButton,
|
||||
MenuItem,
|
||||
OutlinedInput,
|
||||
Select,
|
||||
SelectChangeEvent,
|
||||
} from "@mui/material";
|
||||
import { Box, IconButton } from "@mui/material";
|
||||
import { styled } from "@mui/material/styles";
|
||||
import { signal, Signal } from "@preact/signals-react";
|
||||
import { Signal, useSignal } from "@preact/signals-react";
|
||||
import { useSignals } from "@preact/signals-react/runtime";
|
||||
import React, { useEffect } from "react";
|
||||
import ShortUniqueId from "short-unique-id";
|
||||
import { SelectField } from "../../components/common/SelectField";
|
||||
import { NewMessageInputRow } from "../Home/Home-styles";
|
||||
import { QmailTextField } from "./Components/QmailTextField";
|
||||
import { GroupPermissionType } from "./ForumModal";
|
||||
import { QmailTextField } from "./QmailTextField";
|
||||
|
||||
export interface Group {
|
||||
id: Signal<string>;
|
||||
permissions: Signal<GroupPermissionType>;
|
||||
id: string;
|
||||
permissions: GroupPermissionType;
|
||||
}
|
||||
export interface GroupPermissionsFormProps {
|
||||
groups: Signal<Group[]>;
|
||||
initialGroups?: Group[];
|
||||
afterChange?: (g: Group[]) => void;
|
||||
}
|
||||
export const GroupPermissionsForm = ({ groups }: GroupPermissionsFormProps) => {
|
||||
export const GroupPermissionsForm = ({
|
||||
initialGroups,
|
||||
afterChange,
|
||||
}: GroupPermissionsFormProps) => {
|
||||
useSignals();
|
||||
|
||||
const uid = new ShortUniqueId();
|
||||
const newGroup = {
|
||||
id: signal<string>(""),
|
||||
permissions: signal<GroupPermissionType>("Read"),
|
||||
id: "",
|
||||
permissions: "Read",
|
||||
} as Group;
|
||||
const groups = useSignal<Group[]>(initialGroups || [newGroup]);
|
||||
|
||||
useEffect(() => {
|
||||
if (initialGroups)
|
||||
groups.value = initialGroups?.length > 0 ? initialGroups : [newGroup];
|
||||
}, [initialGroups]);
|
||||
|
||||
const uid = new ShortUniqueId();
|
||||
|
||||
const addGroup = () => {
|
||||
groups.value = [...groups.value, newGroup];
|
||||
if (afterChange) afterChange(groups.value);
|
||||
};
|
||||
|
||||
const removeGroup = (groupIndex: number) => {
|
||||
@ -44,8 +49,12 @@ export const GroupPermissionsForm = ({ groups }: GroupPermissionsFormProps) => {
|
||||
(group, index) => index !== groupIndex
|
||||
);
|
||||
else groups.value = [newGroup];
|
||||
if (afterChange) afterChange(groups.value);
|
||||
};
|
||||
|
||||
const updateGroups = () => {
|
||||
if (afterChange) afterChange(groups.value);
|
||||
};
|
||||
const buttonStyle = {
|
||||
width: "70px",
|
||||
height: "70px",
|
||||
@ -99,7 +108,11 @@ export const GroupPermissionsForm = ({ groups }: GroupPermissionsFormProps) => {
|
||||
<RemoveIcon sx={{ ...iconStyle, fontSize: 50 }} />
|
||||
</StyledIconButton>
|
||||
<QmailTextField
|
||||
value={group.id}
|
||||
afterChange={s => {
|
||||
group.id = s;
|
||||
updateGroups();
|
||||
}}
|
||||
initialValue={group.id}
|
||||
label={"Group ID"}
|
||||
filter={/[^0-9]/}
|
||||
sx={{
|
||||
@ -110,10 +123,13 @@ export const GroupPermissionsForm = ({ groups }: GroupPermissionsFormProps) => {
|
||||
maxLength={10}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<SelectField
|
||||
options={options}
|
||||
value={group.permissions}
|
||||
initialValue={group.permissions}
|
||||
afterChange={s => {
|
||||
group.permissions = s as GroupPermissionType;
|
||||
updateGroups();
|
||||
}}
|
||||
label={"Group Permissions"}
|
||||
sx={{
|
||||
"& .MuiSvgIcon-root": {
|
||||
|
@ -84,7 +84,7 @@ export const MailBodyInnerScroll = styled(Box)`
|
||||
|
||||
export const ComposeContainer = styled(Box)(({ theme }) => ({
|
||||
display: "flex",
|
||||
width: "150px",
|
||||
width: "100%",
|
||||
alignItems: "center",
|
||||
gap: "7px",
|
||||
height: "100%",
|
||||
|
@ -1,8 +1,7 @@
|
||||
import { Box } from "@mui/material";
|
||||
import { useSignals } from "@preact/signals-react/runtime";
|
||||
import React from "react";
|
||||
import { forums } from "../../App";
|
||||
import { ActionBar } from "../Forum/ActionBar";
|
||||
import { ActionBar, forums } from "../Forum/Components/ActionBar";
|
||||
import { Forum } from "../Forum/Forum";
|
||||
|
||||
export const Home = () => {
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { FORUMS_ID } from "../constants/Identifiers";
|
||||
import { appOwner } from "../constants/Misc";
|
||||
import { ForumData } from "../pages/Forum/ForumModal";
|
||||
|
||||
export interface GroupData {
|
||||
groupId: number;
|
||||
@ -19,6 +20,7 @@ export const listGroups = async () => {
|
||||
};
|
||||
|
||||
export const getGroup = async (groupID: number | string) => {
|
||||
if (!groupID) return undefined;
|
||||
const url = `/groups/${groupID.toString()}`;
|
||||
|
||||
try {
|
||||
@ -36,12 +38,12 @@ export const getGroup = async (groupID: number | string) => {
|
||||
|
||||
export const fetchForumData = async () => {
|
||||
try {
|
||||
return await qortalRequest({
|
||||
return (await qortalRequest({
|
||||
action: "FETCH_QDN_RESOURCE",
|
||||
name: appOwner,
|
||||
service: "METADATA",
|
||||
identifier: FORUMS_ID,
|
||||
});
|
||||
})) as ForumData[];
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return undefined;
|
||||
|
Loading…
x
Reference in New Issue
Block a user