Publish Forum button changed to Manage Forums. It handles both publishing and editing forums.

This commit is contained in:
Qortal Dev 2024-10-02 09:45:23 -06:00
parent 0659433f03
commit dd500a3ebb
14 changed files with 512 additions and 335 deletions

View File

@ -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>

View File

@ -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 }}
>

View File

@ -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>
);
};

View 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>
);
};

View 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>
);
};

View File

@ -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

View File

@ -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>

View File

@ -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,17 +60,24 @@ 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) {
console.log("error is: ", error);
formErrorHandler(error);
// throw new Error(defaultErrorMessage);
return false;
}
};
const formErrorHandler = (error: any) => {
let notificationObj = null;
const defaultErrorMessage = "Failed to submit forum data";
@ -120,10 +97,14 @@ export const publishForum = async (formData: ForumData) => {
alertType: "error",
};
}
if (!notificationObj) return;
store.dispatch(setNotification(notificationObj));
throw new Error(defaultErrorMessage);
}
return success;
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));
};

View File

@ -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,93 +41,132 @@ 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 emptyForum: ForumData = {
title: "",
encryption: "None",
groups: [],
descriptionHTML: "",
descriptionText: "",
listIndex: 0,
};
const { user } = useSelector((state: RootState) => state.auth);
const isRenderModal = user?.name === appOwner || useTestIdentifiers;
const formData: ReadonlySignal<ForumData> = useComputed(() => {
return {
title: forumTitle.value,
encryption: selectedEncryptionType.value,
groups: groups.value,
descriptionHTML: descriptionHTML.value,
descriptionText: descriptionText.value,
};
const selectedForumIndex = useSignal<number>(0);
const selectedForum = useComputed<ForumData>(() => {
return tempData.value[selectedForumIndex.value];
});
const closeModal = () => {
isOpen.value = false;
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 (
<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"}
>
<Box
sx={{
display: "flex",
}}
>
{(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",
}}
>
<InstanceListHeader
sx={{
backgroundColor: "unset",
height: "50px",
padding: "20px 42px",
flexDirection: "row",
justifyContent: "space-between",
width: "100%",
alignItems: "center",
gap: "10px",
marginBottom: "10px",
}}
>
<NewMessageHeaderP>{forumText}</NewMessageHeaderP>
<CloseContainer onClick={closeModal}>
<NewMessageCloseImg src={ModalCloseSVG} />
</CloseContainer>
</InstanceListHeader>
<InstanceListContainer
<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;
}}
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}
>
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
value={forumTitle}
initialValue={selectedForum.value?.title}
afterChange={s => {
tempData.value[selectedForumIndex.value].title = s;
updateForums();
}}
label={"Forum Title"}
sx={{
height: "60px",
@ -157,15 +177,25 @@ export const ForumModal = ({ forumData }: NewForumModalProps) => {
<SelectField
options={["None", "Group", "GroupAdmin"]}
label={"Encryption Type"}
value={selectedEncryptionType}
initialValue={selectedForum.value?.encryption}
afterChange={s =>
(selectedForum.value.encryption = s as EncryptionType)
}
sx={{
"& .MuiSvgIcon-root": {
color: "gray",
height: "60px",
},
}}
/>
</NewMessageInputRow>
<GroupPermissionsForm groups={groups} />
<GroupPermissionsForm
initialGroups={selectedForum.value?.groups}
afterChange={g => {
tempData.value[selectedForumIndex.value].groups = g;
//updateForums();
}}
/>
<Spacer height="40px" />
<Box
sx={{
@ -174,45 +204,18 @@ export const ForumModal = ({ forumData }: NewForumModalProps) => {
>
<p style={{ color: "black" }}> Description </p>
<TextEditor
inlineContent={descriptionHTML.value}
inlineContent={selectedForum.value?.descriptionHTML}
setInlineContent={(
value: any,
delta: any,
source: any,
editor: any
) => {
descriptionHTML.value = value;
descriptionText.value = editor.getText(value);
selectedForum.value.descriptionHTML = value;
selectedForum.value.descriptionText = editor.getText(value);
}}
/>
</Box>
</InstanceListContainer>
<InstanceFooter
sx={{
backgroundColor: "rgba(217, 217, 217, 1)",
padding: "20px 42px",
alignItems: "center",
height: "90px",
}}
>
<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>
</ModalButton>
);
};

View File

@ -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}
/>
);
};

View File

@ -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": {

View File

@ -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%",

View File

@ -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 = () => {

View File

@ -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;