From 329a14ccf57633e4e7fdea5c21089d30f76a07e0 Mon Sep 17 00:00:00 2001 From: Puyodead1 Date: Wed, 17 Jul 2024 20:17:18 -0400 Subject: [PATCH] implement changing avatar --- src/App.tsx | 2 +- src/components/Avatar.tsx | 6 +- src/components/ChannelHeader.tsx | 2 +- src/components/ChannelList/ChannelList.tsx | 2 +- .../ChannelList/ChannelListItem.tsx | 2 +- src/components/GuildItem.tsx | 6 +- src/components/GuildSidebar.tsx | 2 +- src/components/Loader.tsx | 2 +- src/components/MemberList/MemberList.tsx | 2 +- src/components/MemberList/MemberListItem.tsx | 2 +- src/components/UserPanel.tsx | 2 +- .../contextMenus/GuildContextMenu.tsx | 2 +- .../contextMenus/MessageContextMenu.tsx | 2 +- .../contextMenus/UserContextMenu.tsx | 2 +- src/components/floating/GuildMenuPopout.tsx | 2 +- src/components/floating/UserProfilePopout.tsx | 2 +- src/components/guards/AuthenticationGuard.tsx | 2 +- .../guards/UnauthenticatedGuard.tsx | 2 +- src/components/markdown/Mention.tsx | 2 +- src/components/messaging/Chat.tsx | 2 +- src/components/messaging/ChatHeader.tsx | 2 +- src/components/messaging/Message.tsx | 2 +- src/components/messaging/MessageAuthor.tsx | 6 +- src/components/messaging/MessageInput.tsx | 4 +- src/components/messaging/MessageList.tsx | 3 +- .../attachments/AttachmentUploadProgress.tsx | 2 +- src/components/modals/BanMemberModal.tsx | 4 +- src/components/modals/CreateChannelModel.tsx | 2 +- src/components/modals/CreateInviteModal.tsx | 2 +- src/components/modals/CreateServerModal.tsx | 2 +- src/components/modals/JoinServerModal.tsx | 2 +- src/components/modals/KickMemberModal.tsx | 4 +- src/components/modals/LeaveServerModal.tsx | 2 +- src/components/modals/SettingsModal.tsx | 2 +- .../SettingsPages/AccountSettingsPage.tsx | 152 +++++++++++++++++- .../modals/SettingsPages/ExperimentsPage.tsx | 2 +- src/contexts/ContextMenuContext.ts | 41 +++++ ...ext.tsx => ContextMenuContextProvider.tsx} | 44 +---- src/contexts/Theme.tsx | 2 +- src/hooks/useAppStore.ts | 7 + src/index.tsx | 2 +- src/pages/LoadingPage.tsx | 2 +- src/pages/LoginPage.tsx | 3 +- src/pages/LogoutPage.tsx | 2 +- src/pages/RegistrationPage.tsx | 3 +- src/pages/subpages/ChannelPage.tsx | 2 +- src/pages/subpages/MFA.tsx | 2 +- src/stores/AppStore.ts | 6 - src/stores/GatewayConnectionStore.ts | 4 + src/utils/REST.ts | 42 +++++ 50 files changed, 300 insertions(+), 103 deletions(-) create mode 100644 src/contexts/ContextMenuContext.ts rename src/contexts/{ContextMenuContext.tsx => ContextMenuContextProvider.tsx} (50%) create mode 100644 src/hooks/useAppStore.ts diff --git a/src/App.tsx b/src/App.tsx index 6de77e2..056c65e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -17,11 +17,11 @@ import useLogger from "./hooks/useLogger"; import AppPage from "./pages/AppPage"; import LogoutPage from "./pages/LogoutPage"; import ChannelPage from "./pages/subpages/ChannelPage"; -import { useAppStore } from "./stores/AppStore"; import { Globals } from "./utils/Globals"; // @ts-expect-error no types import FPSStats from "react-fps-stats"; import { bannerController } from "./controllers/banners"; +import { useAppStore } from "./hooks/useAppStore"; import { isTauri } from "./utils/Utils"; function App() { diff --git a/src/components/Avatar.tsx b/src/components/Avatar.tsx index fd51001..0a22f9f 100644 --- a/src/components/Avatar.tsx +++ b/src/components/Avatar.tsx @@ -1,9 +1,9 @@ import { PresenceUpdateStatus } from "@spacebarchat/spacebar-api-types/v9"; import { observer } from "mobx-react-lite"; -import React from "react"; +import React, { useRef } from "react"; import styled from "styled-components"; +import { useAppStore } from "../hooks/useAppStore"; import AccountStore from "../stores/AccountStore"; -import { useAppStore } from "../stores/AppStore"; import Presence from "../stores/objects/Presence"; import User from "../stores/objects/User"; import Container from "./Container"; @@ -33,7 +33,7 @@ interface Props { function Avatar(props: Props) { const app = useAppStore(); - const ref = React.useRef(null); + const ref = useRef(null); const user = props.user ?? app.account; if (!user) return null; diff --git a/src/components/ChannelHeader.tsx b/src/components/ChannelHeader.tsx index 1726fec..b6bf3b5 100644 --- a/src/components/ChannelHeader.tsx +++ b/src/components/ChannelHeader.tsx @@ -1,7 +1,7 @@ import { observer } from "mobx-react-lite"; import React, { useEffect } from "react"; import styled from "styled-components"; -import { useAppStore } from "../stores/AppStore"; +import { useAppStore } from "../hooks/useAppStore"; import Icon, { IconProps } from "./Icon"; import { SectionHeader } from "./SectionHeader"; import Floating from "./floating/Floating"; diff --git a/src/components/ChannelList/ChannelList.tsx b/src/components/ChannelList/ChannelList.tsx index 8258079..fec3077 100644 --- a/src/components/ChannelList/ChannelList.tsx +++ b/src/components/ChannelList/ChannelList.tsx @@ -2,7 +2,7 @@ import { ChannelType } from "@spacebarchat/spacebar-api-types/v9"; import { observer } from "mobx-react-lite"; import { AutoSizer, List, ListRowProps } from "react-virtualized"; import styled from "styled-components"; -import { useAppStore } from "../../stores/AppStore"; +import { useAppStore } from "../../hooks/useAppStore"; import ChannelListItem from "./ChannelListItem"; const Container = styled.div` diff --git a/src/components/ChannelList/ChannelListItem.tsx b/src/components/ChannelList/ChannelListItem.tsx index 014cb62..6f5fc81 100644 --- a/src/components/ChannelList/ChannelListItem.tsx +++ b/src/components/ChannelList/ChannelListItem.tsx @@ -4,13 +4,13 @@ import { useNavigate } from "react-router-dom"; import styled from "styled-components"; import { ContextMenuContext } from "../../contexts/ContextMenuContext"; import { modalController } from "../../controllers/modals"; -import { useAppStore } from "../../stores/AppStore"; import Channel from "../../stores/objects/Channel"; import { Permissions } from "../../utils/Permissions"; import Icon from "../Icon"; import SidebarPill from "../SidebarPill"; import Floating from "../floating/Floating"; import FloatingTrigger from "../floating/FloatingTrigger"; +import { useAppStore } from "../../hooks/useAppStore"; const ListItem = styled.div<{ isCategory?: boolean }>` padding: ${(props) => (props.isCategory ? "16px 8px 0 0" : "1px 8px 0 0")}; diff --git a/src/components/GuildItem.tsx b/src/components/GuildItem.tsx index e6c29e9..7b2e21f 100644 --- a/src/components/GuildItem.tsx +++ b/src/components/GuildItem.tsx @@ -4,8 +4,8 @@ import React, { useContext } from "react"; import { useNavigate } from "react-router-dom"; import styled from "styled-components"; import { ContextMenuContext } from "../contexts/ContextMenuContext"; +import { useAppStore } from "../hooks/useAppStore"; import useLogger from "../hooks/useLogger"; -import { useAppStore } from "../stores/AppStore"; import Guild from "../stores/objects/Guild"; import { Permissions } from "../utils/Permissions"; import REST from "../utils/REST"; @@ -30,9 +30,7 @@ const Wrapper = styled(Container)<{ active?: boolean; hasImage?: boolean }>` border-radius: ${(props) => (props.active ? "30%" : "50%")}; background-color: ${(props) => props.hasImage ? "transparent" : props.active ? "var(--primary)" : "var(--background-secondary)"}; - transition: - border-radius 0.2s ease, - background-color 0.2s ease; + transition: border-radius 0.2s ease, background-color 0.2s ease; &:hover { border-radius: 30%; diff --git a/src/components/GuildSidebar.tsx b/src/components/GuildSidebar.tsx index f7c5197..e82bffd 100644 --- a/src/components/GuildSidebar.tsx +++ b/src/components/GuildSidebar.tsx @@ -4,7 +4,7 @@ import { useNavigate } from "react-router-dom"; import { AutoSizer, List, ListRowProps } from "react-virtualized"; import styled from "styled-components"; import { modalController } from "../controllers/modals"; -import { useAppStore } from "../stores/AppStore"; +import { useAppStore } from "../hooks/useAppStore"; import GuildItem, { GuildSidebarListItem } from "./GuildItem"; import SidebarAction from "./SidebarAction"; diff --git a/src/components/Loader.tsx b/src/components/Loader.tsx index 9ac1be8..e4cbc66 100644 --- a/src/components/Loader.tsx +++ b/src/components/Loader.tsx @@ -1,8 +1,8 @@ import { invoke } from "@tauri-apps/api/core"; import { observer } from "mobx-react-lite"; import React from "react"; +import { useAppStore } from "../hooks/useAppStore"; import LoadingPage from "../pages/LoadingPage"; -import { useAppStore } from "../stores/AppStore"; import { isTauri } from "../utils/Utils"; interface Props { diff --git a/src/components/MemberList/MemberList.tsx b/src/components/MemberList/MemberList.tsx index 88dd0a8..0246c25 100644 --- a/src/components/MemberList/MemberList.tsx +++ b/src/components/MemberList/MemberList.tsx @@ -2,7 +2,7 @@ import { autorun } from "mobx"; import { observer } from "mobx-react-lite"; import React from "react"; import styled from "styled-components"; -import { useAppStore } from "../../stores/AppStore"; +import { useAppStore } from "../../hooks/useAppStore"; import GuildMemberListStore from "../../stores/GuildMemberListStore"; import ListSection from "../ListSection"; import MemberListItem from "./MemberListItem"; diff --git a/src/components/MemberList/MemberListItem.tsx b/src/components/MemberList/MemberListItem.tsx index 270b1af..dca1ce0 100644 --- a/src/components/MemberList/MemberListItem.tsx +++ b/src/components/MemberList/MemberListItem.tsx @@ -3,7 +3,7 @@ import { observer } from "mobx-react-lite"; import { useContext } from "react"; import styled from "styled-components"; import { ContextMenuContext } from "../../contexts/ContextMenuContext"; -import { useAppStore } from "../../stores/AppStore"; +import { useAppStore } from "../../hooks/useAppStore"; import GuildMember from "../../stores/objects/GuildMember"; import Avatar from "../Avatar"; import Floating from "../floating/Floating"; diff --git a/src/components/UserPanel.tsx b/src/components/UserPanel.tsx index 81f26ce..d7954d2 100644 --- a/src/components/UserPanel.tsx +++ b/src/components/UserPanel.tsx @@ -1,7 +1,7 @@ import { observer } from "mobx-react-lite"; import styled from "styled-components"; import { modalController } from "../controllers/modals"; -import { useAppStore } from "../stores/AppStore"; +import { useAppStore } from "../hooks/useAppStore"; import Avatar from "./Avatar"; import Icon from "./Icon"; import IconButton from "./IconButton"; diff --git a/src/components/contextMenus/GuildContextMenu.tsx b/src/components/contextMenus/GuildContextMenu.tsx index b7aedf3..a0aad1c 100644 --- a/src/components/contextMenus/GuildContextMenu.tsx +++ b/src/components/contextMenus/GuildContextMenu.tsx @@ -2,8 +2,8 @@ import { ChannelType } from "@spacebarchat/spacebar-api-types/v9"; import { modalController } from "../../controllers/modals"; +import { useAppStore } from "../../hooks/useAppStore"; import useLogger from "../../hooks/useLogger"; -import { useAppStore } from "../../stores/AppStore"; import Guild from "../../stores/objects/Guild"; import { ContextMenu, ContextMenuButton, ContextMenuDivider } from "./ContextMenu"; diff --git a/src/components/contextMenus/MessageContextMenu.tsx b/src/components/contextMenus/MessageContextMenu.tsx index 6b904de..2030374 100644 --- a/src/components/contextMenus/MessageContextMenu.tsx +++ b/src/components/contextMenus/MessageContextMenu.tsx @@ -1,5 +1,5 @@ import { modalController } from "../../controllers/modals"; -import { useAppStore } from "../../stores/AppStore"; +import { useAppStore } from "../../hooks/useAppStore"; import Message from "../../stores/objects/Message"; import { ContextMenu, ContextMenuButton, ContextMenuDivider } from "./ContextMenu"; diff --git a/src/components/contextMenus/UserContextMenu.tsx b/src/components/contextMenus/UserContextMenu.tsx index 95eacdb..798e875 100644 --- a/src/components/contextMenus/UserContextMenu.tsx +++ b/src/components/contextMenus/UserContextMenu.tsx @@ -1,7 +1,7 @@ // loosely based on https://github.com/revoltchat/frontend/blob/master/components/app/menus/UserContextMenu.tsx import { modalController } from "../../controllers/modals"; -import { useAppStore } from "../../stores/AppStore"; +import { useAppStore } from "../../hooks/useAppStore"; import GuildMember from "../../stores/objects/GuildMember"; import User from "../../stores/objects/User"; import { ContextMenu, ContextMenuButton, ContextMenuDivider } from "./ContextMenu"; diff --git a/src/components/floating/GuildMenuPopout.tsx b/src/components/floating/GuildMenuPopout.tsx index 6bd794c..5177e52 100644 --- a/src/components/floating/GuildMenuPopout.tsx +++ b/src/components/floating/GuildMenuPopout.tsx @@ -3,7 +3,7 @@ import useLogger from "../../hooks/useLogger"; import React, { useEffect } from "react"; import { modalController } from "../../controllers/modals"; -import { useAppStore } from "../../stores/AppStore"; +import { useAppStore } from "../../hooks/useAppStore"; import { Permissions } from "../../utils/Permissions"; import { ContextMenu, ContextMenuButton, ContextMenuDivider } from "../contextMenus/ContextMenu"; diff --git a/src/components/floating/UserProfilePopout.tsx b/src/components/floating/UserProfilePopout.tsx index c563025..af8aff6 100644 --- a/src/components/floating/UserProfilePopout.tsx +++ b/src/components/floating/UserProfilePopout.tsx @@ -9,7 +9,7 @@ import { HorizontalDivider } from "../Divider"; import { CDNRoutes, ImageFormat } from "@spacebarchat/spacebar-api-types/v9"; import dayjs from "dayjs"; import SpacebarLogoBlue from "../../assets/images/logo/Spacebar_Icon.svg?react"; -import { useAppStore } from "../../stores/AppStore"; +import { useAppStore } from "../../hooks/useAppStore"; import REST from "../../utils/REST"; import Floating from "./Floating"; import FloatingTrigger from "./FloatingTrigger"; diff --git a/src/components/guards/AuthenticationGuard.tsx b/src/components/guards/AuthenticationGuard.tsx index 085a4a9..ffd164f 100644 --- a/src/components/guards/AuthenticationGuard.tsx +++ b/src/components/guards/AuthenticationGuard.tsx @@ -1,6 +1,6 @@ import { Navigate } from "react-router-dom"; +import { useAppStore } from "../../hooks/useAppStore"; import { LoadingSuspense } from "../../pages/LoadingPage"; -import { useAppStore } from "../../stores/AppStore"; interface Props { component: React.FC; diff --git a/src/components/guards/UnauthenticatedGuard.tsx b/src/components/guards/UnauthenticatedGuard.tsx index d59230c..45eefb1 100644 --- a/src/components/guards/UnauthenticatedGuard.tsx +++ b/src/components/guards/UnauthenticatedGuard.tsx @@ -1,6 +1,6 @@ import { Navigate } from "react-router-dom"; +import { useAppStore } from "../../hooks/useAppStore"; import { LoadingSuspense } from "../../pages/LoadingPage"; -import { useAppStore } from "../../stores/AppStore"; interface Props { component: React.FC; diff --git a/src/components/markdown/Mention.tsx b/src/components/markdown/Mention.tsx index e035c24..43359a0 100644 --- a/src/components/markdown/Mention.tsx +++ b/src/components/markdown/Mention.tsx @@ -2,7 +2,7 @@ import React, { memo } from "react"; import { useNavigate } from "react-router-dom"; import styled from "styled-components"; import { ContextMenuContext } from "../../contexts/ContextMenuContext"; -import { useAppStore } from "../../stores/AppStore"; +import { useAppStore } from "../../hooks/useAppStore"; import Channel from "../../stores/objects/Channel"; import Role from "../../stores/objects/Role"; import User from "../../stores/objects/User"; diff --git a/src/components/messaging/Chat.tsx b/src/components/messaging/Chat.tsx index 3d42bc2..afaad0c 100644 --- a/src/components/messaging/Chat.tsx +++ b/src/components/messaging/Chat.tsx @@ -2,8 +2,8 @@ import { runInAction } from "mobx"; import { observer } from "mobx-react-lite"; import React from "react"; import styled from "styled-components"; +import { useAppStore } from "../../hooks/useAppStore"; import useLogger from "../../hooks/useLogger"; -import { useAppStore } from "../../stores/AppStore"; import Channel from "../../stores/objects/Channel"; import Guild from "../../stores/objects/Guild"; import MemberList from "../MemberList/MemberList"; diff --git a/src/components/messaging/ChatHeader.tsx b/src/components/messaging/ChatHeader.tsx index deb0ac2..411fb9e 100644 --- a/src/components/messaging/ChatHeader.tsx +++ b/src/components/messaging/ChatHeader.tsx @@ -1,8 +1,8 @@ import * as Icons from "@mdi/js"; import { observer } from "mobx-react-lite"; import styled from "styled-components"; +import { useAppStore } from "../../hooks/useAppStore"; import useLogger from "../../hooks/useLogger"; -import { useAppStore } from "../../stores/AppStore"; import Channel from "../../stores/objects/Channel"; import Icon from "../Icon"; import { SectionHeader } from "../SectionHeader"; diff --git a/src/components/messaging/Message.tsx b/src/components/messaging/Message.tsx index 0b5ae9f..bb5824c 100644 --- a/src/components/messaging/Message.tsx +++ b/src/components/messaging/Message.tsx @@ -1,7 +1,7 @@ import { observer } from "mobx-react-lite"; import { memo, useContext } from "react"; import { ContextMenuContext } from "../../contexts/ContextMenuContext"; -import { useAppStore } from "../../stores/AppStore"; +import { useAppStore } from "../../hooks/useAppStore"; import { MessageLike } from "../../stores/objects/Message"; import { QueuedMessageStatus } from "../../stores/objects/QueuedMessage"; import Avatar from "../Avatar"; diff --git a/src/components/messaging/MessageAuthor.tsx b/src/components/messaging/MessageAuthor.tsx index 3b8a3cf..73d5920 100644 --- a/src/components/messaging/MessageAuthor.tsx +++ b/src/components/messaging/MessageAuthor.tsx @@ -2,8 +2,8 @@ import { observer } from "mobx-react-lite"; import React, { useContext } from "react"; import styled from "styled-components"; import { ContextMenuContext } from "../../contexts/ContextMenuContext"; +import { useAppStore } from "../../hooks/useAppStore"; import useLogger from "../../hooks/useLogger"; -import { useAppStore } from "../../stores/AppStore"; import Guild from "../../stores/objects/Guild"; import GuildMember from "../../stores/objects/GuildMember"; import { MessageLike } from "../../stores/objects/Message"; @@ -36,7 +36,7 @@ function MessageAuthor({ message, guild }: Props) { React.useEffect(() => { if (!eventData) return; - contextMenu.onContextMenu(eventData, { type: "user", user: message.author, member }); + contextMenu?.onContextMenu(eventData, { type: "user", user: message.author, member }); }, [eventData, member]); const onContextMenu = async (e: React.MouseEvent) => { @@ -68,7 +68,7 @@ function MessageAuthor({ message, guild }: Props) { style={{ color, }} - ref={contextMenu.setReferenceElement} + ref={contextMenu?.setReferenceElement} onContextMenu={onContextMenu} > {message.author.username} diff --git a/src/components/messaging/MessageInput.tsx b/src/components/messaging/MessageInput.tsx index c9969e5..4267fa5 100644 --- a/src/components/messaging/MessageInput.tsx +++ b/src/components/messaging/MessageInput.tsx @@ -6,8 +6,8 @@ import React from "react"; import styled from "styled-components"; import { modalController } from "../../controllers/modals"; +import { useAppStore } from "../../hooks/useAppStore"; import useLogger from "../../hooks/useLogger"; -import { useAppStore } from "../../stores/AppStore"; import Guild from "../../stores/objects/Guild"; import Snowflake from "../../utils/Snowflake"; import { MAX_ATTACHMENTS } from "../../utils/constants"; @@ -197,7 +197,7 @@ function MessageInput({ channel }: Props) { channel.type === ChannelType.DM ? channel.recipients?.[0].username : "#" + channel.name - }` + }` : "You do not have permission to send messages in this channel." } disabled={!channel.hasPermission("SEND_MESSAGES")} diff --git a/src/components/messaging/MessageList.tsx b/src/components/messaging/MessageList.tsx index 94d39b0..6c90a06 100644 --- a/src/components/messaging/MessageList.tsx +++ b/src/components/messaging/MessageList.tsx @@ -4,8 +4,8 @@ import InfiniteScroll from "react-infinite-scroll-component"; import PulseLoader from "react-spinners/PulseLoader"; import styled from "styled-components"; import useResizeObserver from "use-resize-observer"; +import { useAppStore } from "../../hooks/useAppStore"; import useLogger from "../../hooks/useLogger"; -import { useAppStore } from "../../stores/AppStore"; import { MessageGroup as MessageGroupType } from "../../stores/MessageStore"; import Channel from "../../stores/objects/Channel"; import Guild from "../../stores/objects/Guild"; @@ -30,6 +30,7 @@ const EndMessageContainer = styled.div` interface Props { guild: Guild; channel: Channel; + before?: string; } /** diff --git a/src/components/messaging/attachments/AttachmentUploadProgress.tsx b/src/components/messaging/attachments/AttachmentUploadProgress.tsx index 1966b28..de57007 100644 --- a/src/components/messaging/attachments/AttachmentUploadProgress.tsx +++ b/src/components/messaging/attachments/AttachmentUploadProgress.tsx @@ -1,6 +1,6 @@ import { observer } from "mobx-react-lite"; import styled from "styled-components"; -import { useAppStore } from "../../../stores/AppStore"; +import { useAppStore } from "../../../hooks/useAppStore"; import QueuedMessage from "../../../stores/objects/QueuedMessage"; import { bytesToSize } from "../../../utils/Utils"; import Icon from "../../Icon"; diff --git a/src/components/modals/BanMemberModal.tsx b/src/components/modals/BanMemberModal.tsx index 8ccb9c5..faf0425 100644 --- a/src/components/modals/BanMemberModal.tsx +++ b/src/components/modals/BanMemberModal.tsx @@ -4,7 +4,7 @@ import { useForm } from "react-hook-form"; import styled from "styled-components"; import * as yup from "yup"; import { ModalProps, modalController } from "../../controllers/modals"; -import { useAppStore } from "../../stores/AppStore"; +import { useAppStore } from "../../hooks/useAppStore"; import { Modal } from "./ModalComponents"; const DescriptionText = styled.p` @@ -54,7 +54,7 @@ export function BanMemberModal({ target, type, ...props }: ModalProps<"ban_membe data.reason ? { "X-Audit-Log-Reason": data.reason, - } + } : undefined, ) .then(() => { diff --git a/src/components/modals/CreateChannelModel.tsx b/src/components/modals/CreateChannelModel.tsx index 87708d8..dbe5729 100644 --- a/src/components/modals/CreateChannelModel.tsx +++ b/src/components/modals/CreateChannelModel.tsx @@ -11,7 +11,7 @@ import { useNavigate } from "react-router-dom"; import styled from "styled-components"; import * as yup from "yup"; import { ModalProps, modalController } from "../../controllers/modals"; -import { useAppStore } from "../../stores/AppStore"; +import { useAppStore } from "../../hooks/useAppStore"; import { messageFromFieldError } from "../../utils/messageFromFieldError"; import { Input, InputErrorText } from "../AuthComponents"; import { TextDivider } from "../Divider"; diff --git a/src/components/modals/CreateInviteModal.tsx b/src/components/modals/CreateInviteModal.tsx index 1fe2ccb..7370cc0 100644 --- a/src/components/modals/CreateInviteModal.tsx +++ b/src/components/modals/CreateInviteModal.tsx @@ -4,8 +4,8 @@ import React from "react"; import { useForm } from "react-hook-form"; import styled from "styled-components"; import { ModalProps } from "../../controllers/modals/types"; +import { useAppStore } from "../../hooks/useAppStore"; import useLogger from "../../hooks/useLogger"; -import { useAppStore } from "../../stores/AppStore"; import { messageFromFieldError } from "../../utils/messageFromFieldError"; import { Input, InputErrorText, InputLabel, LabelWrapper } from "../AuthComponents"; import Button from "../Button"; diff --git a/src/components/modals/CreateServerModal.tsx b/src/components/modals/CreateServerModal.tsx index 679af67..9b7d068 100644 --- a/src/components/modals/CreateServerModal.tsx +++ b/src/components/modals/CreateServerModal.tsx @@ -4,8 +4,8 @@ import { useForm } from "react-hook-form"; import { useNavigate } from "react-router-dom"; import styled from "styled-components"; import { ModalProps, modalController } from "../../controllers/modals"; +import { useAppStore } from "../../hooks/useAppStore"; import useLogger from "../../hooks/useLogger"; -import { useAppStore } from "../../stores/AppStore"; import { messageFromFieldError } from "../../utils/messageFromFieldError"; import { Input, InputErrorText, InputLabel, InputWrapper, LabelWrapper } from "../AuthComponents"; import { TextDivider } from "../Divider"; diff --git a/src/components/modals/JoinServerModal.tsx b/src/components/modals/JoinServerModal.tsx index 67351e7..f83c607 100644 --- a/src/components/modals/JoinServerModal.tsx +++ b/src/components/modals/JoinServerModal.tsx @@ -3,8 +3,8 @@ import { useForm } from "react-hook-form"; import { useNavigate } from "react-router-dom"; import styled from "styled-components"; import { ModalProps, modalController } from "../../controllers/modals"; +import { useAppStore } from "../../hooks/useAppStore"; import useLogger from "../../hooks/useLogger"; -import { useAppStore } from "../../stores/AppStore"; import { messageFromFieldError } from "../../utils/messageFromFieldError"; import { Input, InputErrorText, InputLabel, LabelWrapper } from "../AuthComponents"; import { TextDivider } from "../Divider"; diff --git a/src/components/modals/KickMemberModal.tsx b/src/components/modals/KickMemberModal.tsx index 9c2b607..3dbed8c 100644 --- a/src/components/modals/KickMemberModal.tsx +++ b/src/components/modals/KickMemberModal.tsx @@ -4,7 +4,7 @@ import { useForm } from "react-hook-form"; import styled from "styled-components"; import * as yup from "yup"; import { ModalProps, modalController } from "../../controllers/modals"; -import { useAppStore } from "../../stores/AppStore"; +import { useAppStore } from "../../hooks/useAppStore"; import { Modal } from "./ModalComponents"; const DescriptionText = styled.p` @@ -54,7 +54,7 @@ export function KickMemberModal({ target, ...props }: ModalProps<"kick_member">) data.reason ? { "X-Audit-Log-Reason": data.reason, - } + } : undefined, ) .then(() => { diff --git a/src/components/modals/LeaveServerModal.tsx b/src/components/modals/LeaveServerModal.tsx index 328cdfb..13ac9d1 100644 --- a/src/components/modals/LeaveServerModal.tsx +++ b/src/components/modals/LeaveServerModal.tsx @@ -2,8 +2,8 @@ import { Routes } from "@spacebarchat/spacebar-api-types/v9"; import { useState } from "react"; import { useNavigate } from "react-router-dom"; import { ModalProps, modalController } from "../../controllers/modals"; +import { useAppStore } from "../../hooks/useAppStore"; import useLogger from "../../hooks/useLogger"; -import { useAppStore } from "../../stores/AppStore"; import { Modal } from "./ModalComponents"; export function LeaveServerModal({ target, ...props }: ModalProps<"leave_server">) { diff --git a/src/components/modals/SettingsModal.tsx b/src/components/modals/SettingsModal.tsx index 0089a0d..95f726f 100644 --- a/src/components/modals/SettingsModal.tsx +++ b/src/components/modals/SettingsModal.tsx @@ -2,7 +2,7 @@ import { observer } from "mobx-react-lite"; import { useState } from "react"; import styled, { css } from "styled-components"; import { ModalProps, modalController } from "../../controllers/modals"; -import { useAppStore } from "../../stores/AppStore"; +import { useAppStore } from "../../hooks/useAppStore"; import { isTauri } from "../../utils/Utils"; import { APP_VERSION, GIT_BRANCH, GIT_REVISION, REPO_URL } from "../../utils/revison"; import Icon from "../Icon"; diff --git a/src/components/modals/SettingsPages/AccountSettingsPage.tsx b/src/components/modals/SettingsPages/AccountSettingsPage.tsx index b6f022e..87df6c5 100644 --- a/src/components/modals/SettingsPages/AccountSettingsPage.tsx +++ b/src/components/modals/SettingsPages/AccountSettingsPage.tsx @@ -1,12 +1,17 @@ +import { RESTPatchAPICurrentUserJSONBody, Routes } from "@spacebarchat/spacebar-api-types/v9"; import { observer } from "mobx-react-lite"; -import { useState } from "react"; +import { useEffect, useRef, useState } from "react"; import styled, { css } from "styled-components"; -import { useAppStore } from "../../../stores/AppStore"; +import { useAppStore } from "../../../hooks/useAppStore"; +import Avatar from "../../Avatar"; +import Button from "../../Button"; import SectionTitle from "../../SectionTitle"; const Content = styled.div` display: flex; flex-direction: column; + + min-width: 30vw; `; const UserInfoContainer = styled.div` @@ -77,25 +82,152 @@ const FieldValueToggle = styled.button` text-rendering: optimizeLegibility; `; +const IconContainer = styled.div` + position: relative; + display: inline-block; +`; + +const IconInput = styled.input` + display: none; +`; + +const FileInput = styled.div` + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + opacity: 0; + cursor: pointer; + font-size: 0px; +`; + +const UnsavedChangesBar = styled.div` + display: flex; + flex-direction: row; + justify-content: space-between; + background-color: var(--background-tertiary); + padding: 10px 16px; + border-radius: 8px; + margin-top: 24px; + align-items: center; +`; + +const UnsavedChangedActions = styled.div` + display: flex; + gap: 10px; +`; + +const Text = styled.p` + color: var(--text-secondary); + font-size: 16px; + font-weight: var(--font-weight-medium); + margin: 0; + padding: 0; +`; + function AccountSettingsPage() { const app = useAppStore(); const [shouldRedactEmail, setShouldRedactEmail] = useState(true); + const [selectedFile, setSelectedFile] = useState(); + const fileInputRef = useRef(null); + const [hasUnsavedChangd, setHasUnsavedChanged] = useState(false); + const [loading, setLoading] = useState(false); const redactEmail = (email: string) => { const [username, domain] = email.split("@"); return `${"*".repeat(username.length)}@${domain}`; }; - const refactPhoneNumber = (phoneNumber: string) => { + const redactPhoneNumber = (phoneNumber: string) => { const lastFour = phoneNumber.slice(-4); return "*".repeat(phoneNumber.length - 4) + lastFour; }; + const onAvatarChange = (event: React.ChangeEvent) => { + if (!event.target.files) return; + setSelectedFile(event.target.files[0]); + }; + + const discardChanges = () => { + setSelectedFile(undefined); + }; + + const save = async () => { + setLoading(true); + if (!selectedFile) return; + const reader = new FileReader(); + reader.onload = async () => { + const payload: RESTPatchAPICurrentUserJSONBody = { + // @ts-expect-error broken types or whatever + avatar: reader.result, + }; + app.rest + .patch(Routes.user(), payload) + .then((r) => { + // runInAction(() => { + // if (r.username) app.account!.username = r.username; + // if (r.avatar) app.account!.avatar = r.avatar; + // }); + setSelectedFile(undefined); + setLoading(false); + }) + .catch((e) => { + console.error(e); + setLoading(false); + }); + }; + reader.readAsDataURL(selectedFile); + }; + + useEffect(() => { + // handle unsaved changes state + if (selectedFile) { + setHasUnsavedChanged(true); + } else { + // Reset state if there is nothing changed + setHasUnsavedChanged(false); + } + }, [selectedFile]); + return (
Account + + + {selectedFile ? ( + Avatar + ) : ( + + )} + + fileInputRef.current?.click()} + aria-disabled={loading} + /> + + + Username @@ -138,6 +270,20 @@ function AccountSettingsPage() { + + {hasUnsavedChangd && ( + + You have unsaved changes. + + + + + + )}
); diff --git a/src/components/modals/SettingsPages/ExperimentsPage.tsx b/src/components/modals/SettingsPages/ExperimentsPage.tsx index af5554b..e19fdbb 100644 --- a/src/components/modals/SettingsPages/ExperimentsPage.tsx +++ b/src/components/modals/SettingsPages/ExperimentsPage.tsx @@ -1,7 +1,7 @@ import { observer } from "mobx-react-lite"; import { useState } from "react"; import styled from "styled-components"; -import { useAppStore } from "../../../stores/AppStore"; +import { useAppStore } from "../../../hooks/useAppStore"; import { EXPERIMENT_LIST, Experiment as ExperimentType } from "../../../stores/ExperimentsStore"; import SectionTitle from "../../SectionTitle"; diff --git a/src/contexts/ContextMenuContext.ts b/src/contexts/ContextMenuContext.ts new file mode 100644 index 0000000..413285f --- /dev/null +++ b/src/contexts/ContextMenuContext.ts @@ -0,0 +1,41 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { useFloating } from "@floating-ui/react"; +import React from "react"; +import Channel from "../stores/objects/Channel"; +import Guild from "../stores/objects/Guild"; +import GuildMember from "../stores/objects/GuildMember"; +import { MessageLike } from "../stores/objects/Message"; +import User from "../stores/objects/User"; + +export type ContextMenuProps = + | { + type: "user"; + user: User; + member?: GuildMember; + } + | { + type: "message"; + message: MessageLike; + } + | { + type: "channel"; + channel: Channel; + } + | { + type: "channelMention"; + channel: Channel; + } + | { + type: "guild"; + guild: Guild; + }; + +export type ContextMenuContextType = { + setReferenceElement: ReturnType["refs"]["setReference"]; + onContextMenu: (e: React.MouseEvent, props: ContextMenuProps) => void; + close: () => void; + open: (props: ContextMenuProps) => void; +}; + +// @ts-expect-error not specifying a default value here +export const ContextMenuContext = React.createContext(); diff --git a/src/contexts/ContextMenuContext.tsx b/src/contexts/ContextMenuContextProvider.tsx similarity index 50% rename from src/contexts/ContextMenuContext.tsx rename to src/contexts/ContextMenuContextProvider.tsx index 4842a61..3cb0eb9 100644 --- a/src/contexts/ContextMenuContext.tsx +++ b/src/contexts/ContextMenuContextProvider.tsx @@ -1,45 +1,7 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import { FloatingPortal, useFloating } from "@floating-ui/react"; +import { FloatingPortal } from "@floating-ui/react"; import React from "react"; import useContextMenu, { ContextMenuComponents } from "../hooks/useContextMenu"; -import Channel from "../stores/objects/Channel"; -import Guild from "../stores/objects/Guild"; -import GuildMember from "../stores/objects/GuildMember"; -import { MessageLike } from "../stores/objects/Message"; -import User from "../stores/objects/User"; - -export type ContextMenuProps = - | { - type: "user"; - user: User; - member?: GuildMember; - } - | { - type: "message"; - message: MessageLike; - } - | { - type: "channel"; - channel: Channel; - } - | { - type: "channelMention"; - channel: Channel; - } - | { - type: "guild"; - guild: Guild; - }; - -export type ContextMenuContextType = { - setReferenceElement: ReturnType["refs"]["setReference"]; - onContextMenu: (e: React.MouseEvent, props: ContextMenuProps) => void; - close: () => void; - open: (props: ContextMenuProps) => void; -}; - -// @ts-expect-error not specifying a default value here -export const ContextMenuContext = React.createContext(); +import { ContextMenuContext, ContextMenuProps } from "./ContextMenuContext"; // eslint-disable-next-line @typescript-eslint/no-explicit-any export const ContextMenuContextProvider: React.FC = ({ children }) => { @@ -53,7 +15,7 @@ export const ContextMenuContextProvider: React.FC = ({ children }) => { ? ContextMenuComponents[contextMenu.props.type] : () => { return null; - }; + }; return ( { this.app.users.update(data); + + if (data.id === this.app.account!.id) { + this.app.setUser(data); + } }; } diff --git a/src/utils/REST.ts b/src/utils/REST.ts index bf380f3..e2bb74f 100644 --- a/src/utils/REST.ts +++ b/src/utils/REST.ts @@ -200,6 +200,48 @@ export default class REST { }); } + public async patch( + path: string, + body?: T, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + queryParams: Record = {}, + headers: Record = {}, + ): Promise { + return new Promise((resolve, reject) => { + const url = REST.makeAPIUrl(path, queryParams); + this.logger.debug(`PATCH ${url}; payload:`, body); + return fetch(url, { + method: "PATCH", + headers: { + ...headers, + ...this.headers, + "Content-Type": "application/json", + }, + body: body ? JSON.stringify(body) : undefined, + mode: "cors", + }) + .then(async (res) => { + // handle json if content type is json + if (res.headers.get("content-type")?.includes("application/json")) { + const data = await res.json(); + if (res.ok) return resolve(data); + else return reject(data); + } + + // if theres content, handle text + if (res.headers.get("content-length") !== "0") { + const data = await res.text(); + if (res.ok) return resolve(data as U); + else return reject(data as U); + } + + if (res.ok) return resolve(res.status as U); + else return reject(res.statusText); + }) + .catch(reject); + }); + } + public async postFormData( path: string, body: FormData,