From 8e205268443f178a79526cf936fabf787691ec5d Mon Sep 17 00:00:00 2001
From: Sophie Zhang <sophie_zhang@brown.edu>
Date: Fri, 7 Jul 2023 21:39:38 -0400
Subject: api key, with mui

---
 src/client/views/MainView.tsx | 96 ++++++++++++++++++++++---------------------
 1 file changed, 50 insertions(+), 46 deletions(-)

(limited to 'src/client/views/MainView.tsx')

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index ab2e0f7c5..4557e5866 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -65,6 +65,8 @@ import { PreviewCursor } from './PreviewCursor';
 import { PropertiesView } from './PropertiesView';
 import { DashboardStyleProvider, DefaultStyleProvider } from './StyleProvider';
 import { TopBar } from './topbar/TopBar';
+import { ThemeProvider } from '@mui/material';
+import { theme } from '../theme';
 const _global = (window /* browser */ || global) /* node */ as any;
 
 @observer
@@ -962,38 +964,39 @@ export class MainView extends React.Component {
 
     render() {
         return (
-            <div
-                className={`mainView-container${this.colorScheme}`}
-                onScroll={() => (ele => (ele.scrollTop = ele.scrollLeft = 0))(document.getElementById('root')!)}
-                ref={r => {
-                    r &&
-                        new _global.ResizeObserver(
-                            action(() => {
-                                this._windowWidth = r.getBoundingClientRect().width;
-                                this._windowHeight = r.getBoundingClientRect().height;
-                            })
-                        ).observe(r);
-                }}>
-                {this.inkResources}
-                <DictationOverlay />
-                <SharingManager />
-                <ServerStats />
-                <RTFMarkup />
-                <SettingsManager />
-                <ReportManager />
-                <CaptureManager />
-                <GroupManager />
-                <GoogleAuthenticationManager />
-                <DocumentDecorations boundsLeft={this.leftScreenOffsetOfMainDocView} boundsTop={this.topOfSidebarDoc} PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} />
-                <ComponentDecorations boundsLeft={this.leftScreenOffsetOfMainDocView} boundsTop={this.topOfMainDocContent} />
-                {this._hideUI ? null : <TopBar />}
-                {LinkDescriptionPopup.descriptionPopup ? <LinkDescriptionPopup /> : null}
-                {DocumentLinksButton.LinkEditorDocView ? <LinkMenu clearLinkEditor={action(() => (DocumentLinksButton.LinkEditorDocView = undefined))} docView={DocumentLinksButton.LinkEditorDocView} /> : null}
-                {this.linkDocPreview}
+            <ThemeProvider theme={theme}>
+                <div
+                    className={`mainView-container${this.colorScheme}`}
+                    onScroll={() => (ele => (ele.scrollTop = ele.scrollLeft = 0))(document.getElementById('root')!)}
+                    ref={r => {
+                        r &&
+                            new _global.ResizeObserver(
+                                action(() => {
+                                    this._windowWidth = r.getBoundingClientRect().width;
+                                    this._windowHeight = r.getBoundingClientRect().height;
+                                })
+                            ).observe(r);
+                    }}>
+                    {this.inkResources}
+                    <DictationOverlay />
+                    <SharingManager />
+                    <ServerStats />
+                    <RTFMarkup />
+                    <SettingsManager />
+                    <ReportManager />
+                    <CaptureManager />
+                    <GroupManager />
+                    <GoogleAuthenticationManager />
+                    <DocumentDecorations boundsLeft={this.leftScreenOffsetOfMainDocView} boundsTop={this.topOfSidebarDoc} PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} />
+                    <ComponentDecorations boundsLeft={this.leftScreenOffsetOfMainDocView} boundsTop={this.topOfMainDocContent} />
+                    {this._hideUI ? null : <TopBar />}
+                    {LinkDescriptionPopup.descriptionPopup ? <LinkDescriptionPopup /> : null}
+                    {DocumentLinksButton.LinkEditorDocView ? <LinkMenu clearLinkEditor={action(() => (DocumentLinksButton.LinkEditorDocView = undefined))} docView={DocumentLinksButton.LinkEditorDocView} /> : null}
+                    {this.linkDocPreview}
 
-                {((page: string) => {
-                    // prettier-ignore
-                    switch (page) {
+                    {((page: string) => {
+                        // prettier-ignore
+                        switch (page) {
                         default:
                         case 'dashboard': return (<>
                                         <div key="dashdiv" style={{ position: 'relative', display: this._hideUI || LightboxView.LightboxDoc ? 'none' : undefined, zIndex: 2001 }}>
@@ -1003,22 +1006,23 @@ export class MainView extends React.Component {
                                     </> );
                         case 'home': return <DashboardView />;
                     }
-                })(Doc.ActivePage)}
+                    })(Doc.ActivePage)}
 
-                <PreviewCursor />
-                <TaskCompletionBox />
-                <ContextMenu />
-                <RadialMenu />
-                <AnchorMenu />
-                <DashFieldViewMenu />
-                <MarqueeOptionsMenu />
-                <OverlayView />
-                <TimelineMenu />
-                <RichTextMenu />
-                <InkTranscription />
-                {this.snapLines}
-                <LightboxView key="lightbox" PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} maxBorder={[200, 50]} />
-            </div>
+                    <PreviewCursor />
+                    <TaskCompletionBox />
+                    <ContextMenu />
+                    <RadialMenu />
+                    <AnchorMenu />
+                    <DashFieldViewMenu />
+                    <MarqueeOptionsMenu />
+                    <OverlayView />
+                    <TimelineMenu />
+                    <RichTextMenu />
+                    <InkTranscription />
+                    {this.snapLines}
+                    <LightboxView key="lightbox" PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} maxBorder={[200, 50]} />
+                </div>
+            </ThemeProvider>
         );
     }
 }
-- 
cgit v1.2.3-70-g09d2


From b1f189ffc7dfe558d5895c8f0cb103ab3e5c17d7 Mon Sep 17 00:00:00 2001
From: Sophie Zhang <sophie_zhang@brown.edu>
Date: Mon, 10 Jul 2023 19:10:34 -0400
Subject: filters and better ui

---
 package-lock.json                                  |  59 --
 package.json                                       |   1 -
 src/client/theme.ts                                |   8 +
 src/client/util/ReportManager.scss                 | 409 ----------
 src/client/util/ReportManager.tsx                  | 548 -------------
 src/client/util/reportManager/ReportManager.scss   | 473 +++++++++++
 src/client/util/reportManager/ReportManager.tsx    | 570 +++++++++++++
 .../util/reportManager/ReportManagerComponents.tsx | 153 ++++
 .../util/reportManager/reportManagerSchema.ts      | 877 +++++++++++++++++++++
 .../util/reportManager/reportManagerUtils.ts       |  54 ++
 src/client/util/reportManagerSchema.ts             | 877 ---------------------
 src/client/views/MainView.tsx                      |   2 +-
 .../CollectionFreeFormLinkView.tsx                 |   3 +
 src/client/views/nodes/LoadingBox.scss             |   4 +
 src/client/views/topbar/TopBar.tsx                 |   2 +-
 15 files changed, 2144 insertions(+), 1896 deletions(-)
 delete mode 100644 src/client/util/ReportManager.scss
 delete mode 100644 src/client/util/ReportManager.tsx
 create mode 100644 src/client/util/reportManager/ReportManager.scss
 create mode 100644 src/client/util/reportManager/ReportManager.tsx
 create mode 100644 src/client/util/reportManager/ReportManagerComponents.tsx
 create mode 100644 src/client/util/reportManager/reportManagerSchema.ts
 create mode 100644 src/client/util/reportManager/reportManagerUtils.ts
 delete mode 100644 src/client/util/reportManagerSchema.ts

(limited to 'src/client/views/MainView.tsx')

diff --git a/package-lock.json b/package-lock.json
index 5b0c4e2f6..fe09f09bb 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -19903,55 +19903,6 @@
             "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
             "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
         },
-        "react-loader-spinner": {
-            "version": "5.3.4",
-            "resolved": "https://registry.npmjs.org/react-loader-spinner/-/react-loader-spinner-5.3.4.tgz",
-            "integrity": "sha512-G2vw4ssX+RDZ/vfaeva06yfNqyFViv/u+tVZ3kFLy5TKNlNx2DbuwreBSpRtPespQA+VxinxUJsigwLwG9erOg==",
-            "requires": {
-                "react-is": "^18.2.0",
-                "styled-components": "^5.3.5",
-                "styled-tools": "^1.7.2"
-            },
-            "dependencies": {
-                "css-to-react-native": {
-                    "version": "3.2.0",
-                    "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz",
-                    "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==",
-                    "requires": {
-                        "camelize": "^1.0.0",
-                        "css-color-keywords": "^1.0.0",
-                        "postcss-value-parser": "^4.0.2"
-                    }
-                },
-                "postcss-value-parser": {
-                    "version": "4.2.0",
-                    "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
-                    "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
-                },
-                "react-is": {
-                    "version": "18.2.0",
-                    "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
-                    "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
-                },
-                "styled-components": {
-                    "version": "5.3.11",
-                    "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.11.tgz",
-                    "integrity": "sha512-uuzIIfnVkagcVHv9nE0VPlHPSCmXIUGKfJ42LNjxCCTDTL5sgnJ8Z7GZBq0EnLYGln77tPpEpExt2+qa+cZqSw==",
-                    "requires": {
-                        "@babel/helper-module-imports": "^7.0.0",
-                        "@babel/traverse": "^7.4.5",
-                        "@emotion/is-prop-valid": "^1.1.0",
-                        "@emotion/stylis": "^0.8.4",
-                        "@emotion/unitless": "^0.7.4",
-                        "babel-plugin-styled-components": ">= 1.12.0",
-                        "css-to-react-native": "^3.0.0",
-                        "hoist-non-react-statics": "^3.0.0",
-                        "shallowequal": "^1.1.0",
-                        "supports-color": "^5.5.0"
-                    }
-                }
-            }
-        },
         "react-loading": {
             "version": "2.0.3",
             "resolved": "https://registry.npmjs.org/react-loading/-/react-loading-2.0.3.tgz",
@@ -21287,11 +21238,6 @@
             "resolved": "https://registry.npmjs.org/shallow-equal/-/shallow-equal-1.2.1.tgz",
             "integrity": "sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA=="
         },
-        "shallowequal": {
-            "version": "1.1.0",
-            "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
-            "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ=="
-        },
         "sharp": {
             "version": "0.23.4",
             "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.23.4.tgz",
@@ -22309,11 +22255,6 @@
                 }
             }
         },
-        "styled-tools": {
-            "version": "1.7.2",
-            "resolved": "https://registry.npmjs.org/styled-tools/-/styled-tools-1.7.2.tgz",
-            "integrity": "sha512-IjLxzM20RMwAsx8M1QoRlCG/Kmq8lKzCGyospjtSXt/BTIIcvgTonaxQAsKnBrsZNwhpHzO9ADx5te0h76ILVg=="
-        },
         "stylis": {
             "version": "4.1.3",
             "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.1.3.tgz",
diff --git a/package.json b/package.json
index 39f70ab66..97701913e 100644
--- a/package.json
+++ b/package.json
@@ -292,7 +292,6 @@
         "react-grid-layout": "^1.3.4",
         "react-icons": "^4.3.1",
         "react-jsx-parser": "^1.29.0",
-        "react-loader-spinner": "^5.3.4",
         "react-loading": "^2.0.3",
         "react-markdown": "^8.0.3",
         "react-measure": "^2.5.2",
diff --git a/src/client/theme.ts b/src/client/theme.ts
index f0a11fe6e..0ef2e5e2c 100644
--- a/src/client/theme.ts
+++ b/src/client/theme.ts
@@ -19,5 +19,13 @@ export const theme = createTheme({
                 },
             },
         },
+
+        MuiTooltip: {
+            styleOverrides: {
+                tooltip: {
+                    fontSize: '12px',
+                },
+            },
+        },
     },
 });
diff --git a/src/client/util/ReportManager.scss b/src/client/util/ReportManager.scss
deleted file mode 100644
index b3bd998e6..000000000
--- a/src/client/util/ReportManager.scss
+++ /dev/null
@@ -1,409 +0,0 @@
-@import '../views/global/globalCssVariables';
-
-// header
-
-.report-header {
-    display: flex;
-    justify-content: space-between;
-    align-items: center;
-
-    h2 {
-        margin: 0;
-        padding: 0;
-        font-size: 24px;
-    }
-}
-
-.report-header-vertical {
-    display: flex;
-    flex-direction: column;
-    align-items: flex-start;
-    gap: 4px;
-
-    h2 {
-        margin: 0;
-        padding: 0;
-        padding-bottom: 8px;
-        font-size: 24px;
-    }
-}
-
-// Report
-
-.report-issue {
-    width: 450px;
-    min-width: 300px;
-    padding: 16px;
-    padding-top: 32px;
-    display: flex;
-    flex-direction: column;
-    gap: 16px;
-    background-color: #ffffff;
-    text-align: left;
-    position: relative;
-
-    .report-label {
-        font-size: 14px;
-        font-weight: 400;
-        color: #747474;
-    }
-
-    .report-section {
-        display: flex;
-        flex-direction: column;
-    }
-
-    .report-textarea {
-        width: 100%;
-        height: 80px;
-        padding: 8px;
-        resize: none;
-    }
-
-    .report-selects {
-        display: flex;
-        flex-direction: column;
-        align-items: stretch;
-        gap: 16px;
-
-        .report-select {
-            padding: 8px;
-            border-color: #c6c6c6;
-
-            .report-opt {
-                padding: 8px;
-            }
-        }
-    }
-}
-
-.report-input {
-    border: none;
-    outline: none;
-    border-bottom: 1px solid #c6c6c6;
-    padding: 8px;
-    padding-left: 0;
-    transition: all 0.2s ease;
-
-    &:hover {
-        border-bottom-color: #7f7f7f;
-    }
-    &:focus {
-        border-bottom-color: #4476f7;
-    }
-}
-
-// View issues
-
-.view-issues {
-    width: 65vw;
-    min-width: 500px;
-    display: flex;
-    gap: 16px;
-    height: 100%;
-
-    .left {
-        height: 100%;
-        flex: 1;
-        padding: 16px;
-        display: flex;
-        flex-direction: column;
-        gap: 16px;
-        background-color: #ffffff;
-        text-align: left;
-        position: relative;
-
-        .issues {
-            position: relative;
-            flex-grow: 1;
-            overflow-y: auto;
-            overflow-x: hidden;
-            display: flex;
-            flex-direction: column;
-            gap: 16px;
-        }
-    }
-
-    .right {
-        padding: 16px;
-        height: 100%;
-        overflow-y: auto;
-        display: flex;
-        flex-direction: column;
-        justify-content: center;
-        align-items: center;
-        flex: 1;
-    }
-}
-
-// video
-
-.default-video::-webkit-media-controls {
-    display: block !important;
-}
-
-// Issue
-
-.issue {
-    cursor: pointer;
-    padding: 16px;
-    background-color: #ffffff;
-    border: 1px solid #d3d3d3;
-    transition: all 0.1s ease;
-    display: flex;
-    flex-direction: column;
-    gap: 8px;
-    border-radius: 8px;
-    transition: all 0.2s ease;
-    // box-shadow: 0 0 8px #d0d0d07c;
-
-    .issue-label {
-        cursor: pointer;
-        font-size: 14px;
-        font-weight: 400;
-        color: #7f7f7f;
-    }
-
-    .issue-title {
-        font-size: 16px;
-        font-weight: 500;
-        padding: 0;
-        margin: 0;
-        color: #4476f7;
-    }
-
-    &:hover {
-        background-color: #4476f7;
-        border-color: #4476f7;
-        color: #ffffff;
-
-        .issue-label {
-            color: #ffffff;
-        }
-
-        .issue-title {
-            color: #ffffff;
-        }
-    }
-}
-
-// Dropzone
-
-.dropzone {
-    padding: 2rem;
-    border-radius: 0.5rem;
-    border: 2px dashed #ebebeb;
-
-    .dropzone-instructions {
-        display: flex;
-        flex-direction: column;
-        align-items: center;
-        gap: 16px;
-        color: #7f7f7f;
-
-        p {
-            text-align: center;
-        }
-    }
-}
-
-.file-list {
-    box-sizing: border-box;
-    margin: 0;
-    padding: 0;
-    font-size: 14px;
-    color: #7f7f7f;
-    width: 100%;
-    overflow-x: auto;
-    list-style-type: none;
-    display: flex;
-    gap: 16px;
-
-    .file-name {
-        padding: 8px 12px;
-        display: flex;
-        align-items: center;
-        gap: 16px;
-        white-space: nowrap;
-    }
-}
-
-// Detailed issue view
-
-.issue-view {
-    height: 100%;
-    width: 100%;
-    display: flex;
-    flex-direction: column;
-    gap: 16px;
-    background-color: #ffffff;
-    text-align: left;
-    position: relative;
-    overflow: auto;
-
-    .issue-label {
-        color: #7f7f7f;
-
-        .issue-link {
-            cursor: pointer;
-            color: #4476f7;
-        }
-    }
-
-    .issue-title {
-        font-size: 24px;
-        margin: 0;
-        padding: 0;
-    }
-
-    .issue-content {
-        font-size: 14px;
-        color: #7f7f7f;
-    }
-}
-
-// Media previews
-
-.report-media-wrapper {
-    position: relative;
-
-    .close-btn {
-        position: absolute;
-        top: 2px;
-        right: 2px;
-        opacity: 0;
-    }
-
-    .report-media-content {
-        position: relative;
-        display: inline block;
-        cursor: pointer;
-    }
-
-    .report-media-content::after {
-        content: '';
-        position: absolute;
-        top: 0;
-        left: 0;
-        width: 100%;
-        height: 100%;
-        background-color: rgba(0, 0, 0, 0.5); /* Adjust the opacity as desired */
-        opacity: 0;
-        transition: opacity 0.3s ease; /* Transition for smooth effect */
-    }
-
-    &:hover {
-        .report-media-content::after {
-            opacity: 1;
-        }
-
-        .close-btn {
-            opacity: 1;
-        }
-    }
-}
-
-.report-audio-wrapper {
-    display: flex;
-    align-items: center;
-    gap: 8px;
-}
-
-@media (max-width: 1100px) {
-    .report-header {
-        flex-direction: column;
-        align-items: stretch;
-        gap: 2rem;
-    }
-}
-
-// Old code
-
-// <---------------------------------------------------------------------------->
-
-// .issue-list-wrapper {
-//     position: relative;
-//     min-width: 250px;
-//     background-color: $light-blue;
-//     overflow-y: scroll;
-// }
-
-// .issue-list {
-//     display: flex;
-//     align-items: center;
-//     justify-content: space-between;
-//     padding: 5px;
-//     margin: 5px;
-//     border-radius: 5px;
-//     border: 1px solid grey;
-//     background-color: lightgoldenrodyellow;
-// }
-
-// // issue should pop up when the user hover over the issue
-// .issue-list:hover {
-//     box-shadow: 2px;
-//     cursor: pointer;
-//     border: 3px solid #252b33;
-// }
-
-// .issue-content {
-//     background-color: white;
-//     padding: 10px;
-//     flex: 1 1 auto;
-//     overflow-y: scroll;
-// }
-
-// .issue-title {
-//     font-size: 20px;
-//     font-weight: 600;
-//     color: black;
-// }
-
-// .issue-body {
-//     padding: 0 10px;
-//     width: 100%;
-//     text-align: left;
-// }
-
-// .issue-body > * {
-//     margin-top: 5px;
-// }
-
-// .issue-body img,
-// .issue-body video {
-//     display: block;
-//     max-width: 100%;
-// }
-
-// .report-issue-fab {
-//     position: fixed;
-//     bottom: 20px;
-//     right: 20px;
-//     display: flex;
-//     align-items: center;
-//     justify-content: center;
-//     cursor: pointer;
-// }
-
-// .loading-center {
-//     margin: auto 0;
-// }
-
-// .settings-content label {
-//     margin-top: 10px;
-// }
-
-// .report-disclaimer {
-//     font-size: 8px;
-//     color: grey;
-//     padding-right: 50px;
-//     font-style: italic;
-//     text-align: left;
-// }
-
-// .flex-select {
-//     display: flex;
-//     align-items: center;
-//     justify-content: center;
-//     gap: 10px;
-// }
diff --git a/src/client/util/ReportManager.tsx b/src/client/util/ReportManager.tsx
deleted file mode 100644
index 125d20876..000000000
--- a/src/client/util/ReportManager.tsx
+++ /dev/null
@@ -1,548 +0,0 @@
-import { ColorState, SketchPicker } from 'react-color';
-import { Id } from '../../fields/FieldSymbols';
-import { BoolCast, Cast, StrCast } from '../../fields/Types';
-import { addStyleSheet, addStyleSheetRule, Utils } from '../../Utils';
-import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager';
-import { DocServer } from '../DocServer';
-import { FontIconBox } from '../views/nodes/button/FontIconBox';
-import { DragManager } from './DragManager';
-import { GroupManager } from './GroupManager';
-import { CheckBox } from '../views/search/CheckBox';
-import { undoBatch } from './UndoManager';
-import * as React from 'react';
-import './SettingsManager.scss';
-import './ReportManager.scss';
-import { action, computed, observable, runInAction } from 'mobx';
-import { BsX } from 'react-icons/bs';
-import { BiX } from 'react-icons/bi';
-import { AiOutlineUpload } from 'react-icons/ai';
-import { HiOutlineArrowLeft } from 'react-icons/hi';
-import { Issue } from './reportManagerSchema';
-import { observer } from 'mobx-react';
-import { Doc } from '../../fields/Doc';
-import { Networking } from '../Network';
-import { MainViewModal } from '../views/MainViewModal';
-import { Octokit } from '@octokit/core';
-import { Button, IconButton } from '@mui/material';
-import { Oval } from 'react-loader-spinner';
-import Dropzone from 'react-dropzone';
-import ReactMarkdown from 'react-markdown';
-import rehypeRaw from 'rehype-raw';
-import remarkGfm from 'remark-gfm';
-import { theme } from '../theme';
-import v4 = require('uuid/v4');
-const higflyout = require('@hig/flyout');
-export const { anchorPoints } = higflyout;
-export const Flyout = higflyout.default;
-
-enum ViewState {
-    VIEW,
-    CREATE,
-}
-
-interface FileData {
-    _id: string;
-    file: File;
-}
-
-// Format reference: "https://browndash.com/files/images/upload_cb31bc0fda59c96ca14193ec494f80cf_o.jpg" />
-
-/**
- * Class for reporting and viewing Github issues within the app.
- */
-@observer
-export class ReportManager extends React.Component<{}> {
-    public static Instance: ReportManager;
-    @observable private isOpen = false;
-
-    @observable private query = '';
-    @action private setQuery = (q: string) => {
-        this.query = q;
-    };
-
-    private octokit: Octokit;
-
-    @observable viewState: ViewState = ViewState.VIEW;
-    @action private setViewState = (state: ViewState) => {
-        this.viewState = state;
-    };
-    @observable submitting: boolean = false;
-    @action private setSubmitting = (submitting: boolean) => {
-        this.submitting = submitting;
-    };
-
-    @observable fetchingIssues: boolean = false;
-    @action private setFetchingIssues = (fetching: boolean) => {
-        this.fetchingIssues = fetching;
-    };
-
-    @observable
-    public shownIssues: Issue[] = [];
-    @action setShownIssues = action((issues: Issue[]) => {
-        this.shownIssues = issues;
-    });
-
-    @observable selectedIssue: Issue | undefined = undefined;
-    @action setSelectedIssue = action((issue: Issue | undefined) => {
-        this.selectedIssue = issue;
-    });
-
-    // Form state
-
-    @observable private bugTitle = '';
-    @action setBugTitle = action((title: string) => {
-        this.bugTitle = title;
-    });
-    @observable private bugDescription = '';
-    @action setBugDescription = action((description: string) => {
-        this.bugDescription = description;
-    });
-    @observable private bugType = '';
-    @action setBugType = action((type: string) => {
-        this.bugType = type;
-    });
-    @observable private bugPriority = '';
-    @action setBugPriority = action((priortiy: string) => {
-        this.bugPriority = priortiy;
-    });
-
-    @observable private mediaFiles: FileData[] = [];
-    @action private setMediaFiles = (files: FileData[]) => {
-        this.mediaFiles = files;
-    };
-
-    public close = action(() => (this.isOpen = false));
-    public open = action(async () => {
-        this.isOpen = true;
-        if (this.shownIssues.length === 0) {
-            this.setFetchingIssues(true);
-            try {
-                // load in the issues if not already loaded
-                const issues = (await this.getAllIssues()) as Issue[];
-                this.setShownIssues(issues.filter(issue => issue.state === 'open' && !issue.pull_request));
-            } catch (err) {
-                console.log(err);
-            }
-            this.setFetchingIssues(false);
-        }
-    });
-
-    constructor(props: {}) {
-        super(props);
-        ReportManager.Instance = this;
-
-        // initializing Github connection
-        this.octokit = new Octokit({
-            auth: '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',
-        });
-    }
-
-    /**
-     * Fethches issues from Github.
-     * @returns array of all issues
-     */
-    public async getAllIssues(): Promise<any[]> {
-        const res = await this.octokit.request('GET /repos/{owner}/{repo}/issues', {
-            owner: 'brown-dash',
-            repo: 'Dash-Web',
-        });
-
-        // 200 status means success
-        if (res.status === 200) {
-            return res.data;
-        } else {
-            throw new Error('Error getting issues');
-        }
-    }
-
-    /**
-     * Sends a request to Github to report a new issue with the form data.
-     * @returns nothing
-     */
-    public async reportIssue(): Promise<void> {
-        if (this.bugTitle === '' || this.bugDescription === '' || this.bugType === '' || this.bugPriority === '') {
-            alert('Please fill out all required fields to report an issue.');
-            return;
-        }
-        this.setSubmitting(true);
-
-        const links = await this.uploadFilesToServer();
-        if (!links) {
-            // error uploading files to the server
-            return;
-        }
-        const formattedLinks = (links ?? []).map(this.fileLinktoServerLink);
-
-        const req = await this.octokit.request('POST /repos/{owner}/{repo}/issues', {
-            owner: 'brown-dash',
-            repo: 'Dash-Web',
-            title: this.formatTitle(this.bugTitle, Doc.CurrentUserEmail),
-            body: `${this.bugDescription} ${formattedLinks.length > 0 && `\n\nFiles:\n${formattedLinks.join('\n')}`}`,
-            labels: ['from-dash-app', this.bugType, this.bugPriority],
-        });
-
-        // 201 status means success
-        if (req.status !== 201) {
-            alert('Error creating issue on github.');
-            return;
-        }
-
-        // Reset fields
-        this.setBugTitle('');
-        this.setBugDescription('');
-        this.setMediaFiles([]);
-        this.setBugType('');
-        this.setBugPriority('');
-        this.setSubmitting(false);
-        alert('Successfully submitted issue.');
-        // this.close();
-    }
-
-    /**
-     * Formats issue title.
-     *
-     * @param title title of issue
-     * @param userEmail email of issue submitter
-     * @returns formatted title
-     */
-    private formatTitle = (title: string, userEmail: string): string => `${title} - ${userEmail.replace('@brown.edu', '')}`;
-
-    // turns an upload link into a servable link
-    // ex:
-    // C: /Users/dash/Documents/GitHub/Dash-Web/src/server/public/files/images/upload_8008dbc4b6424fbff14da7345bb32eb2.png
-    // -> https://browndash.com/files/images/upload_8008dbc4b6424fbff14da7345bb32eb2_l.png
-    private fileLinktoServerLink = (fileLink: string) => {
-        const serverUrl = 'https://browndash.com/';
-
-        const regex = 'public';
-        const publicIndex = fileLink.indexOf(regex) + regex.length;
-
-        const finalUrl = `${serverUrl}${fileLink.substring(publicIndex + 1).replace('.', '_l.')}`;
-        return finalUrl;
-    };
-
-    /**
-     * Gets the server file path.
-     *
-     * @param link response from file upload
-     * @returns server file path
-     */
-    private getServerPath = (link: any): string => {
-        return link.result.accessPaths.agnostic.server as string;
-    };
-
-    /**
-     * Uploads media files to the server.
-     * @returns the server paths or undefined on error
-     */
-    private uploadFilesToServer = async (): Promise<string[] | undefined> => {
-        try {
-            const links = await Networking.UploadFilesToServer(
-                this.mediaFiles.map(file => ({ file: file.file })),
-                true
-            );
-            return (links ?? []).map(this.getServerPath);
-        } catch (err) {
-            if (err instanceof Error) {
-                alert(err.message);
-            } else {
-                alert(err);
-            }
-        }
-    };
-
-    /**
-     * Handles file upload.
-     * @param files uploaded files
-     */
-    private onDrop = (files: File[]) => {
-        this.setMediaFiles([...this.mediaFiles, ...files.map(file => ({ _id: v4(), file }))]);
-    };
-
-    /**
-     * Gets a JSX element to render a media preview
-     * @param fileData file data
-     * @returns JSX element of a piece of media (image, video, audio)
-     */
-    private getMediaPreview = (fileData: FileData): JSX.Element => {
-        const file = fileData.file;
-        const mimeType = file.type;
-        const preview = URL.createObjectURL(file);
-
-        if (mimeType.startsWith('image/')) {
-            return (
-                <div key={fileData._id} className="report-media-wrapper">
-                    <div className="report-media-content">
-                        <img height={50} alt={`Preview of ${file.name}`} src={preview} style={{ display: 'block' }} />
-                    </div>
-                    <IconButton onClick={() => this.setMediaFiles(this.mediaFiles.filter(f => f._id !== fileData._id))} className="close-btn">
-                        <BsX color="#ffffff" />
-                    </IconButton>
-                </div>
-            );
-        } else if (mimeType.startsWith('video/')) {
-            return (
-                <div key={fileData._id} className="report-media-wrapper">
-                    <div className="report-media-content">
-                        <video controls style={{ height: '50px', width: 'auto', display: 'block' }}>
-                            <source src={preview} type="video/mp4" />
-                            Your browser does not support the video tag.
-                        </video>
-                    </div>
-                    <IconButton onClick={() => this.setMediaFiles(this.mediaFiles.filter(f => f._id !== fileData._id))} className="close-btn">
-                        <BsX color="#ffffff" />
-                    </IconButton>
-                </div>
-            );
-        } else if (mimeType.startsWith('audio/')) {
-            return (
-                <div key={fileData._id} className="report-audio-wrapper">
-                    <audio src={preview} controls />
-                    <IconButton onClick={() => this.setMediaFiles(this.mediaFiles.filter(f => f._id !== fileData._id))} className="close-btn">
-                        <BsX />
-                    </IconButton>
-                </div>
-            );
-        }
-        return <></>;
-    };
-
-    /**
-     * @returns the component that dispays all issues
-     */
-    private viewIssuesComponent = () => {
-        return (
-            <div className="view-issues">
-                <div className="left">
-                    <div className="report-header">
-                        <h2>Open Issues</h2>
-                        <Button
-                            variant="contained"
-                            onClick={() => {
-                                this.setViewState(ViewState.CREATE);
-                            }}>
-                            report issue
-                        </Button>
-                    </div>
-                    <input
-                        className="report-input"
-                        type="text"
-                        placeholder="Filter..."
-                        onChange={e => {
-                            this.setQuery(e.target.value);
-                        }}
-                        required
-                    />
-                    <div className="issues">
-                        {this.fetchingIssues ? (
-                            <div style={{ flexGrow: 1, display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
-                                <Oval height={50} width={50} color={theme.palette.primary.main} visible={true} ariaLabel="oval-loading" secondaryColor={theme.palette.primary.main + 'b8'} strokeWidth={3} strokeWidthSecondary={3} />
-                            </div>
-                        ) : (
-                            this.shownIssues
-                                .filter(issue => issue.title.toLowerCase().includes(this.query))
-                                .map(issue => (
-                                    <IssueCard
-                                        key={issue.number}
-                                        issue={issue}
-                                        onSelect={() => {
-                                            this.setSelectedIssue(issue);
-                                        }}
-                                    />
-                                ))
-                        )}
-                    </div>
-                </div>
-                <div className="right">{this.selectedIssue ? <IssueView issue={this.selectedIssue} /> : <div>No issue selected</div>}</div>
-                <IconButton sx={{ position: 'absolute', top: '4px', right: '4px' }} onClick={this.close}>
-                    <BiX size="16px" />
-                </IconButton>
-            </div>
-        );
-    };
-
-    /**
-     * @returns the form component for submitting issues
-     */
-    private reportIssueComponent = () => {
-        return (
-            <div className="report-issue">
-                <div className="report-header-vertical">
-                    <Button
-                        variant="text"
-                        onClick={() => {
-                            this.setViewState(ViewState.VIEW);
-                        }}
-                        sx={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
-                        <HiOutlineArrowLeft color={theme.palette.primary.main} />
-                        back to view
-                    </Button>
-                    <h2>Report an Issue</h2>
-                </div>
-                <div className="report-section">
-                    <label className="report-label">Please provide a title for the bug</label>
-                    <input className="report-input" value={this.bugTitle} type="text" placeholder="Title..." onChange={e => this.setBugTitle(e.target.value)} required />
-                </div>
-                <div className="report-section">
-                    <label className="report-label">Please leave a description for the bug and how it can be recreated</label>
-                    <textarea className="report-textarea" value={this.bugDescription} placeholder="Description..." onChange={e => this.setBugDescription(e.target.value)} required />
-                </div>
-                <div className="report-selects">
-                    <select className="report-select" name="bugType" onChange={e => (this.bugType = e.target.value)}>
-                        <option value="" disabled selected>
-                            Type
-                        </option>
-                        <option className="report-opt" value="priority-low">
-                            Bug
-                        </option>
-                        <option className="report-opt" value="priority-medium">
-                            Poor Design or Cosmetic
-                        </option>
-                        <option className="report-opt" value="priority-high">
-                            Poor Documentation
-                        </option>
-                    </select>
-                    <select className="report-select" name="bigPriority" onChange={e => (this.bugPriority = e.target.value)}>
-                        <option className="report-opt" value="" disabled selected>
-                            Priority
-                        </option>
-                        <option value="priority-low">Low</option>
-                        <option value="priority-medium">Medium</option>
-                        <option value="priority-high">High</option>
-                    </select>
-                </div>
-                <Dropzone
-                    onDrop={this.onDrop}
-                    accept={{
-                        'image/*': ['.png', '.jpg', '.jpeg', '.gif'],
-                        'video/*': ['.mp4', '.mpeg', '.webm', '.mov'],
-                        'audio/mpeg': ['.mp3'],
-                        'audio/wav': ['.wav'],
-                        'audio/ogg': ['.ogg'],
-                    }}>
-                    {({ getRootProps, getInputProps }) => (
-                        <div {...getRootProps({ className: 'dropzone' })}>
-                            <input {...getInputProps()} />
-                            <div className="dropzone-instructions">
-                                <AiOutlineUpload size={25} />
-                                <p>Drop or select media that shows the bug (optional)</p>
-                            </div>
-                        </div>
-                    )}
-                </Dropzone>
-                {this.mediaFiles.length > 0 && <ul className="file-list">{this.mediaFiles.map(file => this.getMediaPreview(file))}</ul>}
-                <Button
-                    variant="contained"
-                    sx={{ fontSize: '14px', display: 'flex', alignItems: 'center', gap: '1rem' }}
-                    onClick={() => {
-                        this.reportIssue();
-                    }}>
-                    Submit
-                    {this.submitting && <Oval height={20} width={20} color="#ffffff" visible={true} ariaLabel="oval-loading" secondaryColor="#ffffff87" strokeWidth={3} strokeWidthSecondary={3} />}
-                </Button>
-                <IconButton sx={{ position: 'absolute', top: '4px', right: '4px' }} onClick={this.close}>
-                    <BiX size={'16px'} />
-                </IconButton>
-            </div>
-        );
-    };
-
-    /**
-     * @returns the component rendered to the modal
-     */
-    private reportComponent = () => {
-        if (this.viewState === ViewState.VIEW) {
-            return this.viewIssuesComponent();
-        } else {
-            return this.reportIssueComponent();
-        }
-    };
-
-    render() {
-        return (
-            <MainViewModal
-                contents={this.reportComponent()}
-                isDisplayed={this.isOpen}
-                interactive={true}
-                closeOnExternalClick={this.close}
-                dialogueBoxStyle={{ width: 'auto', minWidth: '300px', height: '85vh', maxHeight: '90vh', background: '#ffffff', borderRadius: '8px' }}
-            />
-        );
-    }
-}
-
-// Mini components to render issues
-
-interface IssueCardProps {
-    issue: Issue;
-    onSelect: () => void;
-}
-const IssueCard = ({ issue, onSelect }: IssueCardProps) => {
-    return (
-        <div className="issue" onClick={onSelect}>
-            <label className="issue-label">#{issue.number}</label>
-            <h3 className="issue-title">{issue.title}</h3>
-        </div>
-    );
-};
-
-interface IssueViewProps {
-    issue: Issue;
-}
-
-const IssueView = ({ issue }: IssueViewProps) => {
-    const parseBody = (body: string) => {
-        const imgTagRegex = /<img\b[^>]*\/?>/;
-        const fileRegex = /https:\/\/browndash\.com\/files/;
-        const parts = body.split('\n');
-
-        const modifiedParts = parts.map(part => {
-            if (imgTagRegex.test(part)) {
-                return getLinkFromTag(part);
-            } else if (fileRegex.test(part)) {
-                return getTagFromUrl(part);
-            } else {
-                return part;
-            }
-        });
-        return modifiedParts.join('\n');
-    };
-
-    const getLinkFromTag = (tag: string) => {
-        const regex = /src="([^"]+)"/;
-        let url = '';
-        const match = tag.match(regex);
-        if (match) {
-            url = match[1];
-        }
-
-        return `\n${url}`;
-    };
-
-    const getTagFromUrl = (url: string) => {
-        const imgRegex = /https:\/\/browndash\.com\/files[/\\]images/;
-        const videoRegex = /https:\/\/browndash\.com\/files[/\\]videos/;
-        const audioRegex = /https:\/\/browndash\.com\/files[/\\]audio/;
-
-        if (imgRegex.test(url)) {
-            return `${url}\n<img width="100%" alt="Issue asset" src=${url} />`;
-        } else if (videoRegex.test(url)) {
-            return url;
-        } else if (audioRegex.test(url)) {
-            return `${url}\n<audio src=${url} controls />`;
-        }
-    };
-
-    return (
-        <div className="issue-view">
-            <span className="issue-label">
-                Issue{' '}
-                <a className="issue-link" href={issue.html_url} target="_blank">
-                    #{issue.number}
-                </a>
-            </span>
-            <h2 className="issue-title">{issue.title}</h2>
-            <ReactMarkdown children={issue.body ? parseBody(issue.body as string) : ''} className="issue-content" linkTarget={'_blank'} remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeRaw]} />
-        </div>
-    );
-};
diff --git a/src/client/util/reportManager/ReportManager.scss b/src/client/util/reportManager/ReportManager.scss
new file mode 100644
index 000000000..81af41cb0
--- /dev/null
+++ b/src/client/util/reportManager/ReportManager.scss
@@ -0,0 +1,473 @@
+@import '../../views/global/globalCssVariables';
+
+// header
+
+$text-gray: #64748b;
+$outline-gray: #cbd5e1;
+
+.report-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+
+    h2 {
+        margin: 0;
+        padding: 0;
+        font-size: 24px;
+    }
+}
+
+.report-header-vertical {
+    display: flex;
+    flex-direction: column;
+    align-items: flex-start;
+    gap: 4px;
+
+    h2 {
+        margin: 0;
+        padding: 0;
+        padding-bottom: 8px;
+        font-size: 24px;
+    }
+}
+
+// Report
+
+.report-issue {
+    width: 450px;
+    min-width: 300px;
+    padding: 16px;
+    padding-top: 32px;
+    display: flex;
+    flex-direction: column;
+    gap: 16px;
+    background-color: #ffffff;
+    text-align: left;
+    position: relative;
+
+    .report-label {
+        font-size: 14px;
+        font-weight: 400;
+        color: $text-gray;
+    }
+
+    .report-section {
+        display: flex;
+        flex-direction: column;
+    }
+
+    .report-textarea {
+        width: 100%;
+        height: 80px;
+        padding: 8px;
+        resize: vertical;
+        // resize: none;
+    }
+
+    .report-selects {
+        display: flex;
+        flex-direction: column;
+        align-items: stretch;
+        gap: 16px;
+
+        .report-select {
+            padding: 8px;
+            border-color: $outline-gray;
+
+            .report-opt {
+                padding: 8px;
+            }
+        }
+    }
+}
+
+.report-input {
+    border: none;
+    outline: none;
+    border-bottom: 1px solid $outline-gray;
+    padding: 8px;
+    padding-left: 0;
+    transition: all 0.2s ease;
+
+    &:hover {
+        border-bottom-color: $text-gray;
+    }
+    &:focus {
+        border-bottom-color: #4476f7;
+    }
+}
+
+// View issues
+
+.view-issues {
+    width: 75vw;
+    min-width: 500px;
+    display: flex;
+    gap: 16px;
+    height: 100%;
+    overflow-x: auto;
+
+    video::-webkit-media-controls {
+        display: flex !important;
+    }
+
+    .left {
+        flex: 1;
+        height: 100%;
+        padding: 16px;
+        display: flex;
+        flex-direction: column;
+        gap: 16px;
+        background-color: #ffffff;
+        text-align: left;
+        position: relative;
+
+        .issues {
+            padding-top: 24px;
+            position: relative;
+            flex-grow: 1;
+            overflow-y: auto;
+            overflow-x: hidden;
+            display: flex;
+            flex-direction: column;
+            gap: 16px;
+        }
+    }
+
+    .right {
+        position: relative;
+        flex: 1;
+        padding: 16px;
+        min-width: 300px;
+        height: 100%;
+        overflow-y: auto;
+        display: flex;
+        flex-direction: column;
+        justify-content: center;
+        align-items: center;
+    }
+}
+
+// Issue
+
+.issue-card {
+    cursor: pointer;
+    padding: 16px;
+    background-color: #ffffff;
+    border: 1px solid $outline-gray;
+    transition: all 0.1s ease;
+    display: flex;
+    flex-direction: column;
+    gap: 8px;
+    border-radius: 8px;
+    transition: all 0.2s ease;
+
+    .issue-top {
+        display: flex;
+        align-items: center;
+        gap: 16px;
+        padding-bottom: 8px;
+    }
+
+    .issue-label {
+        cursor: pointer;
+        font-size: 14px;
+        font-weight: 400;
+        color: $text-gray;
+        padding: 0;
+        margin: 0;
+    }
+
+    .issue-title {
+        font-size: 16px;
+        font-weight: 500;
+        padding: 0;
+        margin: 0;
+        color: #4476f7;
+    }
+
+    &:hover {
+        background-color: #4476f7;
+        border-color: #4476f7;
+        color: #ffffff;
+
+        .issue-label {
+            color: #ffffff;
+        }
+
+        .issue-title {
+            color: #ffffff;
+        }
+    }
+}
+
+// Dropzone
+
+.dropzone {
+    padding: 2rem;
+    border-radius: 0.5rem;
+    border: 2px dashed #f1f5f9;
+
+    .dropzone-instructions {
+        display: flex;
+        flex-direction: column;
+        align-items: center;
+        gap: 16px;
+        color: $text-gray;
+
+        p {
+            text-align: center;
+        }
+    }
+}
+
+.file-list {
+    box-sizing: border-box;
+    margin: 0;
+    padding: 0;
+    font-size: 14px;
+    color: $text-gray;
+    width: 100%;
+    overflow-x: auto;
+    list-style-type: none;
+    display: flex;
+    align-items: center;
+    gap: 16px;
+
+    .file-name {
+        padding: 8px 12px;
+        display: flex;
+        align-items: center;
+        gap: 16px;
+        white-space: nowrap;
+    }
+}
+
+// Detailed issue view
+
+.issue-view {
+    height: 100%;
+    width: 100%;
+    display: flex;
+    flex-direction: column;
+    gap: 16px;
+    background-color: #ffffff;
+    text-align: left;
+    position: relative;
+    overflow: auto;
+
+    .issue-label {
+        color: $text-gray;
+
+        .issue-link {
+            cursor: pointer;
+            color: #4476f7;
+        }
+    }
+
+    .issue-title {
+        font-size: 24px;
+        margin: 0;
+        padding: 0;
+    }
+
+    .issue-date {
+        font-size: 14px;
+        color: $text-gray;
+    }
+
+    .issue-content {
+        font-size: 14px;
+        color: $text-gray;
+    }
+}
+
+// tags flex lists
+
+.issues-filters {
+    width: 100%;
+    display: flex;
+    flex-direction: column;
+    gap: 16px;
+
+    .issues-filter {
+        display: flex;
+        gap: 8px;
+        align-items: center;
+        white-space: nowrap;
+        overflow-x: auto;
+    }
+}
+
+.issue-tags {
+    display: flex;
+    gap: 8px;
+    align-items: center;
+    white-space: nowrap;
+    overflow-x: auto;
+}
+
+// Media previews
+
+.report-media-wrapper {
+    position: relative;
+    cursor: pointer;
+
+    .close-btn {
+        position: absolute;
+        top: 2px;
+        right: 2px;
+        opacity: 0;
+    }
+
+    .report-media-content {
+        position: relative;
+        display: inline block;
+
+        video::-webkit-media-controls {
+            display: flex !important;
+        }
+    }
+
+    .report-media-content::after {
+        content: '';
+        position: absolute;
+        top: 0;
+        left: 0;
+        width: 100%;
+        height: 100%;
+        background-color: rgba(0, 0, 0, 0.5); /* Adjust the opacity as desired */
+        opacity: 0;
+        transition: opacity 0.3s ease; /* Transition for smooth effect */
+        pointer-events: none;
+
+        video::-webkit-media-controls {
+            pointer-events: all;
+        }
+    }
+
+    &:hover {
+        .report-media-content::after {
+            opacity: 1;
+        }
+
+        .close-btn {
+            opacity: 1;
+        }
+    }
+}
+
+.report-audio-wrapper {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+}
+
+@media (max-width: 1100px) {
+    .report-header {
+        flex-direction: column;
+        align-items: stretch;
+        gap: 2rem;
+    }
+}
+
+// Tag styling
+
+.report-tag {
+    box-sizing: border-box;
+    padding: 4px 10px;
+    font-size: 10px;
+    border-radius: 32px;
+    transition: background-color 0.2s ease, color 0.2s ease, border-color 0.2s ease;
+}
+
+// Old code
+
+// <---------------------------------------------------------------------------->
+
+// .issue-list-wrapper {
+//     position: relative;
+//     min-width: 250px;
+//     background-color: $light-blue;
+//     overflow-y: scroll;
+// }
+
+// .issue-list {
+//     display: flex;
+//     align-items: center;
+//     justify-content: space-between;
+//     padding: 5px;
+//     margin: 5px;
+//     border-radius: 5px;
+//     border: 1px solid grey;
+//     background-color: lightgoldenrodyellow;
+// }
+
+// // issue should pop up when the user hover over the issue
+// .issue-list:hover {
+//     box-shadow: 2px;
+//     cursor: pointer;
+//     border: 3px solid #252b33;
+// }
+
+// .issue-content {
+//     background-color: white;
+//     padding: 10px;
+//     flex: 1 1 auto;
+//     overflow-y: scroll;
+// }
+
+// .issue-title {
+//     font-size: 20px;
+//     font-weight: 600;
+//     color: black;
+// }
+
+// .issue-body {
+//     padding: 0 10px;
+//     width: 100%;
+//     text-align: left;
+// }
+
+// .issue-body > * {
+//     margin-top: 5px;
+// }
+
+// .issue-body img,
+// .issue-body video {
+//     display: block;
+//     max-width: 100%;
+// }
+
+// .report-issue-fab {
+//     position: fixed;
+//     bottom: 20px;
+//     right: 20px;
+//     display: flex;
+//     align-items: center;
+//     justify-content: center;
+//     cursor: pointer;
+// }
+
+// .loading-center {
+//     margin: auto 0;
+// }
+
+// .settings-content label {
+//     margin-top: 10px;
+// }
+
+// .report-disclaimer {
+//     font-size: 8px;
+//     color: grey;
+//     padding-right: 50px;
+//     font-style: italic;
+//     text-align: left;
+// }
+
+// .flex-select {
+//     display: flex;
+//     align-items: center;
+//     justify-content: center;
+//     gap: 10px;
+// }
diff --git a/src/client/util/reportManager/ReportManager.tsx b/src/client/util/reportManager/ReportManager.tsx
new file mode 100644
index 000000000..ff17a5097
--- /dev/null
+++ b/src/client/util/reportManager/ReportManager.tsx
@@ -0,0 +1,570 @@
+import * as React from 'react';
+import '.././SettingsManager.scss';
+import './ReportManager.scss';
+import { action, observable } from 'mobx';
+import { BsX, BsArrowsAngleExpand, BsArrowsAngleContract } from 'react-icons/bs';
+import { CgClose } from 'react-icons/cg';
+import { AiOutlineUpload } from 'react-icons/ai';
+import { HiOutlineArrowLeft } from 'react-icons/hi';
+import { Issue } from './reportManagerSchema';
+import { observer } from 'mobx-react';
+import { Doc } from '../../../fields/Doc';
+import { Networking } from '../../Network';
+import { MainViewModal } from '../../views/MainViewModal';
+import { Octokit } from '@octokit/core';
+import { Button, IconButton, Tooltip } from '@mui/material';
+import Dropzone from 'react-dropzone';
+import { theme } from '../../theme';
+import ReactLoading from 'react-loading';
+import v4 = require('uuid/v4');
+import { BugType, FileData, Priority, ViewState, inactiveBorderColor, inactiveColor } from './reportManagerUtils';
+import { IssueCard, IssueView, Tag } from './ReportManagerComponents';
+// import { IconButton } from 'browndash-components';
+const higflyout = require('@hig/flyout');
+export const { anchorPoints } = higflyout;
+export const Flyout = higflyout.default;
+
+/**
+ * Class for reporting and viewing Github issues within the app.
+ */
+@observer
+export class ReportManager extends React.Component<{}> {
+    public static Instance: ReportManager;
+    @observable private isOpen = false;
+
+    @observable private query = '';
+    @action private setQuery = (q: string) => {
+        this.query = q;
+    };
+
+    private octokit: Octokit;
+
+    @observable viewState: ViewState = ViewState.VIEW;
+    @action private setViewState = (state: ViewState) => {
+        this.viewState = state;
+    };
+    @observable submitting: boolean = false;
+    @action private setSubmitting = (submitting: boolean) => {
+        this.submitting = submitting;
+    };
+
+    @observable fetchingIssues: boolean = false;
+    @action private setFetchingIssues = (fetching: boolean) => {
+        this.fetchingIssues = fetching;
+    };
+
+    @observable
+    public shownIssues: Issue[] = [];
+    @action setShownIssues = action((issues: Issue[]) => {
+        this.shownIssues = issues;
+    });
+
+    @observable
+    public priorityFilter: Priority | null = null;
+    @action setPriorityFilter = action((priority: Priority | null) => {
+        this.priorityFilter = priority;
+    });
+
+    @observable
+    public bugFilter: BugType | null = null;
+    @action setBugFilter = action((bug: BugType | null) => {
+        this.bugFilter = bug;
+    });
+
+    @observable selectedIssue: Issue | undefined = undefined;
+    @action setSelectedIssue = action((issue: Issue | undefined) => {
+        this.selectedIssue = issue;
+    });
+
+    @observable rightExpanded: boolean = false;
+    @action setRightExpanded = action((expanded: boolean) => {
+        this.rightExpanded = expanded;
+    });
+
+    // Form state
+
+    @observable private bugTitle = '';
+    @action setBugTitle = action((title: string) => {
+        this.bugTitle = title;
+    });
+    @observable private bugDescription = '';
+    @action setBugDescription = action((description: string) => {
+        this.bugDescription = description;
+    });
+    @observable private bugType = '';
+    @action setBugType = action((type: string) => {
+        this.bugType = type;
+    });
+    @observable private bugPriority = '';
+    @action setBugPriority = action((priortiy: string) => {
+        this.bugPriority = priortiy;
+    });
+
+    @observable private mediaFiles: FileData[] = [];
+    @action private setMediaFiles = (files: FileData[]) => {
+        this.mediaFiles = files;
+    };
+
+    public close = action(() => (this.isOpen = false));
+    public open = action(async () => {
+        this.isOpen = true;
+        if (this.shownIssues.length === 0) {
+            this.setFetchingIssues(true);
+            try {
+                // load in the issues if not already loaded
+                const issues = (await this.getAllIssues()) as Issue[];
+                console.log(issues);
+                // filtering to include only open issues and exclude pull requests, maybe add a separate tab for pr's?
+                this.setShownIssues(issues.filter(issue => issue.state === 'open' && !issue.pull_request));
+            } catch (err) {
+                console.log(err);
+            }
+            this.setFetchingIssues(false);
+        }
+    });
+
+    constructor(props: {}) {
+        super(props);
+        ReportManager.Instance = this;
+
+        // initializing Github connection
+        this.octokit = new Octokit({
+            auth: process.env.GITHUB_ACCESS_TOKEN,
+        });
+    }
+
+    /**
+     * Fethches issues from Github.
+     * @returns array of all issues
+     */
+    public async getAllIssues(): Promise<any[]> {
+        const res = await this.octokit.request('GET /repos/{owner}/{repo}/issues', {
+            owner: 'brown-dash',
+            repo: 'Dash-Web',
+            per_page: 80,
+        });
+
+        // 200 status means success
+        if (res.status === 200) {
+            return res.data;
+        } else {
+            throw new Error('Error getting issues');
+        }
+    }
+
+    /**
+     * Sends a request to Github to report a new issue with the form data.
+     * @returns nothing
+     */
+    public async reportIssue(): Promise<void> {
+        if (this.bugTitle === '' || this.bugDescription === '' || this.bugType === '' || this.bugPriority === '') {
+            alert('Please fill out all required fields to report an issue.');
+            return;
+        }
+        this.setSubmitting(true);
+
+        const links = await this.uploadFilesToServer();
+        if (!links) {
+            // error uploading files to the server
+            return;
+        }
+        console.log(links);
+        const formattedLinks = (links ?? []).map(this.fileLinktoServerLink);
+
+        // const req = await this.octokit.request('POST /repos/{owner}/{repo}/issues', {
+        //     owner: 'brown-dash',
+        //     repo: 'Dash-Web',
+        //     title: this.formatTitle(this.bugTitle, Doc.CurrentUserEmail),
+        //     body: `${this.bugDescription} ${formattedLinks.length > 0 && `\n\nFiles:\n${formattedLinks.join('\n')}`}`,
+        //     labels: ['from-dash-app', this.bugType, this.bugPriority],
+        // });
+
+        // // 201 status means success
+        // if (req.status !== 201) {
+        //     alert('Error creating issue on github.');
+        //     return;
+        // }
+
+        // Reset fields
+        this.setBugTitle('');
+        this.setBugDescription('');
+        this.setMediaFiles([]);
+        this.setBugType('');
+        this.setBugPriority('');
+        this.setSubmitting(false);
+        alert('Successfully submitted issue.');
+        // this.close();
+    }
+
+    /**
+     * Formats issue title.
+     *
+     * @param title title of issue
+     * @param userEmail email of issue submitter
+     * @returns formatted title
+     */
+    private formatTitle = (title: string, userEmail: string): string => `${title} - ${userEmail.replace('@brown.edu', '')}`;
+
+    // turns an upload link -> server link
+    // ex:
+    // C: /Users/dash/Documents/GitHub/Dash-Web/src/server/public/files/images/upload_8008dbc4b6424fbff14da7345bb32eb2.png
+    // -> https://browndash.com/files/images/upload_8008dbc4b6424fbff14da7345bb32eb2_l.png
+    private fileLinktoServerLink = (fileLink: string) => {
+        const serverUrl = 'https://browndash.com/';
+
+        const regex = 'public';
+        const publicIndex = fileLink.indexOf(regex) + regex.length;
+
+        const finalUrl = `${serverUrl}${fileLink.substring(publicIndex + 1).replace('.', '_l.')}`;
+        return finalUrl;
+    };
+
+    /**
+     * Gets the server file path.
+     *
+     * @param link response from file upload
+     * @returns server file path
+     */
+    private getServerPath = (link: any): string => {
+        return link.result.accessPaths.agnostic.server as string;
+    };
+
+    /**
+     * Uploads media files to the server.
+     * @returns the server paths or undefined on error
+     */
+    private uploadFilesToServer = async (): Promise<string[] | undefined> => {
+        try {
+            // need to always upload to browndash
+            const links = await Networking.UploadFilesToServer(
+                this.mediaFiles.map(file => ({ file: file.file })),
+                true
+            );
+            return (links ?? []).map(this.getServerPath);
+        } catch (err) {
+            if (err instanceof Error) {
+                alert(err.message);
+            } else {
+                alert(err);
+            }
+        }
+    };
+
+    /**
+     * Handles file upload.
+     * @param files uploaded files
+     */
+    private onDrop = (files: File[]) => {
+        this.setMediaFiles([...this.mediaFiles, ...files.map(file => ({ _id: v4(), file }))]);
+    };
+
+    /**
+     * Gets a JSX element to render a media preview
+     * @param fileData file data
+     * @returns JSX element of a piece of media (image, video, audio)
+     */
+    private getMediaPreview = (fileData: FileData): JSX.Element => {
+        const file = fileData.file;
+        const mimeType = file.type;
+        const preview = URL.createObjectURL(file);
+
+        if (mimeType.startsWith('image/')) {
+            return (
+                <div key={fileData._id} className="report-media-wrapper">
+                    <div className="report-media-content">
+                        <img height={100} alt={`Preview of ${file.name}`} src={preview} style={{ display: 'block' }} />
+                    </div>
+                    <IconButton onClick={() => this.setMediaFiles(this.mediaFiles.filter(f => f._id !== fileData._id))} className="close-btn">
+                        <BsX color="#ffffff" />
+                    </IconButton>
+                </div>
+            );
+        } else if (mimeType.startsWith('video/')) {
+            return (
+                <div key={fileData._id} className="report-media-wrapper">
+                    <div className="report-media-content">
+                        <video className="report-default-video" controls style={{ height: '100px', width: 'auto', display: 'block' }}>
+                            <source src={preview} type="video/mp4" />
+                            Your browser does not support the video tag.
+                        </video>
+                    </div>
+                    <IconButton onClick={() => this.setMediaFiles(this.mediaFiles.filter(f => f._id !== fileData._id))} className="close-btn">
+                        <BsX color="#ffffff" />
+                    </IconButton>
+                </div>
+            );
+        } else if (mimeType.startsWith('audio/')) {
+            return (
+                <div key={fileData._id} className="report-audio-wrapper">
+                    <audio src={preview} controls />
+                    <IconButton onClick={() => this.setMediaFiles(this.mediaFiles.filter(f => f._id !== fileData._id))} className="close-btn">
+                        <BsX />
+                    </IconButton>
+                </div>
+            );
+        }
+        return <></>;
+    };
+
+    private passesTagFilter = (issue: Issue) => {
+        let passesPriority = true;
+        let passesBug = true;
+        if (this.priorityFilter) {
+            passesPriority = issue.labels.some(label => {
+                if (typeof label === 'string') {
+                    return label === this.priorityFilter;
+                } else {
+                    return label.name === this.priorityFilter;
+                }
+            });
+        }
+        if (this.bugFilter) {
+            passesBug = issue.labels.some(label => {
+                if (typeof label === 'string') {
+                    return label === this.bugFilter;
+                } else {
+                    return label.name === this.bugFilter;
+                }
+            });
+        }
+        return passesPriority && passesBug;
+    };
+
+    /**
+     * @returns the component that dispays all issues
+     */
+    private viewIssuesComponent = () => {
+        return (
+            <div className="view-issues">
+                <div className="left" style={{ display: this.rightExpanded ? 'none' : 'flex' }}>
+                    <div className="report-header">
+                        <h2>Open Issues</h2>
+                        <Button
+                            variant="contained"
+                            onClick={() => {
+                                this.setViewState(ViewState.CREATE);
+                            }}>
+                            report issue
+                        </Button>
+                    </div>
+                    <input
+                        className="report-input"
+                        type="text"
+                        placeholder="Filter by query..."
+                        onChange={e => {
+                            this.setQuery(e.target.value);
+                        }}
+                        required
+                    />
+                    <div className="issues-filters">
+                        <div className="issues-filter">
+                            <Tag
+                                text={'All'}
+                                onClick={() => {
+                                    this.setPriorityFilter(null);
+                                }}
+                                fontSize="12px"
+                                backgroundColor={this.priorityFilter === null ? theme.palette.primary.main : '#ffffff'}
+                                color={this.priorityFilter === null ? '#ffffff' : inactiveColor}
+                                border
+                                borderColor={this.priorityFilter === null ? theme.palette.primary.main : inactiveBorderColor}
+                            />
+                            {Object.values(Priority).map(p => {
+                                return (
+                                    <Tag
+                                        key={p}
+                                        text={p}
+                                        onClick={() => {
+                                            this.setPriorityFilter(p);
+                                        }}
+                                        fontSize="12px"
+                                        backgroundColor={this.priorityFilter === p ? theme.palette.primary.main : '#ffffff'}
+                                        color={this.priorityFilter === p ? '#ffffff' : inactiveColor}
+                                        border
+                                        borderColor={this.priorityFilter === p ? theme.palette.primary.main : inactiveBorderColor}
+                                    />
+                                );
+                            })}
+                        </div>
+                        <div className="issues-filter">
+                            <Tag
+                                text={'All'}
+                                onClick={() => {
+                                    this.setBugFilter(null);
+                                }}
+                                fontSize="12px"
+                                backgroundColor={this.bugFilter === null ? theme.palette.primary.main : '#ffffff'}
+                                color={this.bugFilter === null ? '#ffffff' : inactiveColor}
+                                border
+                                borderColor={this.bugFilter === null ? theme.palette.primary.main : inactiveBorderColor}
+                            />
+                            {Object.values(BugType).map(b => {
+                                return (
+                                    <Tag
+                                        key={b}
+                                        text={b}
+                                        onClick={() => {
+                                            this.setBugFilter(b);
+                                        }}
+                                        fontSize="12px"
+                                        backgroundColor={this.bugFilter === b ? theme.palette.primary.main : '#ffffff'}
+                                        color={this.bugFilter === b ? '#ffffff' : inactiveColor}
+                                        border
+                                        borderColor={this.bugFilter === b ? theme.palette.primary.main : inactiveBorderColor}
+                                    />
+                                );
+                            })}
+                        </div>
+                    </div>
+                    <div className="issues">
+                        {this.fetchingIssues ? (
+                            <div style={{ flexGrow: 1, display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
+                                <ReactLoading type="spin" color={theme.palette.primary.main} width={50} height={50} />
+                            </div>
+                        ) : (
+                            this.shownIssues
+                                .filter(issue => issue.title.toLowerCase().includes(this.query))
+                                .filter(issue => this.passesTagFilter(issue))
+                                .map(issue => (
+                                    <IssueCard
+                                        key={issue.number}
+                                        issue={issue}
+                                        onSelect={() => {
+                                            this.setSelectedIssue(issue);
+                                        }}
+                                    />
+                                ))
+                        )}
+                    </div>
+                </div>
+                <div className="right">{this.selectedIssue ? <IssueView issue={this.selectedIssue} /> : <div>No issue selected</div>} </div>
+                <div style={{ position: 'absolute', top: '8px', right: '8px', display: 'flex', gap: '16px' }}>
+                    <Tooltip title={this.rightExpanded ? 'Minimize right side' : 'Expand right side'}>
+                        <IconButton
+                            onClick={e => {
+                                e.stopPropagation();
+                                this.setRightExpanded(!this.rightExpanded);
+                            }}>
+                            {this.rightExpanded ? <BsArrowsAngleContract size="16px" /> : <BsArrowsAngleExpand size="16px" />}
+                        </IconButton>
+                    </Tooltip>
+                    <IconButton onClick={this.close}>
+                        <CgClose size="16px" />
+                    </IconButton>
+                </div>
+            </div>
+        );
+    };
+
+    /**
+     * @returns the form component for submitting issues
+     */
+    private reportIssueComponent = () => {
+        return (
+            <div className="report-issue">
+                <div className="report-header-vertical">
+                    <Button
+                        variant="text"
+                        onClick={() => {
+                            this.setViewState(ViewState.VIEW);
+                        }}
+                        sx={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
+                        <HiOutlineArrowLeft color={theme.palette.primary.main} />
+                        back to view
+                    </Button>
+                    <h2>Report an Issue</h2>
+                </div>
+                <div className="report-section">
+                    <label className="report-label">Please provide a title for the bug</label>
+                    <input className="report-input" value={this.bugTitle} type="text" placeholder="Title..." onChange={e => this.setBugTitle(e.target.value)} required />
+                </div>
+                <div className="report-section">
+                    <label className="report-label">Please leave a description for the bug and how it can be recreated</label>
+                    <textarea className="report-textarea" value={this.bugDescription} placeholder="Description..." onChange={e => this.setBugDescription(e.target.value)} required />
+                </div>
+                <div className="report-selects">
+                    <select className="report-select" name="bugType" onChange={e => (this.bugType = e.target.value)}>
+                        <option value="" disabled selected>
+                            Type
+                        </option>
+                        <option className="report-opt" value="bug">
+                            Bug
+                        </option>
+                        <option className="report-opt" value="cosmetic">
+                            Poor Design or Cosmetic
+                        </option>
+                        <option className="report-opt" value="documentation">
+                            Poor Documentation
+                        </option>
+                        <option className="report-opt" value="enhancement">
+                            New feature or request
+                        </option>
+                    </select>
+                    <select className="report-select" name="bigPriority" onChange={e => (this.bugPriority = e.target.value)}>
+                        <option className="report-opt" value="" disabled selected>
+                            Priority
+                        </option>
+                        <option value="priority-low">Low</option>
+                        <option value="priority-medium">Medium</option>
+                        <option value="priority-high">High</option>
+                    </select>
+                </div>
+                <Dropzone
+                    onDrop={this.onDrop}
+                    accept={{
+                        'image/*': ['.png', '.jpg', '.jpeg', '.gif'],
+                        'video/*': ['.mp4', '.mpeg', '.webm', '.mov'],
+                        'audio/mpeg': ['.mp3'],
+                        'audio/wav': ['.wav'],
+                        'audio/ogg': ['.ogg'],
+                    }}>
+                    {({ getRootProps, getInputProps }) => (
+                        <div {...getRootProps({ className: 'dropzone' })}>
+                            <input {...getInputProps()} />
+                            <div className="dropzone-instructions">
+                                <AiOutlineUpload size={25} />
+                                <p>Drop or select media that shows the bug (optional)</p>
+                            </div>
+                        </div>
+                    )}
+                </Dropzone>
+                {this.mediaFiles.length > 0 && <ul className="file-list">{this.mediaFiles.map(file => this.getMediaPreview(file))}</ul>}
+                <Button
+                    variant="contained"
+                    sx={{ fontSize: '14px', display: 'flex', alignItems: 'center', gap: '1rem' }}
+                    onClick={() => {
+                        this.reportIssue();
+                    }}>
+                    Submit
+                    {this.submitting && <ReactLoading type="spin" color={theme.palette.primary.main} width={20} height={20} />}
+                </Button>
+                <IconButton onClick={this.close} sx={{ position: 'absolute', top: '4px', right: '4px' }}>
+                    <CgClose size={'16px'} />
+                </IconButton>
+            </div>
+        );
+    };
+
+    /**
+     * @returns the component rendered to the modal
+     */
+    private reportComponent = () => {
+        if (this.viewState === ViewState.VIEW) {
+            return this.viewIssuesComponent();
+        } else {
+            return this.reportIssueComponent();
+        }
+    };
+
+    render() {
+        return (
+            <MainViewModal
+                contents={this.reportComponent()}
+                isDisplayed={this.isOpen}
+                interactive={true}
+                closeOnExternalClick={this.close}
+                dialogueBoxStyle={{ width: 'auto', minWidth: '300px', height: '85vh', maxHeight: '90vh', background: '#ffffff', borderRadius: '8px' }}
+            />
+        );
+    }
+}
diff --git a/src/client/util/reportManager/ReportManagerComponents.tsx b/src/client/util/reportManager/ReportManagerComponents.tsx
new file mode 100644
index 000000000..1a4ddb3a3
--- /dev/null
+++ b/src/client/util/reportManager/ReportManagerComponents.tsx
@@ -0,0 +1,153 @@
+import * as React from 'react';
+import { Issue } from './reportManagerSchema';
+import { getLabelColors } from './reportManagerUtils';
+import ReactMarkdown from 'react-markdown';
+import rehypeRaw from 'rehype-raw';
+import remarkGfm from 'remark-gfm';
+
+// Mini components to render issues
+
+interface IssueCardProps {
+    issue: Issue;
+    onSelect: () => void;
+}
+export const IssueCard = ({ issue, onSelect }: IssueCardProps) => {
+    return (
+        <div className="issue-card" onClick={onSelect}>
+            <div className="issue-top">
+                <label className="issue-label">#{issue.number}</label>
+                <div className="issue-tags">
+                    {issue.labels.map(label => {
+                        const labelString = typeof label === 'string' ? label : label.name ?? '';
+                        const colors = getLabelColors(labelString);
+                        return <Tag key={labelString} text={labelString} backgroundColor={colors[0]} color={colors[1]} />;
+                    })}
+                </div>
+            </div>
+            <h3 className="issue-title">{issue.title}</h3>
+        </div>
+    );
+};
+
+interface IssueViewProps {
+    issue: Issue;
+}
+
+export const IssueView = ({ issue }: IssueViewProps) => {
+    const [issueBody, setIssueBody] = React.useState('');
+
+    const isVideoValid = (src: string) => {
+        const videoElement = document.createElement('video');
+        const validPromise: Promise<boolean> = new Promise(resolve => {
+            videoElement.addEventListener('loadeddata', () => resolve(true));
+            videoElement.addEventListener('error', () => resolve(false));
+        });
+        videoElement.src = src;
+        return validPromise;
+    };
+
+    const getLinkFromTag = async (tag: string) => {
+        const regex = /src="([^"]+)"/;
+        let url = '';
+        const match = tag.match(regex);
+        if (match) {
+            url = match[1];
+        }
+
+        if (url.startsWith('https://github.com/brown-dash/Dash-Web/assets')) {
+            return `\n${url} (Not authorized to display image here)`;
+        }
+        return await getTagFromUrl(url);
+    };
+
+    const getTagFromUrl = async (url: string) => {
+        const imgRegex = /https:\/\/browndash\.com\/files[/\\]images/;
+        const videoRegex = /https:\/\/browndash\.com\/files[/\\]videos/;
+        const audioRegex = /https:\/\/browndash\.com\/files[/\\]audio/;
+
+        if (imgRegex.test(url) || url.includes('user-images.githubusercontent.com')) {
+            return `\n${url}\n<img width="100%" alt="Issue asset" src=${url} />\n`;
+        } else if (videoRegex.test(url)) {
+            const videoValid = await isVideoValid(url);
+            if (!videoValid) return `\n${url} (This video could not be loaded)\n`;
+            return `\n${url}\n<video class="report-default-video" width="100%" controls alt="Issue asset" src=${url} />\n`;
+        } else if (audioRegex.test(url)) {
+            return `\n${url}\n<audio src=${url} controls />\n`;
+        } else {
+            return url;
+        }
+    };
+
+    const parseBody = async (body: string) => {
+        const imgTagRegex = /<img\b[^>]*\/?>/;
+        const fileRegex = /https:\/\/browndash\.com\/files/;
+        const parts = body.split('\n');
+
+        const modifiedParts = await Promise.all(
+            parts.map(async part => {
+                if (imgTagRegex.test(part)) {
+                    return `\n${await getLinkFromTag(part)}\n`;
+                } else if (fileRegex.test(part)) {
+                    const tag = await getTagFromUrl(part);
+                    return tag;
+                } else {
+                    return part;
+                }
+            })
+        );
+
+        setIssueBody(modifiedParts.join('\n'));
+    };
+
+    React.useEffect(() => {
+        parseBody((issue.body as string) ?? '');
+    }, [issue]);
+
+    return (
+        <div className="issue-view">
+            <span className="issue-label">
+                Issue{' '}
+                <a className="issue-link" href={issue.html_url} target="_blank">
+                    #{issue.number}
+                </a>
+            </span>
+            <h2 className="issue-title">{issue.title}</h2>
+            <div className="issue-date">
+                Opened on {new Date(issue.created_at).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })} {issue.user?.login && `by ${issue.user?.login}`}
+            </div>
+            {issue.labels.length > 0 && (
+                <div>
+                    <div className="issue-tags">
+                        {issue.labels.map(label => {
+                            const labelString = typeof label === 'string' ? label : label.name ?? '';
+                            const colors = getLabelColors(labelString);
+                            return <Tag key={labelString} text={labelString} backgroundColor={colors[0]} color={colors[1]} fontSize="12px" />;
+                        })}
+                    </div>
+                </div>
+            )}
+            <ReactMarkdown children={issueBody} className="issue-content" linkTarget={'_blank'} remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeRaw]} />
+        </div>
+    );
+};
+
+interface TagProps {
+    text: string;
+    fontSize?: string;
+    color?: string;
+    backgroundColor?: string;
+    borderColor?: string;
+    border?: boolean;
+    onClick?: () => void;
+}
+
+export const Tag = ({ text, color, backgroundColor, fontSize, border, borderColor, onClick }: TagProps) => {
+    return (
+        <div
+            onClick={onClick ?? (() => {})}
+            className="report-tag"
+            style={{ color: color ?? '#ffffff', backgroundColor: backgroundColor ?? '#347bff', cursor: onClick ? 'pointer' : 'auto', fontSize: fontSize ?? '10px', border: border ? '1px solid' : 'none', borderColor: borderColor ?? '#94a3b8' }}>
+            {text}
+        </div>
+    );
+};
diff --git a/src/client/util/reportManager/reportManagerSchema.ts b/src/client/util/reportManager/reportManagerSchema.ts
new file mode 100644
index 000000000..56b9aa32b
--- /dev/null
+++ b/src/client/util/reportManager/reportManagerSchema.ts
@@ -0,0 +1,877 @@
+/**
+ * Issue interface schema.
+ */
+export interface Issue {
+    active_lock_reason?: null | string;
+    assignee: null | PurpleSimpleUser;
+    assignees?: AssigneeElement[] | null;
+    /**
+     * How the author is associated with the repository.
+     */
+    author_association: AuthorAssociation;
+    /**
+     * Contents of the issue
+     */
+    body?: null | string;
+    body_html?: string;
+    body_text?: string;
+    closed_at: Date | null;
+    closed_by?: null | FluffySimpleUser;
+    comments: number;
+    comments_url: string;
+    created_at: Date;
+    draft?: boolean;
+    events_url: string;
+    html_url: string;
+    id: number;
+    /**
+     * Labels to associate with this issue; pass one or more label names to replace the set of
+     * labels on this issue; send an empty array to clear all labels from the issue; note that
+     * the labels are silently dropped for users without push access to the repository
+     */
+    labels: Array<LabelObject | string>;
+    labels_url: string;
+    locked: boolean;
+    milestone: null | Milestone;
+    node_id: string;
+    /**
+     * Number uniquely identifying the issue within its repository
+     */
+    number: number;
+    performed_via_github_app?: null | GitHubApp;
+    pull_request?: PullRequest;
+    reactions?: ReactionRollup;
+    /**
+     * A repository on GitHub.
+     */
+    repository?: Repository;
+    repository_url: string;
+    /**
+     * State of the issue; either 'open' or 'closed'
+     */
+    state: string;
+    /**
+     * The reason for the current state
+     */
+    state_reason?: StateReason | null;
+    timeline_url?: string;
+    /**
+     * Title of the issue
+     */
+    title: string;
+    updated_at: Date;
+    /**
+     * URL for the issue
+     */
+    url: string;
+    user: null | TentacledSimpleUser;
+    [property: string]: any;
+}
+
+/**
+ * A GitHub user.
+ */
+export interface PurpleSimpleUser {
+    avatar_url: string;
+    email?: null | string;
+    events_url: string;
+    followers_url: string;
+    following_url: string;
+    gists_url: string;
+    gravatar_id: null | string;
+    html_url: string;
+    id: number;
+    login: string;
+    name?: null | string;
+    node_id: string;
+    organizations_url: string;
+    received_events_url: string;
+    repos_url: string;
+    site_admin: boolean;
+    starred_at?: string;
+    starred_url: string;
+    subscriptions_url: string;
+    type: string;
+    url: string;
+    [property: string]: any;
+}
+
+/**
+ * A GitHub user.
+ */
+export interface AssigneeElement {
+    avatar_url: string;
+    email?: null | string;
+    events_url: string;
+    followers_url: string;
+    following_url: string;
+    gists_url: string;
+    gravatar_id: null | string;
+    html_url: string;
+    id: number;
+    login: string;
+    name?: null | string;
+    node_id: string;
+    organizations_url: string;
+    received_events_url: string;
+    repos_url: string;
+    site_admin: boolean;
+    starred_at?: string;
+    starred_url: string;
+    subscriptions_url: string;
+    type: string;
+    url: string;
+    [property: string]: any;
+}
+
+/**
+ * How the author is associated with the repository.
+ */
+export enum AuthorAssociation {
+    Collaborator = 'COLLABORATOR',
+    Contributor = 'CONTRIBUTOR',
+    FirstTimeContributor = 'FIRST_TIME_CONTRIBUTOR',
+    FirstTimer = 'FIRST_TIMER',
+    Mannequin = 'MANNEQUIN',
+    Member = 'MEMBER',
+    None = 'NONE',
+    Owner = 'OWNER',
+}
+
+/**
+ * A GitHub user.
+ */
+export interface FluffySimpleUser {
+    avatar_url: string;
+    email?: null | string;
+    events_url: string;
+    followers_url: string;
+    following_url: string;
+    gists_url: string;
+    gravatar_id: null | string;
+    html_url: string;
+    id: number;
+    login: string;
+    name?: null | string;
+    node_id: string;
+    organizations_url: string;
+    received_events_url: string;
+    repos_url: string;
+    site_admin: boolean;
+    starred_at?: string;
+    starred_url: string;
+    subscriptions_url: string;
+    type: string;
+    url: string;
+    [property: string]: any;
+}
+
+export interface LabelObject {
+    color?: null | string;
+    default?: boolean;
+    description?: null | string;
+    id?: number;
+    name?: string;
+    node_id?: string;
+    url?: string;
+    [property: string]: any;
+}
+
+/**
+ * A collection of related issues and pull requests.
+ */
+export interface Milestone {
+    closed_at: Date | null;
+    closed_issues: number;
+    created_at: Date;
+    creator: null | MilestoneSimpleUser;
+    description: null | string;
+    due_on: Date | null;
+    html_url: string;
+    id: number;
+    labels_url: string;
+    node_id: string;
+    /**
+     * The number of the milestone.
+     */
+    number: number;
+    open_issues: number;
+    /**
+     * The state of the milestone.
+     */
+    state: State;
+    /**
+     * The title of the milestone.
+     */
+    title: string;
+    updated_at: Date;
+    url: string;
+    [property: string]: any;
+}
+
+/**
+ * A GitHub user.
+ */
+export interface MilestoneSimpleUser {
+    avatar_url: string;
+    email?: null | string;
+    events_url: string;
+    followers_url: string;
+    following_url: string;
+    gists_url: string;
+    gravatar_id: null | string;
+    html_url: string;
+    id: number;
+    login: string;
+    name?: null | string;
+    node_id: string;
+    organizations_url: string;
+    received_events_url: string;
+    repos_url: string;
+    site_admin: boolean;
+    starred_at?: string;
+    starred_url: string;
+    subscriptions_url: string;
+    type: string;
+    url: string;
+    [property: string]: any;
+}
+
+/**
+ * The state of the milestone.
+ */
+export enum State {
+    Closed = 'closed',
+    Open = 'open',
+}
+
+/**
+ * GitHub apps are a new way to extend GitHub. They can be installed directly on
+ * organizations and user accounts and granted access to specific repositories. They come
+ * with granular permissions and built-in webhooks. GitHub apps are first class actors
+ * within GitHub.
+ */
+export interface GitHubApp {
+    client_id?: string;
+    client_secret?: string;
+    created_at: Date;
+    description: null | string;
+    /**
+     * The list of events for the GitHub app
+     */
+    events: string[];
+    external_url: string;
+    html_url: string;
+    /**
+     * Unique identifier of the GitHub app
+     */
+    id: number;
+    /**
+     * The number of installations associated with the GitHub app
+     */
+    installations_count?: number;
+    /**
+     * The name of the GitHub app
+     */
+    name: string;
+    node_id: string;
+    owner: null | GitHubAppSimpleUser;
+    pem?: string;
+    /**
+     * The set of permissions for the GitHub app
+     */
+    permissions: GitHubAppPermissions;
+    /**
+     * The slug name of the GitHub app
+     */
+    slug?: string;
+    updated_at: Date;
+    webhook_secret?: null | string;
+    [property: string]: any;
+}
+
+/**
+ * A GitHub user.
+ */
+export interface GitHubAppSimpleUser {
+    avatar_url: string;
+    email?: null | string;
+    events_url: string;
+    followers_url: string;
+    following_url: string;
+    gists_url: string;
+    gravatar_id: null | string;
+    html_url: string;
+    id: number;
+    login: string;
+    name?: null | string;
+    node_id: string;
+    organizations_url: string;
+    received_events_url: string;
+    repos_url: string;
+    site_admin: boolean;
+    starred_at?: string;
+    starred_url: string;
+    subscriptions_url: string;
+    type: string;
+    url: string;
+    [property: string]: any;
+}
+
+/**
+ * The set of permissions for the GitHub app
+ */
+export interface GitHubAppPermissions {
+    checks?: string;
+    contents?: string;
+    deployments?: string;
+    issues?: string;
+    metadata?: string;
+}
+
+export interface PullRequest {
+    diff_url: null | string;
+    html_url: null | string;
+    merged_at?: Date | null;
+    patch_url: null | string;
+    url: null | string;
+    [property: string]: any;
+}
+
+export interface ReactionRollup {
+    '+1': number;
+    '-1': number;
+    confused: number;
+    eyes: number;
+    heart: number;
+    hooray: number;
+    laugh: number;
+    rocket: number;
+    total_count: number;
+    url: string;
+    [property: string]: any;
+}
+
+/**
+ * A repository on GitHub.
+ */
+export interface Repository {
+    /**
+     * Whether to allow Auto-merge to be used on pull requests.
+     */
+    allow_auto_merge?: boolean;
+    /**
+     * Whether to allow forking this repo
+     */
+    allow_forking?: boolean;
+    /**
+     * Whether to allow merge commits for pull requests.
+     */
+    allow_merge_commit?: boolean;
+    /**
+     * Whether to allow rebase merges for pull requests.
+     */
+    allow_rebase_merge?: boolean;
+    /**
+     * Whether to allow squash merges for pull requests.
+     */
+    allow_squash_merge?: boolean;
+    /**
+     * Whether or not a pull request head branch that is behind its base branch can always be
+     * updated even if it is not required to be up to date before merging.
+     */
+    allow_update_branch?: boolean;
+    /**
+     * Whether anonymous git access is enabled for this repository
+     */
+    anonymous_access_enabled?: boolean;
+    archive_url: string;
+    /**
+     * Whether the repository is archived.
+     */
+    archived: boolean;
+    assignees_url: string;
+    blobs_url: string;
+    branches_url: string;
+    clone_url: string;
+    collaborators_url: string;
+    comments_url: string;
+    commits_url: string;
+    compare_url: string;
+    contents_url: string;
+    contributors_url: string;
+    created_at: Date | null;
+    /**
+     * The default branch of the repository.
+     */
+    default_branch: string;
+    /**
+     * Whether to delete head branches when pull requests are merged
+     */
+    delete_branch_on_merge?: boolean;
+    deployments_url: string;
+    description: null | string;
+    /**
+     * Returns whether or not this repository disabled.
+     */
+    disabled: boolean;
+    downloads_url: string;
+    events_url: string;
+    fork: boolean;
+    forks: number;
+    forks_count: number;
+    forks_url: string;
+    full_name: string;
+    git_commits_url: string;
+    git_refs_url: string;
+    git_tags_url: string;
+    git_url: string;
+    /**
+     * Whether discussions are enabled.
+     */
+    has_discussions?: boolean;
+    /**
+     * Whether downloads are enabled.
+     */
+    has_downloads: boolean;
+    /**
+     * Whether issues are enabled.
+     */
+    has_issues: boolean;
+    has_pages: boolean;
+    /**
+     * Whether projects are enabled.
+     */
+    has_projects: boolean;
+    /**
+     * Whether the wiki is enabled.
+     */
+    has_wiki: boolean;
+    homepage: null | string;
+    hooks_url: string;
+    html_url: string;
+    /**
+     * Unique identifier of the repository
+     */
+    id: number;
+    /**
+     * Whether this repository acts as a template that can be used to generate new repositories.
+     */
+    is_template?: boolean;
+    issue_comment_url: string;
+    issue_events_url: string;
+    issues_url: string;
+    keys_url: string;
+    labels_url: string;
+    language: null | string;
+    languages_url: string;
+    license: null | LicenseSimple;
+    master_branch?: string;
+    /**
+     * The default value for a merge commit message.
+     *
+     * - `PR_TITLE` - default to the pull request's title.
+     * - `PR_BODY` - default to the pull request's body.
+     * - `BLANK` - default to a blank commit message.
+     */
+    merge_commit_message?: MergeCommitMessage;
+    /**
+     * The default value for a merge commit title.
+     *
+     * - `PR_TITLE` - default to the pull request's title.
+     * - `MERGE_MESSAGE` - default to the classic title for a merge message (e.g., Merge pull
+     * request #123 from branch-name).
+     */
+    merge_commit_title?: MergeCommitTitle;
+    merges_url: string;
+    milestones_url: string;
+    mirror_url: null | string;
+    /**
+     * The name of the repository.
+     */
+    name: string;
+    network_count?: number;
+    node_id: string;
+    notifications_url: string;
+    open_issues: number;
+    open_issues_count: number;
+    organization?: null | RepositorySimpleUser;
+    /**
+     * A GitHub user.
+     */
+    owner: OwnerObject;
+    permissions?: RepositoryPermissions;
+    /**
+     * Whether the repository is private or public.
+     */
+    private: boolean;
+    pulls_url: string;
+    pushed_at: Date | null;
+    releases_url: string;
+    /**
+     * The size of the repository. Size is calculated hourly. When a repository is initially
+     * created, the size is 0.
+     */
+    size: number;
+    /**
+     * The default value for a squash merge commit message:
+     *
+     * - `PR_BODY` - default to the pull request's body.
+     * - `COMMIT_MESSAGES` - default to the branch's commit messages.
+     * - `BLANK` - default to a blank commit message.
+     */
+    squash_merge_commit_message?: SquashMergeCommitMessage;
+    /**
+     * The default value for a squash merge commit title:
+     *
+     * - `PR_TITLE` - default to the pull request's title.
+     * - `COMMIT_OR_PR_TITLE` - default to the commit's title (if only one commit) or the pull
+     * request's title (when more than one commit).
+     */
+    squash_merge_commit_title?: SquashMergeCommitTitle;
+    ssh_url: string;
+    stargazers_count: number;
+    stargazers_url: string;
+    starred_at?: string;
+    statuses_url: string;
+    subscribers_count?: number;
+    subscribers_url: string;
+    subscription_url: string;
+    svn_url: string;
+    tags_url: string;
+    teams_url: string;
+    temp_clone_token?: string;
+    template_repository?: null | TemplateRepository;
+    topics?: string[];
+    trees_url: string;
+    updated_at: Date | null;
+    url: string;
+    /**
+     * Whether a squash merge commit can use the pull request title as default. **This property
+     * has been deprecated. Please use `squash_merge_commit_title` instead.
+     */
+    use_squash_pr_title_as_default?: boolean;
+    /**
+     * The repository visibility: public, private, or internal.
+     */
+    visibility?: string;
+    watchers: number;
+    watchers_count: number;
+    /**
+     * Whether to require contributors to sign off on web-based commits
+     */
+    web_commit_signoff_required?: boolean;
+    [property: string]: any;
+}
+
+/**
+ * License Simple
+ */
+export interface LicenseSimple {
+    html_url?: string;
+    key: string;
+    name: string;
+    node_id: string;
+    spdx_id: null | string;
+    url: null | string;
+    [property: string]: any;
+}
+
+/**
+ * The default value for a merge commit message.
+ *
+ * - `PR_TITLE` - default to the pull request's title.
+ * - `PR_BODY` - default to the pull request's body.
+ * - `BLANK` - default to a blank commit message.
+ */
+export enum MergeCommitMessage {
+    Blank = 'BLANK',
+    PRBody = 'PR_BODY',
+    PRTitle = 'PR_TITLE',
+}
+
+/**
+ * The default value for a merge commit title.
+ *
+ * - `PR_TITLE` - default to the pull request's title.
+ * - `MERGE_MESSAGE` - default to the classic title for a merge message (e.g., Merge pull
+ * request #123 from branch-name).
+ */
+export enum MergeCommitTitle {
+    MergeMessage = 'MERGE_MESSAGE',
+    PRTitle = 'PR_TITLE',
+}
+
+/**
+ * A GitHub user.
+ */
+export interface RepositorySimpleUser {
+    avatar_url: string;
+    email?: null | string;
+    events_url: string;
+    followers_url: string;
+    following_url: string;
+    gists_url: string;
+    gravatar_id: null | string;
+    html_url: string;
+    id: number;
+    login: string;
+    name?: null | string;
+    node_id: string;
+    organizations_url: string;
+    received_events_url: string;
+    repos_url: string;
+    site_admin: boolean;
+    starred_at?: string;
+    starred_url: string;
+    subscriptions_url: string;
+    type: string;
+    url: string;
+    [property: string]: any;
+}
+
+/**
+ * A GitHub user.
+ */
+export interface OwnerObject {
+    avatar_url: string;
+    email?: null | string;
+    events_url: string;
+    followers_url: string;
+    following_url: string;
+    gists_url: string;
+    gravatar_id: null | string;
+    html_url: string;
+    id: number;
+    login: string;
+    name?: null | string;
+    node_id: string;
+    organizations_url: string;
+    received_events_url: string;
+    repos_url: string;
+    site_admin: boolean;
+    starred_at?: string;
+    starred_url: string;
+    subscriptions_url: string;
+    type: string;
+    url: string;
+    [property: string]: any;
+}
+
+export interface RepositoryPermissions {
+    admin: boolean;
+    maintain?: boolean;
+    pull: boolean;
+    push: boolean;
+    triage?: boolean;
+    [property: string]: any;
+}
+
+/**
+ * The default value for a squash merge commit message:
+ *
+ * - `PR_BODY` - default to the pull request's body.
+ * - `COMMIT_MESSAGES` - default to the branch's commit messages.
+ * - `BLANK` - default to a blank commit message.
+ */
+export enum SquashMergeCommitMessage {
+    Blank = 'BLANK',
+    CommitMessages = 'COMMIT_MESSAGES',
+    PRBody = 'PR_BODY',
+}
+
+/**
+ * The default value for a squash merge commit title:
+ *
+ * - `PR_TITLE` - default to the pull request's title.
+ * - `COMMIT_OR_PR_TITLE` - default to the commit's title (if only one commit) or the pull
+ * request's title (when more than one commit).
+ */
+export enum SquashMergeCommitTitle {
+    CommitOrPRTitle = 'COMMIT_OR_PR_TITLE',
+    PRTitle = 'PR_TITLE',
+}
+
+export interface TemplateRepository {
+    allow_auto_merge?: boolean;
+    allow_merge_commit?: boolean;
+    allow_rebase_merge?: boolean;
+    allow_squash_merge?: boolean;
+    allow_update_branch?: boolean;
+    archive_url?: string;
+    archived?: boolean;
+    assignees_url?: string;
+    blobs_url?: string;
+    branches_url?: string;
+    clone_url?: string;
+    collaborators_url?: string;
+    comments_url?: string;
+    commits_url?: string;
+    compare_url?: string;
+    contents_url?: string;
+    contributors_url?: string;
+    created_at?: string;
+    default_branch?: string;
+    delete_branch_on_merge?: boolean;
+    deployments_url?: string;
+    description?: string;
+    disabled?: boolean;
+    downloads_url?: string;
+    events_url?: string;
+    fork?: boolean;
+    forks_count?: number;
+    forks_url?: string;
+    full_name?: string;
+    git_commits_url?: string;
+    git_refs_url?: string;
+    git_tags_url?: string;
+    git_url?: string;
+    has_downloads?: boolean;
+    has_issues?: boolean;
+    has_pages?: boolean;
+    has_projects?: boolean;
+    has_wiki?: boolean;
+    homepage?: string;
+    hooks_url?: string;
+    html_url?: string;
+    id?: number;
+    is_template?: boolean;
+    issue_comment_url?: string;
+    issue_events_url?: string;
+    issues_url?: string;
+    keys_url?: string;
+    labels_url?: string;
+    language?: string;
+    languages_url?: string;
+    /**
+     * The default value for a merge commit message.
+     *
+     * - `PR_TITLE` - default to the pull request's title.
+     * - `PR_BODY` - default to the pull request's body.
+     * - `BLANK` - default to a blank commit message.
+     */
+    merge_commit_message?: MergeCommitMessage;
+    /**
+     * The default value for a merge commit title.
+     *
+     * - `PR_TITLE` - default to the pull request's title.
+     * - `MERGE_MESSAGE` - default to the classic title for a merge message (e.g., Merge pull
+     * request #123 from branch-name).
+     */
+    merge_commit_title?: MergeCommitTitle;
+    merges_url?: string;
+    milestones_url?: string;
+    mirror_url?: string;
+    name?: string;
+    network_count?: number;
+    node_id?: string;
+    notifications_url?: string;
+    open_issues_count?: number;
+    owner?: Owner;
+    permissions?: TemplateRepositoryPermissions;
+    private?: boolean;
+    pulls_url?: string;
+    pushed_at?: string;
+    releases_url?: string;
+    size?: number;
+    /**
+     * The default value for a squash merge commit message:
+     *
+     * - `PR_BODY` - default to the pull request's body.
+     * - `COMMIT_MESSAGES` - default to the branch's commit messages.
+     * - `BLANK` - default to a blank commit message.
+     */
+    squash_merge_commit_message?: SquashMergeCommitMessage;
+    /**
+     * The default value for a squash merge commit title:
+     *
+     * - `PR_TITLE` - default to the pull request's title.
+     * - `COMMIT_OR_PR_TITLE` - default to the commit's title (if only one commit) or the pull
+     * request's title (when more than one commit).
+     */
+    squash_merge_commit_title?: SquashMergeCommitTitle;
+    ssh_url?: string;
+    stargazers_count?: number;
+    stargazers_url?: string;
+    statuses_url?: string;
+    subscribers_count?: number;
+    subscribers_url?: string;
+    subscription_url?: string;
+    svn_url?: string;
+    tags_url?: string;
+    teams_url?: string;
+    temp_clone_token?: string;
+    topics?: string[];
+    trees_url?: string;
+    updated_at?: string;
+    url?: string;
+    use_squash_pr_title_as_default?: boolean;
+    visibility?: string;
+    watchers_count?: number;
+    [property: string]: any;
+}
+
+export interface Owner {
+    avatar_url?: string;
+    events_url?: string;
+    followers_url?: string;
+    following_url?: string;
+    gists_url?: string;
+    gravatar_id?: string;
+    html_url?: string;
+    id?: number;
+    login?: string;
+    node_id?: string;
+    organizations_url?: string;
+    received_events_url?: string;
+    repos_url?: string;
+    site_admin?: boolean;
+    starred_url?: string;
+    subscriptions_url?: string;
+    type?: string;
+    url?: string;
+    [property: string]: any;
+}
+
+export interface TemplateRepositoryPermissions {
+    admin?: boolean;
+    maintain?: boolean;
+    pull?: boolean;
+    push?: boolean;
+    triage?: boolean;
+    [property: string]: any;
+}
+
+export enum StateReason {
+    Completed = 'completed',
+    NotPlanned = 'not_planned',
+    Reopened = 'reopened',
+}
+
+/**
+ * A GitHub user.
+ */
+export interface TentacledSimpleUser {
+    avatar_url: string;
+    email?: null | string;
+    events_url: string;
+    followers_url: string;
+    following_url: string;
+    gists_url: string;
+    gravatar_id: null | string;
+    html_url: string;
+    id: number;
+    login: string;
+    name?: null | string;
+    node_id: string;
+    organizations_url: string;
+    received_events_url: string;
+    repos_url: string;
+    site_admin: boolean;
+    starred_at?: string;
+    starred_url: string;
+    subscriptions_url: string;
+    type: string;
+    url: string;
+    [property: string]: any;
+}
diff --git a/src/client/util/reportManager/reportManagerUtils.ts b/src/client/util/reportManager/reportManagerUtils.ts
new file mode 100644
index 000000000..3c919856a
--- /dev/null
+++ b/src/client/util/reportManager/reportManagerUtils.ts
@@ -0,0 +1,54 @@
+// Final file url reference: "https://browndash.com/files/images/upload_cb31bc0fda59c96ca14193ec494f80cf_o.jpg" />
+
+export enum ViewState {
+    VIEW,
+    CREATE,
+}
+
+export enum Priority {
+    HIGH = 'priority-high',
+    MEDIUM = 'priority-medium',
+    LOW = 'priority-low',
+}
+
+export enum BugType {
+    BUG = 'bug',
+    COSMETIC = 'cosmetic',
+    DOCUMENTATION = 'documentation',
+    ENHANCEMENT = 'enhancement',
+}
+
+// [bgColor, color]
+export const priorityColors: { [key: string]: string[] } = {
+    'priority-low': ['#d4e0ff', '#000000'],
+    'priority-medium': ['#6a91f6', '#ffffff'],
+    'priority-high': ['#4476f7', '#ffffff'],
+};
+
+// [bgColor, color]
+export const bugColors: { [key: string]: string[] } = {
+    bug: ['#fe6d6d', '#ffffff'],
+    cosmetic: ['#c650f4', '#ffffff'],
+    documentation: ['#36acf0', '#ffffff'],
+    enhancement: ['#36d4f0', '#ffffff'],
+};
+
+export const prioritySet = new Set(Object.values(Priority));
+export const bugSet = new Set(Object.values(BugType));
+
+export const getLabelColors = (label: string): string[] => {
+    if (prioritySet.has(label as Priority)) {
+        return priorityColors[label];
+    } else if (bugSet.has(label as BugType)) {
+        return bugColors[label];
+    }
+    return ['#347bff', '#ffffff'];
+};
+
+export interface FileData {
+    _id: string;
+    file: File;
+}
+
+export const inactiveBorderColor = '#cbd5e1';
+export const inactiveColor = '#64748b';
diff --git a/src/client/util/reportManagerSchema.ts b/src/client/util/reportManagerSchema.ts
deleted file mode 100644
index 28ce6411d..000000000
--- a/src/client/util/reportManagerSchema.ts
+++ /dev/null
@@ -1,877 +0,0 @@
-/**
- * Issues are a great way to keep track of tasks, enhancements, and bugs for your projects.
- */
-export interface Issue {
-    active_lock_reason?: null | string;
-    assignee: null | PurpleSimpleUser;
-    assignees?: AssigneeElement[] | null;
-    /**
-     * How the author is associated with the repository.
-     */
-    author_association: AuthorAssociation;
-    /**
-     * Contents of the issue
-     */
-    body?: null | string;
-    body_html?: string;
-    body_text?: string;
-    closed_at: Date | null;
-    closed_by?: null | FluffySimpleUser;
-    comments: number;
-    comments_url: string;
-    created_at: Date;
-    draft?: boolean;
-    events_url: string;
-    html_url: string;
-    id: number;
-    /**
-     * Labels to associate with this issue; pass one or more label names to replace the set of
-     * labels on this issue; send an empty array to clear all labels from the issue; note that
-     * the labels are silently dropped for users without push access to the repository
-     */
-    labels: Array<LabelObject | string>;
-    labels_url: string;
-    locked: boolean;
-    milestone: null | Milestone;
-    node_id: string;
-    /**
-     * Number uniquely identifying the issue within its repository
-     */
-    number: number;
-    performed_via_github_app?: null | GitHubApp;
-    pull_request?: PullRequest;
-    reactions?: ReactionRollup;
-    /**
-     * A repository on GitHub.
-     */
-    repository?: Repository;
-    repository_url: string;
-    /**
-     * State of the issue; either 'open' or 'closed'
-     */
-    state: string;
-    /**
-     * The reason for the current state
-     */
-    state_reason?: StateReason | null;
-    timeline_url?: string;
-    /**
-     * Title of the issue
-     */
-    title: string;
-    updated_at: Date;
-    /**
-     * URL for the issue
-     */
-    url: string;
-    user: null | TentacledSimpleUser;
-    [property: string]: any;
-}
-
-/**
- * A GitHub user.
- */
-export interface PurpleSimpleUser {
-    avatar_url: string;
-    email?: null | string;
-    events_url: string;
-    followers_url: string;
-    following_url: string;
-    gists_url: string;
-    gravatar_id: null | string;
-    html_url: string;
-    id: number;
-    login: string;
-    name?: null | string;
-    node_id: string;
-    organizations_url: string;
-    received_events_url: string;
-    repos_url: string;
-    site_admin: boolean;
-    starred_at?: string;
-    starred_url: string;
-    subscriptions_url: string;
-    type: string;
-    url: string;
-    [property: string]: any;
-}
-
-/**
- * A GitHub user.
- */
-export interface AssigneeElement {
-    avatar_url: string;
-    email?: null | string;
-    events_url: string;
-    followers_url: string;
-    following_url: string;
-    gists_url: string;
-    gravatar_id: null | string;
-    html_url: string;
-    id: number;
-    login: string;
-    name?: null | string;
-    node_id: string;
-    organizations_url: string;
-    received_events_url: string;
-    repos_url: string;
-    site_admin: boolean;
-    starred_at?: string;
-    starred_url: string;
-    subscriptions_url: string;
-    type: string;
-    url: string;
-    [property: string]: any;
-}
-
-/**
- * How the author is associated with the repository.
- */
-export enum AuthorAssociation {
-    Collaborator = 'COLLABORATOR',
-    Contributor = 'CONTRIBUTOR',
-    FirstTimeContributor = 'FIRST_TIME_CONTRIBUTOR',
-    FirstTimer = 'FIRST_TIMER',
-    Mannequin = 'MANNEQUIN',
-    Member = 'MEMBER',
-    None = 'NONE',
-    Owner = 'OWNER',
-}
-
-/**
- * A GitHub user.
- */
-export interface FluffySimpleUser {
-    avatar_url: string;
-    email?: null | string;
-    events_url: string;
-    followers_url: string;
-    following_url: string;
-    gists_url: string;
-    gravatar_id: null | string;
-    html_url: string;
-    id: number;
-    login: string;
-    name?: null | string;
-    node_id: string;
-    organizations_url: string;
-    received_events_url: string;
-    repos_url: string;
-    site_admin: boolean;
-    starred_at?: string;
-    starred_url: string;
-    subscriptions_url: string;
-    type: string;
-    url: string;
-    [property: string]: any;
-}
-
-export interface LabelObject {
-    color?: null | string;
-    default?: boolean;
-    description?: null | string;
-    id?: number;
-    name?: string;
-    node_id?: string;
-    url?: string;
-    [property: string]: any;
-}
-
-/**
- * A collection of related issues and pull requests.
- */
-export interface Milestone {
-    closed_at: Date | null;
-    closed_issues: number;
-    created_at: Date;
-    creator: null | MilestoneSimpleUser;
-    description: null | string;
-    due_on: Date | null;
-    html_url: string;
-    id: number;
-    labels_url: string;
-    node_id: string;
-    /**
-     * The number of the milestone.
-     */
-    number: number;
-    open_issues: number;
-    /**
-     * The state of the milestone.
-     */
-    state: State;
-    /**
-     * The title of the milestone.
-     */
-    title: string;
-    updated_at: Date;
-    url: string;
-    [property: string]: any;
-}
-
-/**
- * A GitHub user.
- */
-export interface MilestoneSimpleUser {
-    avatar_url: string;
-    email?: null | string;
-    events_url: string;
-    followers_url: string;
-    following_url: string;
-    gists_url: string;
-    gravatar_id: null | string;
-    html_url: string;
-    id: number;
-    login: string;
-    name?: null | string;
-    node_id: string;
-    organizations_url: string;
-    received_events_url: string;
-    repos_url: string;
-    site_admin: boolean;
-    starred_at?: string;
-    starred_url: string;
-    subscriptions_url: string;
-    type: string;
-    url: string;
-    [property: string]: any;
-}
-
-/**
- * The state of the milestone.
- */
-export enum State {
-    Closed = 'closed',
-    Open = 'open',
-}
-
-/**
- * GitHub apps are a new way to extend GitHub. They can be installed directly on
- * organizations and user accounts and granted access to specific repositories. They come
- * with granular permissions and built-in webhooks. GitHub apps are first class actors
- * within GitHub.
- */
-export interface GitHubApp {
-    client_id?: string;
-    client_secret?: string;
-    created_at: Date;
-    description: null | string;
-    /**
-     * The list of events for the GitHub app
-     */
-    events: string[];
-    external_url: string;
-    html_url: string;
-    /**
-     * Unique identifier of the GitHub app
-     */
-    id: number;
-    /**
-     * The number of installations associated with the GitHub app
-     */
-    installations_count?: number;
-    /**
-     * The name of the GitHub app
-     */
-    name: string;
-    node_id: string;
-    owner: null | GitHubAppSimpleUser;
-    pem?: string;
-    /**
-     * The set of permissions for the GitHub app
-     */
-    permissions: GitHubAppPermissions;
-    /**
-     * The slug name of the GitHub app
-     */
-    slug?: string;
-    updated_at: Date;
-    webhook_secret?: null | string;
-    [property: string]: any;
-}
-
-/**
- * A GitHub user.
- */
-export interface GitHubAppSimpleUser {
-    avatar_url: string;
-    email?: null | string;
-    events_url: string;
-    followers_url: string;
-    following_url: string;
-    gists_url: string;
-    gravatar_id: null | string;
-    html_url: string;
-    id: number;
-    login: string;
-    name?: null | string;
-    node_id: string;
-    organizations_url: string;
-    received_events_url: string;
-    repos_url: string;
-    site_admin: boolean;
-    starred_at?: string;
-    starred_url: string;
-    subscriptions_url: string;
-    type: string;
-    url: string;
-    [property: string]: any;
-}
-
-/**
- * The set of permissions for the GitHub app
- */
-export interface GitHubAppPermissions {
-    checks?: string;
-    contents?: string;
-    deployments?: string;
-    issues?: string;
-    metadata?: string;
-}
-
-export interface PullRequest {
-    diff_url: null | string;
-    html_url: null | string;
-    merged_at?: Date | null;
-    patch_url: null | string;
-    url: null | string;
-    [property: string]: any;
-}
-
-export interface ReactionRollup {
-    '+1': number;
-    '-1': number;
-    confused: number;
-    eyes: number;
-    heart: number;
-    hooray: number;
-    laugh: number;
-    rocket: number;
-    total_count: number;
-    url: string;
-    [property: string]: any;
-}
-
-/**
- * A repository on GitHub.
- */
-export interface Repository {
-    /**
-     * Whether to allow Auto-merge to be used on pull requests.
-     */
-    allow_auto_merge?: boolean;
-    /**
-     * Whether to allow forking this repo
-     */
-    allow_forking?: boolean;
-    /**
-     * Whether to allow merge commits for pull requests.
-     */
-    allow_merge_commit?: boolean;
-    /**
-     * Whether to allow rebase merges for pull requests.
-     */
-    allow_rebase_merge?: boolean;
-    /**
-     * Whether to allow squash merges for pull requests.
-     */
-    allow_squash_merge?: boolean;
-    /**
-     * Whether or not a pull request head branch that is behind its base branch can always be
-     * updated even if it is not required to be up to date before merging.
-     */
-    allow_update_branch?: boolean;
-    /**
-     * Whether anonymous git access is enabled for this repository
-     */
-    anonymous_access_enabled?: boolean;
-    archive_url: string;
-    /**
-     * Whether the repository is archived.
-     */
-    archived: boolean;
-    assignees_url: string;
-    blobs_url: string;
-    branches_url: string;
-    clone_url: string;
-    collaborators_url: string;
-    comments_url: string;
-    commits_url: string;
-    compare_url: string;
-    contents_url: string;
-    contributors_url: string;
-    created_at: Date | null;
-    /**
-     * The default branch of the repository.
-     */
-    default_branch: string;
-    /**
-     * Whether to delete head branches when pull requests are merged
-     */
-    delete_branch_on_merge?: boolean;
-    deployments_url: string;
-    description: null | string;
-    /**
-     * Returns whether or not this repository disabled.
-     */
-    disabled: boolean;
-    downloads_url: string;
-    events_url: string;
-    fork: boolean;
-    forks: number;
-    forks_count: number;
-    forks_url: string;
-    full_name: string;
-    git_commits_url: string;
-    git_refs_url: string;
-    git_tags_url: string;
-    git_url: string;
-    /**
-     * Whether discussions are enabled.
-     */
-    has_discussions?: boolean;
-    /**
-     * Whether downloads are enabled.
-     */
-    has_downloads: boolean;
-    /**
-     * Whether issues are enabled.
-     */
-    has_issues: boolean;
-    has_pages: boolean;
-    /**
-     * Whether projects are enabled.
-     */
-    has_projects: boolean;
-    /**
-     * Whether the wiki is enabled.
-     */
-    has_wiki: boolean;
-    homepage: null | string;
-    hooks_url: string;
-    html_url: string;
-    /**
-     * Unique identifier of the repository
-     */
-    id: number;
-    /**
-     * Whether this repository acts as a template that can be used to generate new repositories.
-     */
-    is_template?: boolean;
-    issue_comment_url: string;
-    issue_events_url: string;
-    issues_url: string;
-    keys_url: string;
-    labels_url: string;
-    language: null | string;
-    languages_url: string;
-    license: null | LicenseSimple;
-    master_branch?: string;
-    /**
-     * The default value for a merge commit message.
-     *
-     * - `PR_TITLE` - default to the pull request's title.
-     * - `PR_BODY` - default to the pull request's body.
-     * - `BLANK` - default to a blank commit message.
-     */
-    merge_commit_message?: MergeCommitMessage;
-    /**
-     * The default value for a merge commit title.
-     *
-     * - `PR_TITLE` - default to the pull request's title.
-     * - `MERGE_MESSAGE` - default to the classic title for a merge message (e.g., Merge pull
-     * request #123 from branch-name).
-     */
-    merge_commit_title?: MergeCommitTitle;
-    merges_url: string;
-    milestones_url: string;
-    mirror_url: null | string;
-    /**
-     * The name of the repository.
-     */
-    name: string;
-    network_count?: number;
-    node_id: string;
-    notifications_url: string;
-    open_issues: number;
-    open_issues_count: number;
-    organization?: null | RepositorySimpleUser;
-    /**
-     * A GitHub user.
-     */
-    owner: OwnerObject;
-    permissions?: RepositoryPermissions;
-    /**
-     * Whether the repository is private or public.
-     */
-    private: boolean;
-    pulls_url: string;
-    pushed_at: Date | null;
-    releases_url: string;
-    /**
-     * The size of the repository. Size is calculated hourly. When a repository is initially
-     * created, the size is 0.
-     */
-    size: number;
-    /**
-     * The default value for a squash merge commit message:
-     *
-     * - `PR_BODY` - default to the pull request's body.
-     * - `COMMIT_MESSAGES` - default to the branch's commit messages.
-     * - `BLANK` - default to a blank commit message.
-     */
-    squash_merge_commit_message?: SquashMergeCommitMessage;
-    /**
-     * The default value for a squash merge commit title:
-     *
-     * - `PR_TITLE` - default to the pull request's title.
-     * - `COMMIT_OR_PR_TITLE` - default to the commit's title (if only one commit) or the pull
-     * request's title (when more than one commit).
-     */
-    squash_merge_commit_title?: SquashMergeCommitTitle;
-    ssh_url: string;
-    stargazers_count: number;
-    stargazers_url: string;
-    starred_at?: string;
-    statuses_url: string;
-    subscribers_count?: number;
-    subscribers_url: string;
-    subscription_url: string;
-    svn_url: string;
-    tags_url: string;
-    teams_url: string;
-    temp_clone_token?: string;
-    template_repository?: null | TemplateRepository;
-    topics?: string[];
-    trees_url: string;
-    updated_at: Date | null;
-    url: string;
-    /**
-     * Whether a squash merge commit can use the pull request title as default. **This property
-     * has been deprecated. Please use `squash_merge_commit_title` instead.
-     */
-    use_squash_pr_title_as_default?: boolean;
-    /**
-     * The repository visibility: public, private, or internal.
-     */
-    visibility?: string;
-    watchers: number;
-    watchers_count: number;
-    /**
-     * Whether to require contributors to sign off on web-based commits
-     */
-    web_commit_signoff_required?: boolean;
-    [property: string]: any;
-}
-
-/**
- * License Simple
- */
-export interface LicenseSimple {
-    html_url?: string;
-    key: string;
-    name: string;
-    node_id: string;
-    spdx_id: null | string;
-    url: null | string;
-    [property: string]: any;
-}
-
-/**
- * The default value for a merge commit message.
- *
- * - `PR_TITLE` - default to the pull request's title.
- * - `PR_BODY` - default to the pull request's body.
- * - `BLANK` - default to a blank commit message.
- */
-export enum MergeCommitMessage {
-    Blank = 'BLANK',
-    PRBody = 'PR_BODY',
-    PRTitle = 'PR_TITLE',
-}
-
-/**
- * The default value for a merge commit title.
- *
- * - `PR_TITLE` - default to the pull request's title.
- * - `MERGE_MESSAGE` - default to the classic title for a merge message (e.g., Merge pull
- * request #123 from branch-name).
- */
-export enum MergeCommitTitle {
-    MergeMessage = 'MERGE_MESSAGE',
-    PRTitle = 'PR_TITLE',
-}
-
-/**
- * A GitHub user.
- */
-export interface RepositorySimpleUser {
-    avatar_url: string;
-    email?: null | string;
-    events_url: string;
-    followers_url: string;
-    following_url: string;
-    gists_url: string;
-    gravatar_id: null | string;
-    html_url: string;
-    id: number;
-    login: string;
-    name?: null | string;
-    node_id: string;
-    organizations_url: string;
-    received_events_url: string;
-    repos_url: string;
-    site_admin: boolean;
-    starred_at?: string;
-    starred_url: string;
-    subscriptions_url: string;
-    type: string;
-    url: string;
-    [property: string]: any;
-}
-
-/**
- * A GitHub user.
- */
-export interface OwnerObject {
-    avatar_url: string;
-    email?: null | string;
-    events_url: string;
-    followers_url: string;
-    following_url: string;
-    gists_url: string;
-    gravatar_id: null | string;
-    html_url: string;
-    id: number;
-    login: string;
-    name?: null | string;
-    node_id: string;
-    organizations_url: string;
-    received_events_url: string;
-    repos_url: string;
-    site_admin: boolean;
-    starred_at?: string;
-    starred_url: string;
-    subscriptions_url: string;
-    type: string;
-    url: string;
-    [property: string]: any;
-}
-
-export interface RepositoryPermissions {
-    admin: boolean;
-    maintain?: boolean;
-    pull: boolean;
-    push: boolean;
-    triage?: boolean;
-    [property: string]: any;
-}
-
-/**
- * The default value for a squash merge commit message:
- *
- * - `PR_BODY` - default to the pull request's body.
- * - `COMMIT_MESSAGES` - default to the branch's commit messages.
- * - `BLANK` - default to a blank commit message.
- */
-export enum SquashMergeCommitMessage {
-    Blank = 'BLANK',
-    CommitMessages = 'COMMIT_MESSAGES',
-    PRBody = 'PR_BODY',
-}
-
-/**
- * The default value for a squash merge commit title:
- *
- * - `PR_TITLE` - default to the pull request's title.
- * - `COMMIT_OR_PR_TITLE` - default to the commit's title (if only one commit) or the pull
- * request's title (when more than one commit).
- */
-export enum SquashMergeCommitTitle {
-    CommitOrPRTitle = 'COMMIT_OR_PR_TITLE',
-    PRTitle = 'PR_TITLE',
-}
-
-export interface TemplateRepository {
-    allow_auto_merge?: boolean;
-    allow_merge_commit?: boolean;
-    allow_rebase_merge?: boolean;
-    allow_squash_merge?: boolean;
-    allow_update_branch?: boolean;
-    archive_url?: string;
-    archived?: boolean;
-    assignees_url?: string;
-    blobs_url?: string;
-    branches_url?: string;
-    clone_url?: string;
-    collaborators_url?: string;
-    comments_url?: string;
-    commits_url?: string;
-    compare_url?: string;
-    contents_url?: string;
-    contributors_url?: string;
-    created_at?: string;
-    default_branch?: string;
-    delete_branch_on_merge?: boolean;
-    deployments_url?: string;
-    description?: string;
-    disabled?: boolean;
-    downloads_url?: string;
-    events_url?: string;
-    fork?: boolean;
-    forks_count?: number;
-    forks_url?: string;
-    full_name?: string;
-    git_commits_url?: string;
-    git_refs_url?: string;
-    git_tags_url?: string;
-    git_url?: string;
-    has_downloads?: boolean;
-    has_issues?: boolean;
-    has_pages?: boolean;
-    has_projects?: boolean;
-    has_wiki?: boolean;
-    homepage?: string;
-    hooks_url?: string;
-    html_url?: string;
-    id?: number;
-    is_template?: boolean;
-    issue_comment_url?: string;
-    issue_events_url?: string;
-    issues_url?: string;
-    keys_url?: string;
-    labels_url?: string;
-    language?: string;
-    languages_url?: string;
-    /**
-     * The default value for a merge commit message.
-     *
-     * - `PR_TITLE` - default to the pull request's title.
-     * - `PR_BODY` - default to the pull request's body.
-     * - `BLANK` - default to a blank commit message.
-     */
-    merge_commit_message?: MergeCommitMessage;
-    /**
-     * The default value for a merge commit title.
-     *
-     * - `PR_TITLE` - default to the pull request's title.
-     * - `MERGE_MESSAGE` - default to the classic title for a merge message (e.g., Merge pull
-     * request #123 from branch-name).
-     */
-    merge_commit_title?: MergeCommitTitle;
-    merges_url?: string;
-    milestones_url?: string;
-    mirror_url?: string;
-    name?: string;
-    network_count?: number;
-    node_id?: string;
-    notifications_url?: string;
-    open_issues_count?: number;
-    owner?: Owner;
-    permissions?: TemplateRepositoryPermissions;
-    private?: boolean;
-    pulls_url?: string;
-    pushed_at?: string;
-    releases_url?: string;
-    size?: number;
-    /**
-     * The default value for a squash merge commit message:
-     *
-     * - `PR_BODY` - default to the pull request's body.
-     * - `COMMIT_MESSAGES` - default to the branch's commit messages.
-     * - `BLANK` - default to a blank commit message.
-     */
-    squash_merge_commit_message?: SquashMergeCommitMessage;
-    /**
-     * The default value for a squash merge commit title:
-     *
-     * - `PR_TITLE` - default to the pull request's title.
-     * - `COMMIT_OR_PR_TITLE` - default to the commit's title (if only one commit) or the pull
-     * request's title (when more than one commit).
-     */
-    squash_merge_commit_title?: SquashMergeCommitTitle;
-    ssh_url?: string;
-    stargazers_count?: number;
-    stargazers_url?: string;
-    statuses_url?: string;
-    subscribers_count?: number;
-    subscribers_url?: string;
-    subscription_url?: string;
-    svn_url?: string;
-    tags_url?: string;
-    teams_url?: string;
-    temp_clone_token?: string;
-    topics?: string[];
-    trees_url?: string;
-    updated_at?: string;
-    url?: string;
-    use_squash_pr_title_as_default?: boolean;
-    visibility?: string;
-    watchers_count?: number;
-    [property: string]: any;
-}
-
-export interface Owner {
-    avatar_url?: string;
-    events_url?: string;
-    followers_url?: string;
-    following_url?: string;
-    gists_url?: string;
-    gravatar_id?: string;
-    html_url?: string;
-    id?: number;
-    login?: string;
-    node_id?: string;
-    organizations_url?: string;
-    received_events_url?: string;
-    repos_url?: string;
-    site_admin?: boolean;
-    starred_url?: string;
-    subscriptions_url?: string;
-    type?: string;
-    url?: string;
-    [property: string]: any;
-}
-
-export interface TemplateRepositoryPermissions {
-    admin?: boolean;
-    maintain?: boolean;
-    pull?: boolean;
-    push?: boolean;
-    triage?: boolean;
-    [property: string]: any;
-}
-
-export enum StateReason {
-    Completed = 'completed',
-    NotPlanned = 'not_planned',
-    Reopened = 'reopened',
-}
-
-/**
- * A GitHub user.
- */
-export interface TentacledSimpleUser {
-    avatar_url: string;
-    email?: null | string;
-    events_url: string;
-    followers_url: string;
-    following_url: string;
-    gists_url: string;
-    gravatar_id: null | string;
-    html_url: string;
-    id: number;
-    login: string;
-    name?: null | string;
-    node_id: string;
-    organizations_url: string;
-    received_events_url: string;
-    repos_url: string;
-    site_admin: boolean;
-    starred_at?: string;
-    starred_url: string;
-    subscriptions_url: string;
-    type: string;
-    url: string;
-    [property: string]: any;
-}
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 4557e5866..786154620 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -21,7 +21,7 @@ import { DocumentManager } from '../util/DocumentManager';
 import { GroupManager } from '../util/GroupManager';
 import { HistoryUtil } from '../util/History';
 import { Hypothesis } from '../util/HypothesisUtils';
-import { ReportManager } from '../util/ReportManager';
+import { ReportManager } from '../util/reportManager/ReportManager';
 import { RTFMarkup } from '../util/RTFMarkup';
 import { ScriptingGlobals } from '../util/ScriptingGlobals';
 import { SelectionManager } from '../util/SelectionManager';
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index f1d98d22a..fb8ec93b2 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -235,6 +235,9 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
             bActive,
             textX,
             textY,
+            // pt1,
+            // pt2,
+            // this code adds space between links
             pt1: [pt1[0] + pt1normalized[0] * 13, pt1[1] + pt1normalized[1] * 13],
             pt2: [pt2[0] + pt2normalized[0] * 13, pt2[1] + pt2normalized[1] * 13],
         };
diff --git a/src/client/views/nodes/LoadingBox.scss b/src/client/views/nodes/LoadingBox.scss
index 4c3b8dabe..d4a7e18f2 100644
--- a/src/client/views/nodes/LoadingBox.scss
+++ b/src/client/views/nodes/LoadingBox.scss
@@ -12,6 +12,10 @@
         text-overflow: ellipsis;
         max-width: 80%;
         text-align: center;
+        display: flex;
+        flex-direction: column;
+        gap: 8px;
+        align-items: center;
     }
 }
 
diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx
index 20cf563c1..f131032b5 100644
--- a/src/client/views/topbar/TopBar.tsx
+++ b/src/client/views/topbar/TopBar.tsx
@@ -11,7 +11,7 @@ import { StrCast } from '../../../fields/Types';
 import { GetEffectiveAcl } from '../../../fields/util';
 import { DocumentManager } from '../../util/DocumentManager';
 import { PingManager } from '../../util/PingManager';
-import { ReportManager } from '../../util/ReportManager';
+import { ReportManager } from '../../util/reportManager/ReportManager';
 import { ServerStats } from '../../util/ServerStats';
 import { SettingsManager } from '../../util/SettingsManager';
 import { SharingManager } from '../../util/SharingManager';
-- 
cgit v1.2.3-70-g09d2

