Browse Source

Forums can now be published using the ForumModal.

Forums added to Home.tsx as forumData[], which is fetched when Home component initializes

Many functions moved from ForumModal to ForumModal-Data,

Most code in Home page deleted because it was from Q-Mail and not needed.

Minor changes to identifiers

App owner temporarily changed to Qortal Seth for easier testing.
master
Qortal Dev 2 days ago
parent
commit
0659433f03
  1. 32
      package-lock.json
  2. 1
      package.json
  3. 13
      src/App.tsx
  4. 10
      src/constants/Identifiers.ts
  5. 4
      src/constants/Misc.ts
  6. 28
      src/pages/Forum/ActionBar.tsx
  7. 75
      src/pages/Forum/Forum.tsx
  8. 129
      src/pages/Forum/ForumModal-Data.ts
  9. 125
      src/pages/Forum/ForumModal.tsx
  10. 3
      src/pages/Forum/ForumThreads.tsx
  11. 4
      src/pages/Forum/GroupPermissionsForm.tsx
  12. 289
      src/pages/Forum/NewForumModal-data.ts
  13. 1
      src/pages/Home/Home-styles.ts
  14. 99
      src/pages/Home/Home.tsx
  15. 4
      src/pages/Mail/GroupMail.tsx
  16. 6
      src/pages/Mail/Thread.tsx
  17. 36
      src/utils/QortalRequests.ts
  18. 1
      src/utils/toBase64.ts

32
package-lock.json generated

@ -1,12 +1,12 @@
{
"name": "q-mail",
"version": "0.0.0",
"name": "q-mintership",
"version": "1.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "q-mail",
"version": "0.0.0",
"name": "q-mintership",
"version": "1.0.0",
"dependencies": {
"@emotion/react": "^11.10.6",
"@emotion/styled": "^11.10.6",
@ -19,6 +19,7 @@
"@tiptap/extension-underline": "^2.0.4",
"@tiptap/starter-kit": "^2.0.4",
"@types/react-grid-layout": "^1.3.2",
"@types/react-redux": "^7.1.33",
"axios": "1.6.0",
"compressorjs": "^1.2.1",
"dompurify": "^3.0.3",
@ -2422,6 +2423,18 @@
"@types/react": "*"
}
},
"node_modules/@types/react-redux": {
"version": "7.1.33",
"resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.33.tgz",
"integrity": "sha512-NF8m5AjWCkert+fosDsN3hAlHzpjSiXlVy9EgQEmLoBhaNXbmyeGs/aj5dQzKuF+/q+S7JQagorGDW8pJ28Hmg==",
"license": "MIT",
"dependencies": {
"@types/hoist-non-react-statics": "^3.3.0",
"@types/react": "*",
"hoist-non-react-statics": "^3.3.0",
"redux": "^4.0.0"
}
},
"node_modules/@types/react-transition-group": {
"version": "4.4.5",
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.5.tgz",
@ -7173,6 +7186,17 @@
"@types/react": "*"
}
},
"@types/react-redux": {
"version": "7.1.33",
"resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.33.tgz",
"integrity": "sha512-NF8m5AjWCkert+fosDsN3hAlHzpjSiXlVy9EgQEmLoBhaNXbmyeGs/aj5dQzKuF+/q+S7JQagorGDW8pJ28Hmg==",
"requires": {
"@types/hoist-non-react-statics": "^3.3.0",
"@types/react": "*",
"hoist-non-react-statics": "^3.3.0",
"redux": "^4.0.0"
}
},
"@types/react-transition-group": {
"version": "4.4.5",
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.5.tgz",

1
package.json

@ -20,6 +20,7 @@
"@tiptap/extension-underline": "^2.0.4",
"@tiptap/starter-kit": "^2.0.4",
"@types/react-grid-layout": "^1.3.2",
"@types/react-redux": "^7.1.33",
"axios": "1.6.0",
"compressorjs": "^1.2.1",
"dompurify": "^3.0.3",

13
src/App.tsx

@ -1,20 +1,31 @@
// @ts-nocheck
import { signal } from "@preact/signals-react";
import { useEffect } from "react";
import { Routes, Route } from "react-router-dom";
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 Notification from "./components/common/Notification/Notification";
export const forums = signal<ForumData[]>([]);
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}>

10
src/constants/Identifiers.ts

@ -1,3 +1,5 @@
import { appOwner } from "./Misc";
export const useTestIdentifiers = true;
const appName = useTestIdentifiers ? "mintTEST" : "mintership";
@ -6,8 +8,6 @@ export const QMAIL_BASE = `${appName}_mail`;
export const ATTATCHMENT_BASE = `${appName}_attachments`;
export const AUDIO_BASE = `${appName}_qaudio`;
export const FORUM_BASE = `${appName}_forum`;
export const THREAD_BASE = `${appName}_thread`;
export const THREAD_MESSAGE = `${appName}_thread_message`;
export const FORUM_GROUPID = 0; // will be determined later
export const FORUMS_ID = `${appName}_forums_${appOwner}`;
export const THREAD_BASE = `${appName}_thread_${appOwner}`;
export const THREAD_MESSAGE_BASE = `${appName}_thread_message`;

4
src/constants/Misc.ts

@ -1,4 +1,4 @@
export const appOwner = "Q-Mintership";
//export const appOwner = "Q-Mintership";
export const appOwner = "Qortal Seth";
const maxSizeMB = 25;
export const maxPrivateFileSize = maxSizeMB * 1024 * 1024;

28
src/pages/Forum/ActionBar.tsx

@ -0,0 +1,28 @@
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>
);
};

75
src/pages/Forum/Forum.tsx

@ -9,23 +9,16 @@ import {
InstanceContainer,
} from "../Home/Home-styles";
import { GroupMail } from "../Mail/GroupMail";
import { EncryptionType } from "./NewForumModal";
import { Group } from "./GroupPermissionsForm";
import { EncryptionType, ForumData } from "./ForumModal";
export type GroupPermissionType = "Read" | "Write";
export interface GroupPermissions {
id: string;
permissions: GroupPermissionType;
}
interface ForumProps {
title: string;
description: string;
encryption?: EncryptionType;
groupID: string;
groupPermissions: GroupPermissions[];
}
export const Forum = ({ encryption = "None" }: ForumProps) => {
export const Forum = ({
title,
encryption,
groups,
descriptionHTML,
descriptionText,
}: ForumData) => {
const [currentThread, setCurrentThread] = useState<any>(null);
const openNewThread = () => {
@ -36,20 +29,44 @@ export const Forum = ({ encryption = "None" }: ForumProps) => {
executeEvent("openNewThreadModal", {});
};
const forumWidth = 95;
const forumMarginLeft = (100 - forumWidth) / 2;
return (
<Box>
<InstanceContainer>
<ComposeContainer onClick={openNewThread}>
<ComposeIcon src={ComposeIconSVG} />
<ComposeP>{currentThread ? "New Post" : "New Thread"}</ComposeP>
</ComposeContainer>
</InstanceContainer>
{/*<GroupMail*/}
{/* groupInfo={selectedGroup}*/}
{/* currentThread={currentThread}*/}
{/* setCurrentThread={setCurrentThread}*/}
{/*/>*/}
<Box
sx={{
display: "flex",
flexDirection: "column",
width: `${forumWidth}vw`,
marginLeft: `${forumMarginLeft}vw`,
justifyItems: "center",
marginTop: "10px",
border: "1px solid white",
}}
>
<Box
sx={{
backgroundImage: "linear-gradient(#355D80, #85A1BE)", // ends at #85A1BE bottom 1px line is #375576
width: "100%",
border: "1px solid gray",
}}
>
<span>{title}</span>
<Box>{descriptionText.trim()}</Box>
</Box>
</Box>
// <Box>
// <InstanceContainer>
// <ComposeContainer onClick={openNewThread}>
// <ComposeIcon src={ComposeIconSVG} />
// <ComposeP>{currentThread ? "New Post" : "New Thread"}</ComposeP>
// </ComposeContainer>
// </InstanceContainer>
/*<GroupMail*/
/* groupInfo={selectedGroup}*/
/* currentThread={currentThread}*/
/* setCurrentThread={setCurrentThread}*/
/*/>*/
);
};

129
src/pages/Forum/ForumModal-Data.ts

@ -0,0 +1,129 @@
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 { setNotification } from "../../state/features/notificationsSlice";
import { store } from "../../state/store";
import { getGroup } from "../../utils/QortalRequests";
import { objectToBase64, toBase64 } from "../../utils/toBase64";
import { Group } from "./GroupPermissionsForm";
import { descriptionMaxLength, ForumData } from "./ForumModal";
const getGroupNames = async (groups: Group[]) => {
const groupPromises = groups.map(group => getGroup(group.id.value));
return await Promise.all(groupPromises);
};
const verifyData = async (formData: ForumData) => {
const userName = store.getState()?.auth?.user?.name;
const { title, encryption, groups, descriptionHTML, descriptionText } =
formData;
let errorMsg = "";
if (!userName) errorMsg = "Cannot send a message without access to your name";
if (!title) errorMsg = "Forum title is empty";
if (!encryption) errorMsg = "Encryption Type is empty";
if (!descriptionText) errorMsg = "Description is empty";
if (groups.filter(group => !!group.id).length < groups.length)
errorMsg = "A group has an empty ID";
if (groups.filter(group => !!group.permissions).length < groups.length)
errorMsg = "A group has empty permissions";
const groupsWithNames = await getGroupNames(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];
};
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;
try {
await addForum(formData);
const publishDescription =
formData.title +
"_" +
formData.descriptionText.substring(0, descriptionMaxLength);
const userName = store.getState()?.auth?.user?.name;
await qortalRequest({
action: "PUBLISH_QDN_RESOURCE",
name: userName,
service: "METADATA",
identifier: FORUMS_ID,
description: publishDescription,
data64: await objectToBase64(forums),
});
success = true;
store.dispatch(
setNotification({
msg: "Forum published",
alertType: "success",
})
);
} 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);
}
return success;
};

125
src/pages/Forum/NewForumModal.tsx → src/pages/Forum/ForumModal.tsx

@ -1,36 +1,28 @@
import CloseIcon from "@mui/icons-material/Close";
import { Box, Input, Typography } from "@mui/material";
import MenuBookIcon from "@mui/icons-material/MenuBook";
import { Box } from "@mui/material";
import {
ReadonlySignal,
Signal,
signal,
useComputed,
useSignal,
useSignalEffect,
} from "@preact/signals-react";
import { useSignals } from "@preact/signals-react/runtime";
import mime from "mime";
import React, { useState } from "react";
import { useDropzone } from "react-dropzone";
import { useDispatch, useSelector } from "react-redux";
import React 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 AttachmentSVG from "../../assets/svgs/NewMessageAttachment.svg";
import { SendNewMessage } from "../../assets/svgs/SendNewMessage";
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, maxPrivateFileSize } from "../../constants/Misc";
import { setNotification } from "../../state/features/notificationsSlice";
import { appOwner } from "../../constants/Misc";
import { RootState } from "../../state/store";
import { formatBytes } from "../../utils/displaySize";
import { getFileExtension } from "../../utils/helpers";
import {
AttachmentContainer,
CloseContainer,
ComposeContainer,
ComposeIcon,
@ -39,81 +31,84 @@ import {
InstanceFooter,
InstanceListContainer,
InstanceListHeader,
NewMessageAttachmentImg,
NewMessageCloseImg,
NewMessageHeaderP,
NewMessageInputRow,
NewMessageSendButton,
NewMessageSendP,
} from "../Home/Home-styles";
import { GroupPermissionType } from "./Forum";
import { Group, GroupPermissionsForm } from "./GroupPermissionsForm";
import { publishForum } from "./NewForumModal-data";
import { publishForum } from "./ForumModal-Data";
import { QmailTextField } from "./QmailTextField";
export type EncryptionType = "None" | "Group" | "GroupAdmin";
export type NewForumModalData = {
name: string | undefined;
export type EncryptionType = "None" | "Group" | "GroupAdmin" | "";
export interface ForumData {
title: string;
encryption: "None" | "Group" | "GroupAdmin" | "";
encryption: EncryptionType;
groups: Group[];
description: string;
};
descriptionHTML: string;
descriptionText: string;
}
export type GroupPermissionType = "Read" | "Write";
export interface GroupPermissions {
id: string;
permissions: GroupPermissionType;
}
export const NewForumModal = () => {
export const titleMaxLength = 60;
export const descriptionMaxLength = 160;
interface NewForumModalProps {
forumData?: ForumData[];
}
export const ForumModal = ({ forumData }: NewForumModalProps) => {
useSignals();
const navigate = useNavigate();
const isOpen = useSignal<boolean>(false);
const forumTitle = useSignal<string>("");
const description = useSignal<string>("");
const descriptionHTML = useSignal<string>("");
const descriptionText = useSignal<string>("");
const groups = useSignal<Group[]>([
{
id: signal<string>(""),
permissions: signal<GroupPermissionType>("Read"),
},
]);
const selectedEncryptionType = useSignal<EncryptionType | "">("None");
const formData = useComputed(() => {
const { user } = useSelector((state: RootState) => state.auth);
const formData: ReadonlySignal<ForumData> = useComputed(() => {
return {
name: user?.name,
title: forumTitle.value,
encryption: selectedEncryptionType.value,
groups: groups.value,
description: description.value,
descriptionHTML: descriptionHTML.value,
descriptionText: descriptionText.value,
};
});
const { user } = useSelector((state: RootState) => state.auth);
// const dispatch = useDispatch();
const closeModal = () => {
forumTitle.value = "";
description.value = "";
isOpen.value = false;
};
const groups = signal<Group[]>([
{
id: signal<string>(""),
name: "",
permissions: signal<GroupPermissionType>("Read"),
},
]);
const forumText = !!forumData ? "Edit Forums" : "Add Forum";
const publishText = !!forumData ? "Edit Forums" : "Create Forum";
return (
<Box
sx={{
display: "flex",
}}
>
<InstanceContainer>
{(user?.name === appOwner || useTestIdentifiers) && (
<ComposeContainer onClick={e => (isOpen.value = true)}>
<ComposeIcon src={ComposeIconSVG} />
<ComposeP>{"New Forum"}</ComposeP>
</ComposeContainer>
)}
<ComposeContainer onClick={e => navigate("/sponsorshipData")}>
{(user?.name === appOwner || useTestIdentifiers) && (
<ComposeContainer onClick={e => (isOpen.value = true)}>
<ComposeIcon src={ComposeIconSVG} />
<ComposeP>{"Sponsorship Data"}</ComposeP>
<ComposeP sx={{ fontSize: "70%" }}>{forumText}</ComposeP>
</ComposeContainer>
</InstanceContainer>
)}
<ReusableModal
open={isOpen.value}
customStyles={{
@ -136,7 +131,7 @@ export const NewForumModal = () => {
alignItems: "center",
}}
>
<NewMessageHeaderP>{"New Forum"}</NewMessageHeaderP>
<NewMessageHeaderP>{forumText}</NewMessageHeaderP>
<CloseContainer onClick={closeModal}>
<NewMessageCloseImg src={ModalCloseSVG} />
</CloseContainer>
@ -157,7 +152,7 @@ export const NewForumModal = () => {
height: "60px",
borderBottom: "1px solid gray",
}}
maxLength={30}
maxLength={titleMaxLength}
/>
<SelectField
options={["None", "Group", "GroupAdmin"]}
@ -179,9 +174,15 @@ export const NewForumModal = () => {
>
<p style={{ color: "black" }}> Description </p>
<TextEditor
inlineContent={description.value}
setInlineContent={(val: any) => {
description.value = val;
inlineContent={descriptionHTML.value}
setInlineContent={(
value: any,
delta: any,
source: any,
editor: any
) => {
descriptionHTML.value = value;
descriptionText.value = editor.getText(value);
}}
/>
</Box>
@ -194,8 +195,14 @@ export const NewForumModal = () => {
height: "90px",
}}
>
<NewMessageSendButton onClick={() => publishForum(formData.value)}>
<NewMessageSendP>{"Create Forum"}</NewMessageSendP>
<NewMessageSendButton
onClick={() => {
publishForum(formData.value).then(success => {
if (success) closeModal();
});
}}
>
<NewMessageSendP>{publishText}</NewMessageSendP>
<CreateThreadIcon
color="red"

3
src/pages/Forum/ForumThreads.tsx

@ -0,0 +1,3 @@
export const ForumThreads = () => {
return <></>;
};

4
src/pages/Forum/GroupPermissionsForm.tsx

@ -16,12 +16,11 @@ import React, { useEffect } from "react";
import ShortUniqueId from "short-unique-id";
import { SelectField } from "../../components/common/SelectField";
import { NewMessageInputRow } from "../Home/Home-styles";
import { GroupPermissions, GroupPermissionType } from "./Forum";
import { GroupPermissionType } from "./ForumModal";
import { QmailTextField } from "./QmailTextField";
export interface Group {
id: Signal<string>;
name: string;
permissions: Signal<GroupPermissionType>;
}
export interface GroupPermissionsFormProps {
@ -33,7 +32,6 @@ export const GroupPermissionsForm = ({ groups }: GroupPermissionsFormProps) => {
const uid = new ShortUniqueId();
const newGroup = {
id: signal<string>(""),
name: "",
permissions: signal<GroupPermissionType>("Read"),
} as Group;
const addGroup = () => {

289
src/pages/Forum/NewForumModal-data.ts

@ -1,289 +0,0 @@
import { ReadonlySignal } from "@preact/signals-react";
import { ATTATCHMENT_BASE, THREAD_BASE } from "../../constants/Identifiers";
import {
MAIL_ATTACHMENT_SERVICE_TYPE,
MAIL_SERVICE_TYPE,
THREAD_SERVICE_TYPE,
} from "../../constants/mail";
import { setNotification } from "../../state/features/notificationsSlice";
import { store } from "../../state/store";
import { getGroup } from "../../utils/QortalRequests";
import { objectToBase64, toBase64 } from "../../utils/toBase64";
import { Group } from "./GroupPermissionsForm";
import { NewForumModalData } from "./NewForumModal";
const getGroupNames = async (groups: Group[]) => {
const groupPromises = groups.map(group => getGroup(group.id.value));
return await Promise.all(groupPromises);
};
const verifyData = async (formData: NewForumModalData) => {
const { name, title, encryption, groups, description } = formData;
let errorMsg = "";
if (!name) errorMsg = "Cannot send a message without a access to your name";
if (!title) errorMsg = "Forum title is empty";
if (!encryption) errorMsg = "Encryption Type is empty";
if (!description) errorMsg = "Description is empty";
if (groups.filter(group => !!group.id).length < groups.length)
errorMsg = "A group has an empty ID";
if (groups.filter(group => !!group.permissions).length < groups.length)
errorMsg = "A group has an empty permissions";
const groupsWithNames = await getGroupNames(groups);
if (groupsWithNames.filter(group => !!group.groupName).length < groups.length)
errorMsg = "A group doesn't exist";
// if (!groupInfo) {
// errorMsg = "Cannot access group information";
// }
//
// // if (!description) missingFields.push('subject')
// if (missingFields.length > 0) {
// const missingFieldsString = missingFields.join(", ");
// const errMsg = `Missing: ${missingFieldsString}`;
// errorMsg = errMsg;
// }
// const noExtension = attachments.filter(item => !item.extension);
// if (noExtension.length > 0) {
// errorMsg =
// "One of your attachments does not have an extension (example: .png, .pdf, ect...)";
// }
//
return errorMsg;
};
export const publishForum = async (formData: NewForumModalData) => {
const { name, title, encryption, groups, description } = formData;
const errorMsg = await verifyData(formData);
try {
if (errorMsg) {
store.dispatch(
setNotification({
msg: errorMsg,
alertType: "error",
})
);
throw new Error(errorMsg);
}
} catch (error) {
console.log(error);
}
debugger;
//
// const mailObject: any = {
// subject,
// createdAt: Date.now(),
// version: 1,
// attachments,
// textContentV2: value,
// name,
// threadOwner: currentThread?.threadData?.name || name,
// };
//
// try {
// const groupPublicKeys = Object.keys(members)?.map(
// (key: any) => members[key]?.publicKey
// );
// if (!groupPublicKeys || groupPublicKeys?.length === 0) {
// throw new Error("No members in this group could be found");
// }
//
// // START OF ATTACHMENT LOGIC
//
// const attachmentArray: any[] = [];
// for (const singleAttachment of attachments) {
// const attachment = singleAttachment.file;
//
// const fileBase64 = await toBase64(attachment);
// if (typeof fileBase64 !== "string" || !fileBase64)
// throw new Error("Could not convert file to base64");
// const base64String = fileBase64.split(",")[1];
//
// const id = uid();
// const id2 = uid();
// const identifier = `${ATTATCHMENT_BASE}${id}_${id2}`;
// let fileExtension = attachment?.name?.split(".")?.pop();
// if (!fileExtension) {
// fileExtension = singleAttachment.extension;
// }
// const obj = {
// name: name,
// service: MAIL_ATTACHMENT_SERVICE_TYPE,
// filename: `${id}.${fileExtension}`,
// originalFilename: attachment?.name || "",
// identifier,
// data64: base64String,
// type: attachment?.type,
// };
//
// attachmentArray.push(obj);
// }
//
// if (attachmentArray?.length > 0) {
// mailObject.attachments = attachmentArray.map(item => {
// return {
// identifier: item.identifier,
// name,
// service: MAIL_ATTACHMENT_SERVICE_TYPE,
// filename: item.filename,
// originalFilename: item.originalFilename,
// type: item?.type,
// };
// });
//
// // const multiplePublish = {
// // action: "PUBLISH_MULTIPLE_QDN_RESOURCES",
// // resources: [...attachmentArray],
// // encrypt: true,
// // publicKeys: groupPublicKeys,
// // };
// // await qortalRequest(multiplePublish);
// }
//
// //END OF ATTACHMENT LOGIC
// if (!isMessage) {
// const idThread = uid();
// const messageToBase64 = await objectToBase64(mailObject);
// const threadObject = {
// title: threadTitle,
// groupId: groupInfo.id,
// createdAt: Date.now(),
// name,
// };
// const threadToBase64 = await objectToBase64(threadObject);
// let identifierThread = `${THREAD_BASE}${groupInfo.id}_${idThread}`;
// let requestBodyThread: any = {
// name: name,
// service: THREAD_SERVICE_TYPE,
// data64: threadToBase64,
// identifier: identifierThread,
// description: threadTitle?.slice(0, 200),
// action: "PUBLISH_QDN_RESOURCE",
// };
// const idMsg = uid();
// let groupIndex = identifierThread.indexOf("group");
// let result = identifierThread.substring(groupIndex);
// let identifier = `qortal_qmail_thmsg_${result}_${idMsg}`;
// let requestBody: any = {
// name: name,
// service: MAIL_SERVICE_TYPE,
// data64: messageToBase64,
// identifier,
// };
// const multiplePublishMsg = {
// action: "PUBLISH_MULTIPLE_QDN_RESOURCES",
// resources: [requestBody, ...attachmentArray],
// encrypt: true,
// publicKeys: groupPublicKeys,
// };
// await qortalRequest(requestBodyThread);
// setPublishes(multiplePublishMsg);
// setIsOpenMultiplePublish(true);
// // await qortalRequest(multiplePublishMsg);
// // dispatch(
// // setNotification({
// // msg: "Message sent",
// // alertType: "success",
// // })
// // );
// if (threadCallback) {
// // threadCallback({
// // threadData: threadObject,
// // threadOwner: name,
// // name,
// // threadId: identifierThread,
// // created: Date.now(),
// // service: 'MAIL_PRIVATE',
// // identifier: identifier
// // })
// setCallbackContent({
// thread: {
// threadData: threadObject,
// threadOwner: name,
// name,
// threadId: identifierThread,
// created: Date.now(),
// service: "MAIL_PRIVATE",
// identifier: identifier,
// },
// });
// }
// closeModal();
// } else {
// if (!currentThread) throw new Error("unable to locate thread Id");
// const idThread = currentThread.threadId;
// const messageToBase64 = await objectToBase64(mailObject);
// const idMsg = uid();
// let groupIndex = idThread.indexOf("group");
// let result = idThread.substring(groupIndex);
// let identifier = `qortal_qmail_thmsg_${result}_${idMsg}`;
// let requestBody: any = {
// name: name,
// service: MAIL_SERVICE_TYPE,
// data64: messageToBase64,
// identifier,
// };
// const multiplePublishMsg = {
// action: "PUBLISH_MULTIPLE_QDN_RESOURCES",
// resources: [requestBody, ...attachmentArray],
// encrypt: true,
// publicKeys: groupPublicKeys,
// };
// setPublishes(multiplePublishMsg);
// setIsOpenMultiplePublish(true);
// // await qortalRequest(multiplePublishMsg);
// // dispatch(
// // setNotification({
// // msg: "Message sent",
// // alertType: "success",
// // })
// // );
// if (messageCallback) {
// setCallbackContent({
// message: {
// identifier,
// id: identifier,
// name,
// service: MAIL_SERVICE_TYPE,
// created: Date.now(),
// ...mailObject,
// },
// });
// // messageCallback({
// // identifier,
// // id: identifier,
// // name,
// // service: MAIL_SERVICE_TYPE,
// // created: Date.now(),
// // ...mailObject,
// // });
// }
//
// closeModal();
// }
// } catch (error: any) {
// let notificationObj = null;
// if (typeof error === "string") {
// notificationObj = {
// msg: error || "Failed to send message",
// alertType: "error",
// };
// } else if (typeof error?.error === "string") {
// notificationObj = {
// msg: error?.error || "Failed to send message",
// alertType: "error",
// };
// } else {
// notificationObj = {
// msg: error?.message || "Failed to send message",
// alertType: "error",
// };
// }
// if (!notificationObj) return;
// dispatch(setNotification(notificationObj));
//
// throw new Error("Failed to send message");
// }
};

1
src/pages/Home/Home-styles.ts

@ -15,7 +15,6 @@ export const InstanceContainer = styled(Box)(({ theme }) => ({
backgroundColor: "var(--color-instance)",
height: "59px",
flexShrink: 0,
justifyContent: "space-between",
}));
export const MailContainer = styled(Box)(({ theme }) => ({
display: "flex",

99
src/pages/Home/Home.tsx

@ -1,98 +1,19 @@
import { Box } from "@mui/material";
import { signal } from "@preact/signals-react";
import React, {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from "react";
import { useSelector } from "react-redux";
import ComposeIconSVG from "../../assets/svgs/ComposeIcon.svg";
import { useTestIdentifiers } from "../../constants/Identifiers";
import { appOwner } from "../../constants/Misc";
import { useFetchMail } from "../../hooks/useFetchMail";
import { RootState } from "../../state/store";
import { executeEvent } from "../../utils/events";
import { NewForumModal } from "../Forum/NewForumModal";
import {
ComposeContainer,
ComposeIcon,
ComposeP,
InstanceContainer,
MailContainer,
} from "./Home-styles";
import { useSignals } from "@preact/signals-react/runtime";
import React from "react";
import { forums } from "../../App";
import { ActionBar } from "../Forum/ActionBar";
import { Forum } from "../Forum/Forum";
export const Home = () => {
const { user } = useSelector((state: RootState) => state.auth);
const privateGroups = useSelector(
(state: RootState) => state.global.privateGroups
);
const options = useMemo(() => {
return Object.keys(privateGroups).map(key => {
return {
...privateGroups[key],
name: privateGroups[key].groupName,
id: key,
};
});
}, [privateGroups]);
const hashMapMailMessages = useSelector(
(state: RootState) => state.mail.hashMapMailMessages
);
const userName = useMemo(() => {
if (!user?.name) return "";
return user.name;
}, [user]);
const { getMailMessages, checkNewMessages } = useFetchMail();
const getMessages = React.useCallback(
async (isOnMount?: boolean) => {
if (!user?.name || !user?.address) return;
try {
await getMailMessages(user.name, user.address);
} catch (error) {}
},
[getMailMessages, user]
);
const interval = useRef<any>(null);
const checkNewMessagesFunc = useCallback(() => {
if (!user?.name || !user?.address) return;
let isCalling = false;
interval.current = setInterval(async () => {
if (isCalling || !user?.name || !user?.address) return;
isCalling = true;
const res = await checkNewMessages(user?.name, user.address);
isCalling = false;
}, 30000);
}, [checkNewMessages, user]);
useEffect(() => {
checkNewMessagesFunc();
return () => {
if (interval?.current) {
clearInterval(interval.current);
}
};
}, [checkNewMessagesFunc]);
const firstMount = useRef(false);
useEffect(() => {
if (user?.name && !firstMount.current) {
getMessages(true);
firstMount.current = true;
}
}, [user]);
useSignals();
return (
<Box>
<NewForumModal />
<ActionBar />
{forums.value.map(forum => (
<Forum key={forum.title} {...forum} />
))}
</Box>
);
};

4
src/pages/Mail/GroupMail.tsx

@ -8,7 +8,7 @@ import React, {
} from "react";
import { useNavigate } from "react-router-dom";
import { useDispatch, useSelector } from "react-redux";
import { THREAD_BASE, THREAD_MESSAGE } from "../../constants/Identifiers";
import { THREAD_BASE, THREAD_MESSAGE_BASE } from "../../constants/Identifiers";
import { RootState } from "../../state/store";
import EditIcon from "@mui/icons-material/Edit";
import {
@ -214,7 +214,7 @@ export const GroupMail = ({
.join("");
dispatch(setIsLoadingCustom("Loading recent threads"));
const query = `${THREAD_MESSAGE}${groupId}`;
const query = `${THREAD_MESSAGE_BASE}${groupId}`;
const url = `/arbitrary/resources/search?mode=ALL&service=${MAIL_SERVICE_TYPE}&query=${query}&limit=100&includemetadata=false&offset=${0}&reverse=true&excludeblocked=true${queryString}`;
const response = await fetch(url, {
method: "GET",

6
src/pages/Mail/Thread.tsx

@ -8,7 +8,7 @@ import React, {
} from "react";
import { useNavigate } from "react-router-dom";
import { useDispatch, useSelector } from "react-redux";
import { THREAD_MESSAGE } from "../../constants/Identifiers";
import { THREAD_MESSAGE_BASE } from "../../constants/Identifiers";
import { RootState } from "../../state/store";
import {
@ -99,7 +99,7 @@ export const Thread = ({
let result = parts[0];
const threadId = result;
const offset = messages.length;
const query = `${THREAD_MESSAGE}${groupInfo?.threadData?.groupId}_${threadId}`;
const query = `${THREAD_MESSAGE_BASE}${groupInfo?.threadData?.groupId}_${threadId}`;
const url = `/arbitrary/resources/search?mode=ALL&service=${MAIL_SERVICE_TYPE}&query=${query}&limit=20&includemetadata=false&offset=${offset}&reverse=true&excludeblocked=true`;
const response = await fetch(url, {
method: "GET",
@ -195,7 +195,7 @@ export const Thread = ({
let parts = str.split("_").reverse();
let result = parts[0];
const threadId = result;
const query = `${THREAD_MESSAGE}${groupInfo?.threadData?.groupId}_${threadId}`;
const query = `${THREAD_MESSAGE_BASE}${groupInfo?.threadData?.groupId}_${threadId}`;
const url = `/arbitrary/resources/search?mode=ALL&service=${MAIL_SERVICE_TYPE}&query=${query}&limit=20&includemetadata=false&offset=${0}&reverse=true&excludeblocked=true`;
const response = await fetch(url, {
method: "GET",

36
src/utils/QortalRequests.ts

@ -1,3 +1,6 @@
import { FORUMS_ID } from "../constants/Identifiers";
import { appOwner } from "../constants/Misc";
export interface GroupData {
groupId: number;
owner: string;
@ -17,11 +20,30 @@ export const listGroups = async () => {
export const getGroup = async (groupID: number | string) => {
const url = `/groups/${groupID.toString()}`;
const response = await fetch(url, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
return (await response.json()) as GroupData;
try {
const response = await fetch(url, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
return (await response.json()) as GroupData;
} catch (error) {
return undefined;
}
};
export const fetchForumData = async () => {
try {
return await qortalRequest({
action: "FETCH_QDN_RESOURCE",
name: appOwner,
service: "METADATA",
identifier: FORUMS_ID,
});
} catch (error) {
console.log(error);
return undefined;
}
};

1
src/utils/toBase64.ts

@ -25,7 +25,6 @@ export const objectToBase64 = async (obj: any): Promise<string> => {
"data:application/json;base64,",
""
);
console.log(`base64 resolution: ${base64}`);
resolve(base64);
} else {
reject(new Error("Failed to read the Blob as a base64-encoded string"));

Loading…
Cancel
Save