From 9e460c5b3da63c9e70b27e2b855e731f06048852 Mon Sep 17 00:00:00 2001
From: Paul <paulmakles@gmail.com>
Date: Sun, 20 Jun 2021 20:30:42 +0100
Subject: [PATCH] Refactor + add message box.

---
 src/components/common/messaging/Message.tsx   |  4 +-
 .../common/messaging/MessageBox.tsx           | 89 +++++++++++++++++++
 .../common/messaging/SystemMessage.tsx        |  6 +-
 .../common/{ => user}/UserCheckbox.tsx        |  2 +-
 .../common/{ => user}/UserHeader.tsx          | 10 +--
 src/components/common/{ => user}/UserIcon.tsx |  8 +-
 .../common/{ => user}/UserShort.tsx           |  0
 .../common/{ => user}/UserStatus.tsx          |  0
 .../navigation/items/ButtonItem.tsx           |  4 +-
 .../navigation/left/HomeSidebar.tsx           | 30 ++++---
 src/components/ui/TextArea.tsx                | 43 +++++++--
 src/context/intermediate/modals/Prompt.tsx    |  6 +-
 .../intermediate/popovers/UserPicker.tsx      |  2 +-
 .../intermediate/popovers/UserProfile.tsx     | 22 ++---
 src/context/revoltjs/CheckAuth.tsx            |  4 +-
 src/context/revoltjs/hooks.ts                 | 13 ++-
 src/lib/defer.ts                              |  3 +
 src/pages/channels/Channel.tsx                |  2 +
 src/pages/friends/Friend.tsx                  |  4 +-
 src/pages/settings/Settings.module.scss       | 22 -----
 src/pages/settings/SettingsTextArea.tsx       |  6 --
 src/pages/settings/channel/Overview.tsx       | 12 +--
 src/pages/settings/panes/Account.tsx          |  6 +-
 src/pages/settings/panes/Appearance.tsx       | 11 +--
 src/pages/settings/panes/Feedback.tsx         |  8 +-
 src/pages/settings/panes/Profile.tsx          | 12 +--
 src/pages/settings/server/Invites.tsx         |  2 +-
 src/pages/settings/server/Overview.tsx        | 12 +--
 28 files changed, 224 insertions(+), 119 deletions(-)
 create mode 100644 src/components/common/messaging/MessageBox.tsx
 rename src/components/common/{ => user}/UserCheckbox.tsx (85%)
 rename src/components/common/{ => user}/UserHeader.tsx (88%)
 rename src/components/common/{ => user}/UserIcon.tsx (92%)
 rename src/components/common/{ => user}/UserShort.tsx (100%)
 rename src/components/common/{ => user}/UserStatus.tsx (100%)
 create mode 100644 src/lib/defer.ts
 delete mode 100644 src/pages/settings/SettingsTextArea.tsx

diff --git a/src/components/common/messaging/Message.tsx b/src/components/common/messaging/Message.tsx
index 03c1359..dfe9793 100644
--- a/src/components/common/messaging/Message.tsx
+++ b/src/components/common/messaging/Message.tsx
@@ -1,5 +1,5 @@
-import UserIcon from "../UserIcon";
-import { Username } from "../UserShort";
+import UserIcon from "../user/UserIcon";
+import { Username } from "../user/UserShort";
 import Markdown from "../../markdown/Markdown";
 import { Children } from "../../../types/Preact";
 import { attachContextMenu } from "preact-context-menu";
diff --git a/src/components/common/messaging/MessageBox.tsx b/src/components/common/messaging/MessageBox.tsx
new file mode 100644
index 0000000..ae98052
--- /dev/null
+++ b/src/components/common/messaging/MessageBox.tsx
@@ -0,0 +1,89 @@
+import { useContext } from "preact/hooks";
+import { Channel } from "revolt.js";
+import { ulid } from "ulid";
+import { AppContext } from "../../../context/revoltjs/RevoltClient";
+import { takeError } from "../../../context/revoltjs/util";
+import { defer } from "../../../lib/defer";
+import { isTouchscreenDevice } from "../../../lib/isTouchscreenDevice";
+import { SingletonMessageRenderer, SMOOTH_SCROLL_ON_RECEIVE } from "../../../lib/renderer/Singleton";
+import { connectState } from "../../../redux/connector";
+import { WithDispatcher } from "../../../redux/reducers";
+import TextArea from "../../ui/TextArea";
+
+type Props = WithDispatcher & {
+    channel: Channel;
+    draft?: string;
+};
+
+function MessageBox({ channel, draft, dispatcher }: Props) {
+    const client = useContext(AppContext);
+
+    function setMessage(content?: string) {
+        if (content) {
+            dispatcher({
+                type: "SET_DRAFT",
+                channel: channel._id,
+                content
+            });
+        } else {
+            dispatcher({
+                type: "CLEAR_DRAFT",
+                channel: channel._id
+            });
+        }
+    }
+
+    async function send() {
+        const nonce = ulid();
+
+        const content = draft?.trim() ?? '';
+        if (content.length === 0) return;
+
+        setMessage();
+        dispatcher({
+            type: "QUEUE_ADD",
+            nonce,
+            channel: channel._id,
+            message: {
+                _id: nonce,
+                channel: channel._id,
+                author: client.user!._id,
+                content
+            }
+        });
+
+        defer(() => SingletonMessageRenderer.jumpToBottom(channel._id, SMOOTH_SCROLL_ON_RECEIVE));
+        // Sounds.playOutbound();
+
+        try {
+            await client.channels.sendMessage(channel._id, {
+                content,
+                nonce
+            });
+        } catch (error) {
+            dispatcher({
+                type: "QUEUE_FAIL",
+                error: takeError(error),
+                nonce
+            });
+        }
+    }
+
+    return (
+        <TextArea
+            value={draft}
+            onKeyDown={e => {
+                if (!e.shiftKey && e.key === "Enter" && !isTouchscreenDevice) {
+                    e.preventDefault();
+                    return send();
+                }
+            }}
+            onChange={e => setMessage(e.currentTarget.value)} />
+    )
+}
+
+export default connectState<Omit<Props, "dispatcher" | "draft">>(MessageBox, (state, { channel }) => {
+    return {
+        draft: state.drafts[channel._id]
+    }
+}, true)
diff --git a/src/components/common/messaging/SystemMessage.tsx b/src/components/common/messaging/SystemMessage.tsx
index c867f91..26de5f5 100644
--- a/src/components/common/messaging/SystemMessage.tsx
+++ b/src/components/common/messaging/SystemMessage.tsx
@@ -4,9 +4,9 @@ import { attachContextMenu } from "preact-context-menu";
 import { MessageObject } from "../../../context/revoltjs/util";
 import { useForceUpdate, useUser } from "../../../context/revoltjs/hooks";
 import { TextReact } from "../../../lib/i18n";
-import UserIcon from "../UserIcon";
-import Username from "../UserShort";
-import UserShort from "../UserShort";
+import UserIcon from "../user/UserIcon";
+import Username from "../user/UserShort";
+import UserShort from "../user/UserShort";
 import MessageBase, { MessageDetail, MessageInfo } from "./MessageBase";
 import styled from "styled-components";
 
diff --git a/src/components/common/UserCheckbox.tsx b/src/components/common/user/UserCheckbox.tsx
similarity index 85%
rename from src/components/common/UserCheckbox.tsx
rename to src/components/common/user/UserCheckbox.tsx
index 35577eb..05cb69f 100644
--- a/src/components/common/UserCheckbox.tsx
+++ b/src/components/common/user/UserCheckbox.tsx
@@ -1,6 +1,6 @@
 import { User } from "revolt.js";
 import UserIcon from "./UserIcon";
-import Checkbox, { CheckboxProps } from "../ui/Checkbox";
+import Checkbox, { CheckboxProps } from "../../ui/Checkbox";
 
 type UserProps = Omit<CheckboxProps, "children"> & { user: User };
 
diff --git a/src/components/common/UserHeader.tsx b/src/components/common/user/UserHeader.tsx
similarity index 88%
rename from src/components/common/UserHeader.tsx
rename to src/components/common/user/UserHeader.tsx
index 6a8265e..848e8ce 100644
--- a/src/components/common/UserHeader.tsx
+++ b/src/components/common/user/UserHeader.tsx
@@ -1,17 +1,17 @@
-import Tooltip from "./Tooltip";
+import Tooltip from "../Tooltip";
 import { User } from "revolt.js";
-import Header from "../ui/Header";
 import UserIcon from "./UserIcon";
 import { Text } from "preact-i18n";
+import Header from "../../ui/Header";
 import UserStatus from './UserStatus';
 import styled from "styled-components";
 import { Localizer } from 'preact-i18n';
 import { Link } from "react-router-dom";
-import IconButton from "../ui/IconButton";
+import IconButton from "../../ui/IconButton";
 import { Settings } from "@styled-icons/feather";
 import { openContextMenu } from "preact-context-menu";
-import { isTouchscreenDevice } from "../../lib/isTouchscreenDevice";
-import { useIntermediate } from "../../context/intermediate/Intermediate";
+import { isTouchscreenDevice } from "../../../lib/isTouchscreenDevice";
+import { useIntermediate } from "../../../context/intermediate/Intermediate";
 
 const HeaderBase = styled.div`
     gap: 0;
diff --git a/src/components/common/UserIcon.tsx b/src/components/common/user/UserIcon.tsx
similarity index 92%
rename from src/components/common/UserIcon.tsx
rename to src/components/common/user/UserIcon.tsx
index 5e9243f..65418b7 100644
--- a/src/components/common/UserIcon.tsx
+++ b/src/components/common/user/UserIcon.tsx
@@ -2,10 +2,10 @@ import { User } from "revolt.js";
 import { useContext } from "preact/hooks";
 import { MicOff } from "@styled-icons/feather";
 import styled, { css } from "styled-components";
-import { ThemeContext } from "../../context/Theme";
 import { Users } from "revolt.js/dist/api/objects";
-import IconBase, { IconBaseProps } from "./IconBase";
-import { AppContext } from "../../context/revoltjs/RevoltClient";
+import { ThemeContext } from "../../../context/Theme";
+import IconBase, { IconBaseProps } from "../IconBase";
+import { AppContext } from "../../../context/revoltjs/RevoltClient";
 
 type VoiceStatus = "muted";
 interface Props extends IconBaseProps<User> {
@@ -47,7 +47,7 @@ const VoiceIndicator = styled.div<{ status: VoiceStatus }>`
     ` }
 `;
 
-import fallback from './assets/user.png';
+import fallback from '../assets/user.png';
 
 export default function UserIcon(props: Props & Omit<JSX.SVGAttributes<SVGSVGElement>, keyof Props>) {
     const client = useContext(AppContext);
diff --git a/src/components/common/UserShort.tsx b/src/components/common/user/UserShort.tsx
similarity index 100%
rename from src/components/common/UserShort.tsx
rename to src/components/common/user/UserShort.tsx
diff --git a/src/components/common/UserStatus.tsx b/src/components/common/user/UserStatus.tsx
similarity index 100%
rename from src/components/common/UserStatus.tsx
rename to src/components/common/user/UserStatus.tsx
diff --git a/src/components/navigation/items/ButtonItem.tsx b/src/components/navigation/items/ButtonItem.tsx
index bcbd59a..4f5064e 100644
--- a/src/components/navigation/items/ButtonItem.tsx
+++ b/src/components/navigation/items/ButtonItem.tsx
@@ -2,12 +2,12 @@ import classNames from 'classnames';
 import styles from "./Item.module.scss";
 import Tooltip from '../../common/Tooltip';
 import IconButton from '../../ui/IconButton';
-import UserIcon from '../../common/UserIcon';
 import { Localizer, Text } from "preact-i18n";
 import { X, Zap } from "@styled-icons/feather";
-import UserStatus from '../../common/UserStatus';
 import { Children } from "../../../types/Preact";
+import UserIcon from '../../common/user/UserIcon';
 import ChannelIcon from '../../common/ChannelIcon';
+import UserStatus from '../../common/user/UserStatus';
 import { attachContextMenu } from 'preact-context-menu';
 import { Channels, Users } from "revolt.js/dist/api/objects";
 import { isTouchscreenDevice } from "../../../lib/isTouchscreenDevice";
diff --git a/src/components/navigation/left/HomeSidebar.tsx b/src/components/navigation/left/HomeSidebar.tsx
index a3082f3..9c6cf7a 100644
--- a/src/components/navigation/left/HomeSidebar.tsx
+++ b/src/components/navigation/left/HomeSidebar.tsx
@@ -2,23 +2,23 @@ import { Localizer, Text } from "preact-i18n";
 import { useContext } from "preact/hooks";
 import { Home, Users, Tool, Save } from "@styled-icons/feather";
 
-import { Link, Redirect, useLocation, useParams } from "react-router-dom";
+import styled from "styled-components";
+import Category from '../../ui/Category';
+import PaintCounter from "../../../lib/PaintCounter";
+import UserHeader from "../../common/user/UserHeader";
+import { Channels } from "revolt.js/dist/api/objects";
+import { connectState } from "../../../redux/connector";
+import ConnectionStatus from '../items/ConnectionStatus';
 import { WithDispatcher } from "../../../redux/reducers";
 import { Unreads } from "../../../redux/reducers/unreads";
-import { connectState } from "../../../redux/connector";
-import { AppContext } from "../../../context/revoltjs/RevoltClient";
-import { useChannels, useDMs, useForceUpdate, useUsers } from "../../../context/revoltjs/hooks";
-import { Users as UsersNS } from 'revolt.js/dist/api/objects';
 import { mapChannelWithUnread, useUnreads } from "./common";
-import { Channels } from "revolt.js/dist/api/objects";
-import { isTouchscreenDevice } from "../../../lib/isTouchscreenDevice";
-import ConnectionStatus from '../items/ConnectionStatus';
+import { Users as UsersNS } from 'revolt.js/dist/api/objects';
 import ButtonItem, { ChannelButton } from '../items/ButtonItem';
-import styled from "styled-components";
-import UserHeader from "../../common/UserHeader";
-import Category from '../../ui/Category';
-import PaintCounter from "../../../lib/PaintCounter";
+import { AppContext } from "../../../context/revoltjs/RevoltClient";
+import { isTouchscreenDevice } from "../../../lib/isTouchscreenDevice";
+import { Link, Redirect, useLocation, useParams } from "react-router-dom";
 import { useIntermediate } from "../../../context/intermediate/Intermediate";
+import { useDMs, useForceUpdate, useUsers } from "../../../context/revoltjs/hooks";
 
 type Props = WithDispatcher & {
     unreads: Unreads;
@@ -126,7 +126,11 @@ function HomeSidebar(props: Props) {
                     let user;
                     if (x.channel_type === 'DirectMessage') {
                         let recipient = client.channels.getRecipient(x._id);
-                        user = users.find(x => x!._id === recipient);
+                        user = users.find(x => x?._id === recipient);
+                        if (!user) {
+                            console.warn(`Skipped DM ${x._id} because user was missing.`);
+                            return null;
+                        }
                     }
                     
                     return (
diff --git a/src/components/ui/TextArea.tsx b/src/components/ui/TextArea.tsx
index d88dcb7..ed15695 100644
--- a/src/components/ui/TextArea.tsx
+++ b/src/components/ui/TextArea.tsx
@@ -1,12 +1,37 @@
-// ! FIXME: temporarily here until re-written
-// ! DO NOT IMRPOVE, JUST RE-WRITE
+// import classNames from "classnames";
+// import { memo } from "preact/compat";
+// import styles from "./TextArea.module.scss";
+// import { useState, useEffect, useRef, useLayoutEffect } from "preact/hooks";
+import styled, { css } from "styled-components";
+
+interface Props {
+    code?: boolean;
+}
+
+export default styled.textarea<Props>`
+    width: 100%;
+    resize: none;
+    display: block;
+    border-radius: 4px;
+
+    color: var(--foreground);
+    border: 2px solid transparent;
+    background: var(--secondary-background);
+    transition: border-color .2s ease-in-out;
+
+    &:focus {
+        outline: none;
+        border: 2px solid var(--accent);
+    }
 
-import classNames from "classnames";
-import { memo } from "preact/compat";
-import styles from "./TextArea.module.scss";
-import { useState, useEffect, useRef, useLayoutEffect } from "preact/hooks";
+    ${ props => props.code ? css`
+        font-family: 'Fira Mono', 'Courier New', Courier, monospace;
+    ` : css`
+        font-family: 'Open Sans', sans-serif;
+    ` }
+`;
 
-export interface TextAreaProps {
+/*export interface TextAreaProps {
     id?: string;
     value: string;
     maxRows?: number;
@@ -30,7 +55,7 @@ export interface TextAreaProps {
 
 const lineHeight = 20;
 
-export const TextArea = memo((props: TextAreaProps) => {
+export const TextAreaB = memo((props: TextAreaProps) => {
     const padding = props.padding ? props.padding * 2 : 0;
 
     const [height, setHeightState] = useState(
@@ -143,4 +168,4 @@ export const TextArea = memo((props: TextAreaProps) => {
             </div>
         </div>
     );
-});
+});*/
diff --git a/src/context/intermediate/modals/Prompt.tsx b/src/context/intermediate/modals/Prompt.tsx
index 3a1dac5..1a48e62 100644
--- a/src/context/intermediate/modals/Prompt.tsx
+++ b/src/context/intermediate/modals/Prompt.tsx
@@ -4,12 +4,12 @@ import { Children } from "../../../types/Preact";
 import { useIntermediate } from "../Intermediate";
 import InputBox from "../../../components/ui/InputBox";
 import Overline from "../../../components/ui/Overline";
-import UserIcon from "../../../components/common/UserIcon";
+import { AppContext } from "../../revoltjs/RevoltClient";
+import { mapMessage, takeError } from "../../revoltjs/util";
 import Modal, { Action } from "../../../components/ui/Modal";
 import { Channels, Servers } from "revolt.js/dist/api/objects";
 import { useContext, useEffect, useState } from "preact/hooks";
-import { AppContext } from "../../revoltjs/RevoltClient";
-import { mapMessage, takeError } from "../../revoltjs/util";
+import UserIcon from "../../../components/common/user/UserIcon";
 import Message from "../../../components/common/messaging/Message";
 
 interface Props {
diff --git a/src/context/intermediate/popovers/UserPicker.tsx b/src/context/intermediate/popovers/UserPicker.tsx
index 9c2a8e6..b4ab76c 100644
--- a/src/context/intermediate/popovers/UserPicker.tsx
+++ b/src/context/intermediate/popovers/UserPicker.tsx
@@ -4,7 +4,7 @@ import styles from "./UserPicker.module.scss";
 import { useUsers } from "../../revoltjs/hooks";
 import Modal from "../../../components/ui/Modal";
 import { User, Users } from "revolt.js/dist/api/objects";
-import UserCheckbox from "../../../components/common/UserCheckbox";
+import UserCheckbox from "../../../components/common/user/UserCheckbox";
 
 interface Props {
     omit?: string[];
diff --git a/src/context/intermediate/popovers/UserProfile.tsx b/src/context/intermediate/popovers/UserProfile.tsx
index 0a11cfd..2b37ee7 100644
--- a/src/context/intermediate/popovers/UserProfile.tsx
+++ b/src/context/intermediate/popovers/UserProfile.tsx
@@ -1,22 +1,22 @@
-import Modal from "../../../components/ui/Modal";
+import { decodeTime } from "ulid";
 import { Localizer, Text } from "preact-i18n";
 import styles from "./UserProfile.module.scss";
-import Preloader from "../../../components/ui/Preloader";
+import Modal from "../../../components/ui/Modal";
 import { Route } from "revolt.js/dist/api/routes";
 import { Users } from "revolt.js/dist/api/objects";
-import { IntermediateContext, useIntermediate } from "../Intermediate";
-import { Globe, Mail, Edit, UserPlus, Shield } from "@styled-icons/feather";
+import { useIntermediate } from "../Intermediate";
 import { Link, useHistory } from "react-router-dom";
-import { useContext, useEffect, useLayoutEffect, useState } from "preact/hooks";
-import { decodeTime } from "ulid";
 import { CashStack } from "@styled-icons/bootstrap";
-import { AppContext, ClientStatus, StatusContext } from "../../revoltjs/RevoltClient";
-import { useChannels, useForceUpdate, useUser, useUsers } from "../../revoltjs/hooks";
-import UserIcon from '../../../components/common/UserIcon';
-import UserStatus from '../../../components/common/UserStatus';
+import Preloader from "../../../components/ui/Preloader";
 import Tooltip from '../../../components/common/Tooltip';
-import ChannelIcon from '../../../components/common/ChannelIcon';
 import Markdown from '../../../components/markdown/Markdown';
+import UserIcon from '../../../components/common/user/UserIcon';
+import ChannelIcon from '../../../components/common/ChannelIcon';
+import UserStatus from '../../../components/common/user/UserStatus';
+import { Mail, Edit, UserPlus, Shield } from "@styled-icons/feather";
+import { useChannels, useForceUpdate, useUsers } from "../../revoltjs/hooks";
+import { useContext, useEffect, useLayoutEffect, useState } from "preact/hooks";
+import { AppContext, ClientStatus, StatusContext } from "../../revoltjs/RevoltClient";
 
 interface Props {
     user_id: string;
diff --git a/src/context/revoltjs/CheckAuth.tsx b/src/context/revoltjs/CheckAuth.tsx
index f084e93..1951985 100644
--- a/src/context/revoltjs/CheckAuth.tsx
+++ b/src/context/revoltjs/CheckAuth.tsx
@@ -1,12 +1,12 @@
-import { ReactNode } from "react";
 import { useContext } from "preact/hooks";
 import { Redirect } from "react-router-dom";
+import { Children } from "../../types/Preact";
 
 import { OperationsContext } from "./RevoltClient";
 
 interface Props {
     auth?: boolean;
-    children: ReactNode | ReactNode[];
+    children: Children;
 }
 
 export const CheckAuth = (props: Props) => {
diff --git a/src/context/revoltjs/hooks.ts b/src/context/revoltjs/hooks.ts
index bb9fae1..e5e25a8 100644
--- a/src/context/revoltjs/hooks.ts
+++ b/src/context/revoltjs/hooks.ts
@@ -11,8 +11,17 @@ export interface HookContext {
 export function useForceUpdate(context?: HookContext): HookContext {
     const client = useContext(AppContext);
     if (context) return context;
-    const [, updateState] = useState({});
-    return { client, forceUpdate: useCallback(() => updateState({}), []) };
+    const H = useState(undefined);
+    var updateState: (_: undefined) => void;
+    if (Array.isArray(H)) {
+        let [, u] = H;
+        updateState = u;
+    } else {
+        console.warn('Failed to construct using useState.');
+        console.warn(H);
+        updateState = ()=>{};
+    }
+    return { client, forceUpdate: useCallback(() => updateState(undefined), []) };
 }
 
 function useObject(type: string, id?: string | string[], context?: HookContext) {
diff --git a/src/lib/defer.ts b/src/lib/defer.ts
new file mode 100644
index 0000000..2ad2d46
--- /dev/null
+++ b/src/lib/defer.ts
@@ -0,0 +1,3 @@
+export function defer(cb: () => void) {
+    setTimeout(cb, 0);
+}
diff --git a/src/pages/channels/Channel.tsx b/src/pages/channels/Channel.tsx
index 4c731ce..5e4ec84 100644
--- a/src/pages/channels/Channel.tsx
+++ b/src/pages/channels/Channel.tsx
@@ -4,6 +4,7 @@ import Header from "../../components/ui/Header";
 import { useRenderState } from "../../lib/renderer/Singleton";
 import { useChannel, useForceUpdate, useUsers } from "../../context/revoltjs/hooks";
 import { MessageArea } from "./messaging/MessageArea";
+import MessageBox from "../../components/common/messaging/MessageBox";
 
 const ChannelMain = styled.div`
     flex-grow: 1;
@@ -37,6 +38,7 @@ export default function Channel() {
             <ChannelMain>
                 <ChannelContent>
                     <MessageArea id={id} />
+                    <MessageBox channel={channel} />
                 </ChannelContent>
             </ChannelMain>
         </>
diff --git a/src/pages/friends/Friend.tsx b/src/pages/friends/Friend.tsx
index d27ee85..bcacd29 100644
--- a/src/pages/friends/Friend.tsx
+++ b/src/pages/friends/Friend.tsx
@@ -4,12 +4,12 @@ import styles from "./Friend.module.scss";
 import { useContext } from "preact/hooks";
 import { Children } from "../../types/Preact";
 import { X, Plus, Mail } from "@styled-icons/feather";
-import UserIcon from "../../components/common/UserIcon";
 import IconButton from "../../components/ui/IconButton";
 import { attachContextMenu } from "preact-context-menu";
 import { User, Users } from "revolt.js/dist/api/objects";
-import UserStatus from '../../components/common/UserStatus';
 import { stopPropagation } from "../../lib/stopPropagation";
+import UserIcon from "../../components/common/user/UserIcon";
+import UserStatus from '../../components/common/user/UserStatus';
 import { AppContext } from "../../context/revoltjs/RevoltClient";
 import { useIntermediate } from "../../context/intermediate/Intermediate";
 
diff --git a/src/pages/settings/Settings.module.scss b/src/pages/settings/Settings.module.scss
index 9497a95..dcb24eb 100644
--- a/src/pages/settings/Settings.module.scss
+++ b/src/pages/settings/Settings.module.scss
@@ -185,25 +185,3 @@
         margin: auto;
     }
 }
-
-.textarea {
-    margin-bottom: 1em;
-    border-radius: 4px;
-    font-family: 'Courier New', Courier, monospace;
-
-    textarea {
-        resize: none;
-        padding: 12px;
-        min-height: 180px;
-        border-radius: 4px;
-        color: var(--foreground);
-        border: 2px solid transparent;
-        background: var(--secondary-background);
-        transition: border-color .2s ease-in-out;
-
-        &:focus {
-            outline: none;
-            border: 2px solid var(--accent);
-        }
-    }
-}
diff --git a/src/pages/settings/SettingsTextArea.tsx b/src/pages/settings/SettingsTextArea.tsx
deleted file mode 100644
index 2cf4a64..0000000
--- a/src/pages/settings/SettingsTextArea.tsx
+++ /dev/null
@@ -1,6 +0,0 @@
-import styles from "./Settings.module.scss";
-import { TextArea, TextAreaProps } from "../../components/ui/TextArea";
-
-export function SettingsTextArea(props: TextAreaProps) {
-    return <TextArea {...props} className={styles.textarea} padding={16} />;
-}
diff --git a/src/pages/settings/channel/Overview.tsx b/src/pages/settings/channel/Overview.tsx
index 22eedca..a73a3fd 100644
--- a/src/pages/settings/channel/Overview.tsx
+++ b/src/pages/settings/channel/Overview.tsx
@@ -3,7 +3,7 @@ import styles from "./Panes.module.scss";
 import Button from "../../../components/ui/Button";
 import { Channels } from "revolt.js/dist/api/objects";
 import InputBox from "../../../components/ui/InputBox";
-import { SettingsTextArea } from "../SettingsTextArea";
+import TextArea from "../../../components/ui/TextArea";
 import { useContext, useEffect, useState } from "preact/hooks";
 import { AppContext } from "../../../context/revoltjs/RevoltClient";
 import { FileUploader } from "../../../context/revoltjs/FileUploads";
@@ -70,14 +70,14 @@ export function Overview({ channel }: Props) {
                     <Text id="app.main.groups.description" /> :
                     <Text id="app.main.servers.channel_description" /> }
             </h3>
-            <SettingsTextArea
-                maxRows={10}
-                minHeight={60}
+            <TextArea
+                // maxRows={10}
+                // minHeight={60}
                 maxLength={1024}
                 value={description}
                 placeholder={"Add a description..."}
-                onChange={content => {
-                    setDescription(content);
+                onChange={ev => {
+                    setDescription(ev.currentTarget.value);
                     if (!changed) setChanged(true)
                 }}
             />
diff --git a/src/pages/settings/panes/Account.tsx b/src/pages/settings/panes/Account.tsx
index 6147c25..b3f7e61 100644
--- a/src/pages/settings/panes/Account.tsx
+++ b/src/pages/settings/panes/Account.tsx
@@ -6,11 +6,11 @@ import { Users } from "revolt.js/dist/api/objects";
 import { Link, useHistory } from "react-router-dom";
 import Overline from "../../../components/ui/Overline";
 import { AtSign, Key, Mail } from "@styled-icons/feather";
-import { useForceUpdate, useSelf } from "../../../context/revoltjs/hooks";
-import UserIcon from "../../../components/common/UserIcon";
 import { useContext, useEffect, useState } from "preact/hooks";
-import { ClientStatus, StatusContext } from "../../../context/revoltjs/RevoltClient";
+import UserIcon from "../../../components/common/user/UserIcon";
+import { useForceUpdate, useSelf } from "../../../context/revoltjs/hooks";
 import { useIntermediate } from "../../../context/intermediate/Intermediate";
+import { ClientStatus, StatusContext } from "../../../context/revoltjs/RevoltClient";
 
 export function Account() {
     const { openScreen } = useIntermediate();
diff --git a/src/pages/settings/panes/Appearance.tsx b/src/pages/settings/panes/Appearance.tsx
index a746614..4fa442e 100644
--- a/src/pages/settings/panes/Appearance.tsx
+++ b/src/pages/settings/panes/Appearance.tsx
@@ -2,8 +2,8 @@ import { Text } from "preact-i18n";
 import styles from "./Panes.module.scss";
 import { debounce } from "../../../lib/debounce";
 import Button from "../../../components/ui/Button";
+import TextArea from "../../../components/ui/TextArea";
 import InputBox from "../../../components/ui/InputBox";
-import { SettingsTextArea } from "../SettingsTextArea";
 import { connectState } from "../../../redux/connector";
 import { WithDispatcher } from "../../../redux/reducers";
 import ColourSwatches from "../../../components/ui/ColourSwatches";
@@ -267,11 +267,12 @@ export function Component(props: Props & WithDispatcher) {
                 <h3>
                     <Text id="app.settings.pages.appearance.custom_css" />
                 </h3>
-                <SettingsTextArea
-                    maxRows={20}
-                    minHeight={480}
+                <TextArea
+                    // maxRows={20}
+                    // minHeight={480}
+                    code
                     value={css}
-                    onChange={css => setCSS(css)}
+                    onChange={ev => setCSS(ev.currentTarget.value)}
                 />
             </details>
 
diff --git a/src/pages/settings/panes/Feedback.tsx b/src/pages/settings/panes/Feedback.tsx
index bda00cd..a54c087 100644
--- a/src/pages/settings/panes/Feedback.tsx
+++ b/src/pages/settings/panes/Feedback.tsx
@@ -4,7 +4,7 @@ import { Localizer, Text } from "preact-i18n";
 import Radio from "../../../components/ui/Radio";
 import Button from "../../../components/ui/Button";
 import InputBox from "../../../components/ui/InputBox";
-import { SettingsTextArea } from "../SettingsTextArea";
+import TextArea from "../../../components/ui/TextArea";
 import { useSelf } from "../../../context/revoltjs/hooks";
 
 export function Feedback() {
@@ -80,12 +80,12 @@ export function Feedback() {
             <h3>
                 <Text id="app.settings.pages.feedback.describe" />
             </h3>
-            <SettingsTextArea
-                maxRows={10}
+            <TextArea
+                // maxRows={10}
                 value={description}
                 id="entry.685672624"
                 disabled={state === "sending"}
-                onChange={value => setDescription(value)}
+                onChange={ev => setDescription(ev.currentTarget.value)}
             />
             <Button type="submit" contrast>
                 <Text id="app.settings.pages.feedback.send" />
diff --git a/src/pages/settings/panes/Profile.tsx b/src/pages/settings/panes/Profile.tsx
index c87287f..aab0ae0 100644
--- a/src/pages/settings/panes/Profile.tsx
+++ b/src/pages/settings/panes/Profile.tsx
@@ -1,7 +1,7 @@
 import styles from "./Panes.module.scss";
 import Button from "../../../components/ui/Button";
 import { Users } from "revolt.js/dist/api/objects";
-import { SettingsTextArea } from "../SettingsTextArea";
+import TextArea from "../../../components/ui/TextArea";
 import { IntlContext, Text, translate } from "preact-i18n";
 import { useContext, useEffect, useState } from "preact/hooks";
 import { FileUploader } from "../../../context/revoltjs/FileUploads";
@@ -93,14 +93,14 @@ export function Profile() {
             <h3>
                 <Text id="app.settings.pages.profile.info" />
             </h3>
-            <SettingsTextArea
-                maxRows={10}
-                minHeight={200}
+            <TextArea
+                // maxRows={10}
+                // minHeight={200}
                 maxLength={2000}
                 value={profile?.content ?? ""}
                 disabled={typeof profile === "undefined"}
-                onChange={content => {
-                    setProfile({ ...profile, content })
+                onChange={ev => {
+                    setProfile({ ...profile, content: ev.currentTarget.value })
                     if (!changed) setChanged(true)
                 }}
                 placeholder={translate(
diff --git a/src/pages/settings/server/Invites.tsx b/src/pages/settings/server/Invites.tsx
index c1bca3d..42144a9 100644
--- a/src/pages/settings/server/Invites.tsx
+++ b/src/pages/settings/server/Invites.tsx
@@ -2,8 +2,8 @@ import styles from './Panes.module.scss';
 import { XCircle } from "@styled-icons/feather";
 import { useEffect, useState } from "preact/hooks";
 import Preloader from "../../../components/ui/Preloader";
-import UserIcon from "../../../components/common/UserIcon";
 import IconButton from "../../../components/ui/IconButton";
+import UserIcon from "../../../components/common/user/UserIcon";
 import { getChannelName } from "../../../context/revoltjs/util";
 import { Invites as InvitesNS, Servers } from "revolt.js/dist/api/objects";
 import { useChannels, useForceUpdate, useUsers } from "../../../context/revoltjs/hooks";
diff --git a/src/pages/settings/server/Overview.tsx b/src/pages/settings/server/Overview.tsx
index d3ba3a3..3c8e2ec 100644
--- a/src/pages/settings/server/Overview.tsx
+++ b/src/pages/settings/server/Overview.tsx
@@ -2,7 +2,7 @@ import { Text } from "preact-i18n";
 import styles from './Panes.module.scss';
 import Button from "../../../components/ui/Button";
 import { Servers } from "revolt.js/dist/api/objects";
-import { SettingsTextArea } from "../SettingsTextArea";
+import TextArea from "../../../components/ui/TextArea";
 import InputBox from "../../../components/ui/InputBox";
 import { useContext, useEffect, useState } from "preact/hooks";
 import { AppContext } from "../../../context/revoltjs/RevoltClient";
@@ -65,14 +65,14 @@ export function Overview({ server }: Props) {
             <h3>
                 <Text id="app.main.servers.description" />
             </h3>
-            <SettingsTextArea
-                maxRows={10}
-                minHeight={60}
+            <TextArea
+                // maxRows={10}
+                // minHeight={60}
                 maxLength={1024}
                 value={description}
                 placeholder={"Add a topic..."}
-                onChange={content => {
-                    setDescription(content);
+                onChange={ev => {
+                    setDescription(ev.currentTarget.value);
                     if (!changed) setChanged(true)
                 }}
             />
-- 
GitLab