diff --git a/README.md b/README.md index 36283357ae..6c539de219 100644 --- a/README.md +++ b/README.md @@ -121,4 +121,4 @@ Looking to report a security vulnerability? Please don't post about it in GitHub - + diff --git a/frontend/.eslintrc.json b/frontend/.eslintrc.json new file mode 100644 index 0000000000..0c5504b90d --- /dev/null +++ b/frontend/.eslintrc.json @@ -0,0 +1,46 @@ +{ + "extends": "next/core-web-vitals", + "plugins": [ + "simple-import-sort" + ], + "rules": { + "react-hooks/exhaustive-deps": "off", + "simple-import-sort/exports": "warn", + "simple-import-sort/imports": [ + "warn", + { + "groups": [ + // Node.js builtins. You could also generate this regex if you use a `.js` config. + // For example: `^(${require("module").builtinModules.join("|")})(/|$)` + // Note that if you use the `node:` prefix for Node.js builtins, + // you can avoid this complexity: You can simply use "^node:". + [ + "^(assert|buffer|child_process|cluster|console|constants|crypto|dgram|dns|domain|events|fs|http|https|module|net|os|path|punycode|querystring|readline|repl|stream|string_decoder|sys|timers|tls|tty|url|util|vm|zlib|freelist|v8|process|async_hooks|http2|perf_hooks)(/.*|$)" + ], + // Packages `react` related packages + [ + "^react", + "^next", + "^@?\\w" + ], + // Internal packages. + [ + "^~(/.*|$)" + ], + // Relative imports + [ + "^\\.\\.(?!/?$)", + "^\\.\\./?$", + "^\\./(?=.*/)(?!/?$)", + "^\\.(?!/?$)", + "^\\./?$" + ], + // Style imports. + [ + "^.+\\.?(css|scss)$" + ] + ] + } + ] + } +} \ No newline at end of file diff --git a/frontend/components/RouteGuard.js b/frontend/components/RouteGuard.js index bedf124cc9..71bfd25624 100644 --- a/frontend/components/RouteGuard.js +++ b/frontend/components/RouteGuard.js @@ -1,7 +1,9 @@ -import { useState, useEffect } from "react"; -import { useRouter } from "next/router"; -import checkAuth from "../pages/api/auth/CheckAuth"; +import { useEffect,useState } from "react"; import Image from "next/image"; +import { useRouter } from "next/router"; + +import checkAuth from "~/pages/api/auth/CheckAuth"; + import { publicPaths } from "../const"; // #TODO: finish spinner only when the data loads fully @@ -11,9 +13,11 @@ export default function RouteGuard({ children }) { const router = useRouter(); const [authorized, setAuthorized] = useState(false); - useEffect(async () => { + useEffect(() => { // on initial load - run auth check - await authCheck(router.asPath); + (async () => { + await authCheck(router.asPath); + })(); // on route change start - hide page content by setting authorized to false // #TODO: add the loading page when not yet authorized. @@ -31,7 +35,6 @@ export default function RouteGuard({ children }) { router.events.off("routeChangeComplete", authCheck); // router.events.off("routeChangeError", onError); }; - // eslint-disable-next-line react-hooks/exhaustive-deps }, []); @@ -79,4 +82,4 @@ export default function RouteGuard({ children }) { ); } -} \ No newline at end of file +} diff --git a/frontend/components/analytics/posthog.js b/frontend/components/analytics/posthog.js index 5eda4de3a2..c480c4e6b2 100644 --- a/frontend/components/analytics/posthog.js +++ b/frontend/components/analytics/posthog.js @@ -1,4 +1,5 @@ import posthog from "posthog-js"; + import { ENV, POSTHOG_API_KEY, POSTHOG_HOST, TELEMETRY_ENABLED } from "../utilities/config"; export const initPostHog = () => { diff --git a/frontend/components/basic/Error.js b/frontend/components/basic/Error.js index ad058b701e..11b9c6dd31 100644 --- a/frontend/components/basic/Error.js +++ b/frontend/components/basic/Error.js @@ -1,6 +1,6 @@ import React, { useState } from "react"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faExclamationTriangle } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; export default function Error({ text }) { return ( diff --git a/frontend/components/basic/InputField.js b/frontend/components/basic/InputField.js index c61c148a5a..a339b139a4 100644 --- a/frontend/components/basic/InputField.js +++ b/frontend/components/basic/InputField.js @@ -1,6 +1,6 @@ import React from "react"; -import Error from "./Error"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { useState } from "react"; +import { useRouter } from "next/router"; import { faCircle, faCircleExclamation, @@ -8,9 +8,10 @@ import { faEye, faEyeSlash, } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; + import guidGenerator from "../utilities/randomId"; -import { useState } from "react"; -import { useRouter } from "next/router"; +import Error from "./Error"; const InputField = (props) => { const [passwordVisible, setPasswordVisible] = useState(false); diff --git a/frontend/components/basic/Listbox.js b/frontend/components/basic/Listbox.js index c9365c243e..eeb444aa5f 100644 --- a/frontend/components/basic/Listbox.js +++ b/frontend/components/basic/Listbox.js @@ -1,9 +1,9 @@ import React from "react"; -import { Listbox, Transition } from "@headlessui/react"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faCheck, faPlus, faAngleDown } from "@fortawesome/free-solid-svg-icons"; import { Fragment } from "react"; import { useRouter } from "next/router"; +import { faAngleDown,faCheck, faPlus } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { Listbox, Transition } from "@headlessui/react"; /** * This is the component that we use for drop down lists. diff --git a/frontend/components/basic/buttons/Button.js b/frontend/components/basic/buttons/Button.js index cd24f13180..c66a9b990b 100644 --- a/frontend/components/basic/buttons/Button.js +++ b/frontend/components/basic/buttons/Button.js @@ -1,8 +1,8 @@ import React from "react"; -import Link from "next/link"; import Image from "next/image"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import Link from "next/link"; import { faPlus } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; var classNames = require("classnames"); diff --git a/frontend/components/basic/dialog/AddIncidentContactDialog.js b/frontend/components/basic/dialog/AddIncidentContactDialog.js index 3c749d2fa3..4e5004db8c 100644 --- a/frontend/components/basic/dialog/AddIncidentContactDialog.js +++ b/frontend/components/basic/dialog/AddIncidentContactDialog.js @@ -1,8 +1,10 @@ -import { Dialog, Transition } from "@headlessui/react"; import { Fragment, useState } from "react"; -import InputField from "../InputField"; -import addIncidentContact from "../../../pages/api/organization/addIncidentContact"; +import { Dialog, Transition } from "@headlessui/react"; + +import addIncidentContact from "~/pages/api/organization/addIncidentContact"; + import Button from "../buttons/Button"; +import InputField from "../InputField"; const AddIncidentContactDialog = ({ isOpen, diff --git a/frontend/components/basic/dialog/AddProjectMemberDialog.js b/frontend/components/basic/dialog/AddProjectMemberDialog.js index 60712e21cb..a170a4d498 100644 --- a/frontend/components/basic/dialog/AddProjectMemberDialog.js +++ b/frontend/components/basic/dialog/AddProjectMemberDialog.js @@ -1,8 +1,9 @@ -import { Dialog, Transition } from "@headlessui/react"; import { Fragment, useState } from "react"; -import ListBox from "../Listbox"; import { useRouter } from "next/router"; +import { Dialog, Transition } from "@headlessui/react"; + import Button from "../buttons/Button"; +import ListBox from "../Listbox"; const AddProjectMemberDialog = ({ isOpen, diff --git a/frontend/components/basic/dialog/AddServiceTokenDialog.js b/frontend/components/basic/dialog/AddServiceTokenDialog.js index b4c982f891..0e084d29db 100644 --- a/frontend/components/basic/dialog/AddServiceTokenDialog.js +++ b/frontend/components/basic/dialog/AddServiceTokenDialog.js @@ -1,15 +1,17 @@ -import { Dialog, Transition } from "@headlessui/react"; import { Fragment, useState } from "react"; -import ListBox from "../Listbox"; import { useRouter } from "next/router"; -import Button from "../buttons/Button"; -import InputField from "../InputField"; -import getLatestFileKey from "../../../pages/api/workspace/getLatestFileKey"; -import { decryptAssymmetric, encryptAssymmetric } from "../../utilities/crypto"; -import addServiceToken from "../../../pages/api/serviceToken/addServiceToken"; -import nacl from "tweetnacl"; import { faCheck, faCopy } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { Dialog, Transition } from "@headlessui/react"; +import nacl from "tweetnacl"; + +import addServiceToken from "~/pages/api/serviceToken/addServiceToken"; +import getLatestFileKey from "~/pages/api/workspace/getLatestFileKey"; + +import { decryptAssymmetric, encryptAssymmetric } from "../../utilities/crypto"; +import Button from "../buttons/Button"; +import InputField from "../InputField"; +import ListBox from "../Listbox"; const envMapping = { Development: "dev", @@ -28,7 +30,7 @@ const AddServiceTokenDialog = ({ isOpen, closeModal, workspaceId, - workspaceName + workspaceName, }) => { const router = useRouter(); const [serviceToken, setServiceToken] = useState(""); @@ -38,52 +40,52 @@ const AddServiceTokenDialog = ({ const [serviceTokenCopied, setServiceTokenCopied] = useState(false); const generateServiceToken = async () => { - const latestFileKey = await getLatestFileKey(workspaceId); + const latestFileKey = await getLatestFileKey(workspaceId); const key = decryptAssymmetric({ ciphertext: latestFileKey.latestKey.encryptedKey, nonce: latestFileKey.latestKey.nonce, publicKey: latestFileKey.latestKey.sender.publicKey, - privateKey: localStorage.getItem("PRIVATE_KEY") + privateKey: localStorage.getItem("PRIVATE_KEY"), }); // generate new public/private key pair const pair = nacl.box.keyPair(); const publicKey = nacl.util.encodeBase64(pair.publicKey); const privateKey = nacl.util.encodeBase64(pair.secretKey); - + // encrypt workspace key under newly-generated public key const { ciphertext: encryptedKey, nonce } = encryptAssymmetric({ plaintext: key, publicKey, - privateKey + privateKey, }); let newServiceToken = await addServiceToken({ - name: serviceTokenName, - workspaceId, + name: serviceTokenName, + workspaceId, environment: envMapping[serviceTokenEnv], - expiresIn: expiryMapping[serviceTokenExpiresIn], - publicKey, + expiresIn: expiryMapping[serviceTokenExpiresIn], + publicKey, encryptedKey, - nonce - }) - - const serviceToken = newServiceToken + ',' + privateKey; + nonce, + }); + + const serviceToken = newServiceToken + "," + privateKey; setServiceToken(serviceToken); - } + }; function copyToClipboard() { // Get the text field var copyText = document.getElementById("serviceToken"); - + // Select the text field copyText.select(); copyText.setSelectionRange(0, 99999); // For mobile devices - - // Copy the text inside the text field + + // Copy the text inside the text field navigator.clipboard.writeText(copyText.value); - + setServiceTokenCopied(true); setTimeout(() => setServiceTokenCopied(false), 2000); // Alert the copied text @@ -94,7 +96,7 @@ const AddServiceTokenDialog = ({ closeModal(); setServiceTokenName(""); setServiceToken(""); - } + }; return (
@@ -123,18 +125,25 @@ const AddServiceTokenDialog = ({ leaveFrom="opacity-100 scale-100" leaveTo="opacity-0 scale-95" > - {serviceToken == "" - ? + {serviceToken == "" ? ( + - Add a service token for {workspaceName} + Add a service token for{" "} + {workspaceName}

- Specify the name, environment, and expiry period. When a token is generated, you will only be able to see it once before it disappears. Make sure to save it somewhere. + Specify the name, + environment, and expiry + period. When a token is + generated, you will only be + able to see it once before + it disappears. Make sure to + save it somewhere.

@@ -154,7 +163,12 @@ const AddServiceTokenDialog = ({ @@ -162,8 +176,14 @@ const AddServiceTokenDialog = ({
@@ -171,17 +191,24 @@ const AddServiceTokenDialog = ({
- : + ) : ( +

- Once you close this popup, you will never see your service token again + Once you close this popup, + you will never see your + service token again

- -
{serviceToken}
+ +
+ {serviceToken} +
- Click to Copy @@ -211,14 +261,16 @@ const AddServiceTokenDialog = ({
- } + )}
diff --git a/frontend/components/basic/dialog/AddUserDialog.js b/frontend/components/basic/dialog/AddUserDialog.js index eeec682b38..59cc2ec58b 100644 --- a/frontend/components/basic/dialog/AddUserDialog.js +++ b/frontend/components/basic/dialog/AddUserDialog.js @@ -1,9 +1,10 @@ -import { Dialog, Transition } from "@headlessui/react"; import { Fragment } from "react"; -import InputField from "../InputField"; import { useRouter } from "next/router"; -import Button from "../buttons/Button"; +import { Dialog, Transition } from "@headlessui/react"; + import { STRIPE_PRODUCT_STARTER } from "../../utilities/config"; +import Button from "../buttons/Button"; +import InputField from "../InputField"; const AddUserDialog = ({ isOpen, diff --git a/frontend/components/basic/dialog/AddWorkspaceDialog.js b/frontend/components/basic/dialog/AddWorkspaceDialog.js index 7f2908e0af..361aa3f8b4 100644 --- a/frontend/components/basic/dialog/AddWorkspaceDialog.js +++ b/frontend/components/basic/dialog/AddWorkspaceDialog.js @@ -1,9 +1,10 @@ -import { Dialog, Transition } from "@headlessui/react"; import { Fragment, useState } from "react"; -import InputField from "../InputField"; import Image from "next/image"; -import { Checkbox } from "../table/Checkbox"; +import { Dialog, Transition } from "@headlessui/react"; + import Button from "../buttons/Button"; +import InputField from "../InputField"; +import { Checkbox } from "../table/Checkbox"; /** * The dialog modal for when the user wants to create a new workspace diff --git a/frontend/components/basic/dialog/DeleteUserDialog.js b/frontend/components/basic/dialog/DeleteUserDialog.js index 60cad3a7b7..2d97b0ff04 100644 --- a/frontend/components/basic/dialog/DeleteUserDialog.js +++ b/frontend/components/basic/dialog/DeleteUserDialog.js @@ -1,5 +1,6 @@ -import { Dialog, Transition } from '@headlessui/react' import { Fragment, useState } from 'react' +import { Dialog, Transition } from '@headlessui/react' + import InputField from '../InputField'; // #TODO: USE THIS. Currently it's not. Kinda complicated to set up because of state. diff --git a/frontend/components/basic/layout.js b/frontend/components/basic/layout.js index 28557d8539..382eb2777e 100644 --- a/frontend/components/basic/layout.js +++ b/frontend/components/basic/layout.js @@ -1,28 +1,29 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +import { useEffect, useState } from "react"; import Link from "next/link"; import { useRouter } from "next/router"; -import { useEffect, useState } from "react"; - -import NavBarDashboard from "../navigation/NavBarDashboard"; -import Listbox from "./Listbox"; -import getWorkspaces from "../../pages/api/workspace/getWorkspaces"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { - faHouse, - faUser, faGear, - faMobile, + faHouse, faLink, + faMobile, + faUser, } from "@fortawesome/free-solid-svg-icons"; -import AddWorkspaceDialog from "./dialog/AddWorkspaceDialog"; -import createWorkspace from "../../pages/api/workspace/createWorkspace"; -import getOrganizationUserProjects from "../../pages/api/organization/GetOrgUserProjects"; -import getOrganizationUsers from "../../pages/api/organization/GetOrgUsers"; -import addUserToWorkspace from "../../pages/api/workspace/addUserToWorkspace"; -import getOrganizations from "../../pages/api/organization/getOrgs"; import { faPlus } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import getOrganizations from "~/pages/api/organization/getOrgs"; +import getOrganizationUserProjects from "~/pages/api/organization/GetOrgUserProjects"; +import getOrganizationUsers from "~/pages/api/organization/GetOrgUsers"; +import addUserToWorkspace from "~/pages/api/workspace/addUserToWorkspace"; +import createWorkspace from "~/pages/api/workspace/createWorkspace"; +import getWorkspaces from "~/pages/api/workspace/getWorkspaces"; + +import NavBarDashboard from "../navigation/NavBarDashboard"; import { decryptAssymmetric, encryptAssymmetric } from "../utilities/crypto"; import Button from "./buttons/Button"; +import AddWorkspaceDialog from "./dialog/AddWorkspaceDialog"; +import Listbox from "./Listbox"; export default function Layout({ children }) { const router = useRouter(); @@ -48,9 +49,7 @@ export default function Layout({ children }) { setLoading(true); setTimeout(() => setLoading(false), 1500); const workspaces = await getWorkspaces(); - const currentWorkspaces = workspaces.map( - (workspace) => workspace.name - ); + const currentWorkspaces = workspaces.map((workspace) => workspace.name); if (!currentWorkspaces.includes(workspaceName)) { const newWorkspace = await createWorkspace( workspaceName, @@ -141,7 +140,10 @@ export default function Layout({ children }) { useEffect(async () => { // Put a user in a workspace if they're not in one yet - if (localStorage.getItem("orgData.id") == null || localStorage.getItem("orgData.id") == "") { + if ( + localStorage.getItem("orgData.id") == null || + localStorage.getItem("orgData.id") == "" + ) { const userOrgs = await getOrganizations(); localStorage.setItem("orgData.id", userOrgs[0]._id); } @@ -150,7 +152,11 @@ export default function Layout({ children }) { orgId: localStorage.getItem("orgData.id"), }); let userWorkspaces = orgUserProjects; - if (userWorkspaces.length == 0 && (router.asPath != "/noprojects" && !router.asPath.includes("settings"))) { + if ( + userWorkspaces.length == 0 && + router.asPath != "/noprojects" && + !router.asPath.includes("settings") + ) { router.push("/noprojects"); } else if (router.asPath != "/noprojects") { const intendedWorkspaceId = router.asPath @@ -158,7 +164,9 @@ export default function Layout({ children }) { [router.asPath.split("/").length - 1].split("?")[0]; // If a user is not a member of a workspace they are trying to access, just push them to one of theirs - if (intendedWorkspaceId != "heroku" && !userWorkspaces + if ( + intendedWorkspaceId != "heroku" && + !userWorkspaces .map((workspace) => workspace._id) .includes(intendedWorkspaceId) ) { @@ -207,7 +215,10 @@ export default function Layout({ children }) { workspaceMapping[workspaceSelected] + "?Development" ); - localStorage.setItem("projectData.id", workspaceMapping[workspaceSelected]) + localStorage.setItem( + "projectData.id", + workspaceMapping[workspaceSelected] + ); } } catch (error) { console.log(error); @@ -226,8 +237,8 @@ export default function Layout({ children }) {
PROJECT
- {workspaceList.length>0 - ? 0 ? ( + - :
diff --git a/frontend/components/basic/popups/BottomRightPopup.js b/frontend/components/basic/popups/BottomRightPopup.js index 61190a2f6e..d756992907 100644 --- a/frontend/components/basic/popups/BottomRightPopup.js +++ b/frontend/components/basic/popups/BottomRightPopup.js @@ -1,8 +1,6 @@ import React from "react"; +import { faXmark } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { - faXmark, -} from "@fortawesome/free-solid-svg-icons"; export default function BottonRightPopup({ buttonText, @@ -11,22 +9,34 @@ export default function BottonRightPopup({ emoji, textLine1, textLine2, - setCheckDocsPopUpVisible + setCheckDocsPopUpVisible, }) { return ( -