diff --git a/.eslintrc.json b/.eslintrc.json index 310d9826c95..8b137891791 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,11 +1 @@ -{ - "extends": ["next", "prettier", "plugin:prettier/recommended"], - "rules": { - "@next/next/no-img-element": "off", - "unused-imports/no-unused-imports-ts": "error", - "@typescript-eslint/consistent-type-imports": "error", - "no-constant-condition": "warn" - }, - "ignorePatterns": ["node_modules/", ".next/", ".github/"], - "plugins": ["unused-imports", "@typescript-eslint"] -} + diff --git a/package.json b/package.json index ec428a81563..ab97f4552aa 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "lint" ], "dependencies": { + "@cometchat-pro/chat": "3.0.10", "@date-io/date-fns": "^2.15.0", "@emotion/cache": "^11.10.1", "@emotion/react": "^11.10.0", @@ -79,6 +80,7 @@ "react-papaparse": "^4.0.2", "react-qr-reader": "2.2.1", "react-redux": "^8.0.2", + "react-toastify": "^9.1.1", "semver": "^7.3.7" }, "devDependencies": { diff --git a/src/components/chat/folder.tsx b/src/components/chat/folder.tsx new file mode 100644 index 00000000000..01474939f7c --- /dev/null +++ b/src/components/chat/folder.tsx @@ -0,0 +1,113 @@ +import { useEffect, useState, type ReactElement } from 'react' +import Button from '@mui/material/Button' +import AddIcon from '@/public/images/common/add.svg' +import SafeListItem from '../sidebar/SafeListItem' +import SvgIcon from '@mui/material/SvgIcon' +import css from './styles.module.css' + +//@ts-ignore +const Folder = ({group}): ReactElement => { + const [safeAddress, setSafeAddress] = useState(''); + const [safes, setSafes] = useState(['']); + + window?.addEventListener('storage', () => { + const items = JSON.parse(localStorage.getItem(group)!); + // const myArray = items.split(","); + if (items) { + setSafes(items); + } + }) + + useEffect(() => { + const activeGroups = async () =>{ + const items = JSON.parse(localStorage.getItem(group)!); + // const myArray = items.split(","); + if (items) { + setSafes(items); + } + } + activeGroups() + window.addEventListener('storage', activeGroups) + return () => { + window.removeEventListener('storage', activeGroups) + } + }, [localStorage.getItem(group)]); + + const addSafeToFolder = async () => { + const safes = JSON.parse(localStorage.getItem(group)!); + if (safes) { + localStorage.setItem(group, JSON.stringify([...safes, safeAddress])); + } else { + localStorage.setItem(group, JSON.stringify([safeAddress])); + } + window.dispatchEvent(new Event("storage")); + } + + const deleteSafeFromFolder = async () => { + const safes = JSON.parse(localStorage.getItem(group)!); + const updated = safes.filter((address: string) => address !== safeAddress) + if (updated) { + localStorage.setItem(group, JSON.stringify(updated)); + } else { + localStorage.setItem(group, JSON.stringify([safeAddress])); + } + window.dispatchEvent(new Event("storage")); + } + + const handleSetSafeAddress = (address: string) => { + setSafeAddress(address) + } + + const deleteFolder = async () => { + console.log(group) + await localStorage.removeItem(group); + window.dispatchEvent(new Event("storage")); + } + + return ( +
+

{group}

+ +
+ + handleSetSafeAddress(e.target.value)} placeholder={'Safe address'}/> + + +
+ + <> + { + safes.map((safe) => { + return {}} + shouldScrollToSafe={true} + /> + }) + } + +
+ ) +} + +export default Folder diff --git a/src/components/chat/groups.tsx b/src/components/chat/groups.tsx new file mode 100644 index 00000000000..0438c7a8ee1 --- /dev/null +++ b/src/components/chat/groups.tsx @@ -0,0 +1,70 @@ +import { useEffect, useState, type ReactElement } from 'react' +import Track from '@/components/common/Track' +import Button from '@mui/material/Button' +import SvgIcon from '@mui/material/SvgIcon' +import Folder from './folder' +import AddIcon from '@/public/images/common/add.svg' +import css from './styles.module.css' +import { OVERVIEW_EVENTS } from '@/services/analytics' + +const GroupList = (): ReactElement => { + const [groups, setGroups] = useState([]); + const [folderName, setFolderName] = useState(); + + useEffect(() => { + const activeGroups = async () =>{ + const items = JSON.parse(localStorage.getItem('folders')!); + // const myArray = items.split(","); + if (items) { + setGroups(items); + } + } + activeGroups() + window.addEventListener('storage', activeGroups) + return () => { + window.removeEventListener('storage', activeGroups) + } + }, []); + + const createFolder = async () => { + const folders = JSON.parse(localStorage.getItem('folders')!); + localStorage.setItem('folders', JSON.stringify(folders ? [...folders, `${folderName!},`] : [folderName!])); + window.dispatchEvent(new Event("storage")); + } + + const nameFolder = (name: string) => { + setFolderName(name) + } + + return ( +
+
+

+ My Folders +

+ +
+ nameFolder(e.target.value)}/> + +
+ +
+ <> + {groups?.map((group: string) => { + return
+ })} + +
+ ) +} + +export default GroupList diff --git a/src/components/chat/index.tsx b/src/components/chat/index.tsx new file mode 100644 index 00000000000..050dc6c1558 --- /dev/null +++ b/src/components/chat/index.tsx @@ -0,0 +1,245 @@ +import { useEffect, useState, useRef } from 'react' +import { toast } from 'react-toastify' +import css from './styles.module.css' +import useTxHistory from '@/hooks/useTxHistory' +import useWallet from '@/hooks/wallets/useWallet' +import TxListItem from '../transactions/TxListItem' +import { + getMessages, + initCometChat, + listenForMessage, + sendMessage, + createNewGroup, + getGroup, +} from '../../services/chat' +import useTxQueue from '@/hooks/useTxQueue' +import useSafeInfo from '@/hooks/useSafeInfo' + +//@ts-ignore +const Chat = ({ user }) => { + const { safeAddress } = useSafeInfo() + const [message, setMessage] = useState('') + const [messages, setMessages] = useState([]) + const [chatData, setChatData] = useState([]) + const [group, setGroup] = useState() + const wallet = useWallet() + const txHistory = useTxHistory() + const txQueue = useTxQueue() + + const handleSubmit = async (e: any) => { + e.preventDefault() + + if (!message) return + await sendMessage(`pid_${safeAddress}`, message) + .then(async (msg: any) => { + //@ts-ignore + setMessages((prevState) => [...prevState, msg]) + setMessage('') + }) + .catch((error: any) => { + console.log(error) + }) + } + + useEffect(() => { + initCometChat() + async function getM() { + await getMessages(`pid_${safeAddress!}`) + .then((msgs: any) => { + setMessages(msgs) + }) + .catch((error) => console.log(error)) + + await listenForMessage(`pid_${safeAddress!}`) + .then((msg: any) => { + setMessages((prevState) => [...prevState, msg]) + }) + .catch((error) => console.log(error)) + } + getM() + }, [group, wallet, user]) + + useEffect(() => { + if (messages.length == 0) { + return + } + let allData: any[] = [] + messages.forEach((message: any) => { + allData.push({ + data: message, + timestamp: +message.sentAt * 1000, + type: 'message', + }) + }) + txHistory.page?.results.forEach((tx: any) => { + if (tx.type === 'DATE_LABEL') { + return + } + allData.push({ + data: tx, + timestamp: tx.transaction.timestamp, + type: 'tx', + }) + }) + txQueue.page?.results.forEach((tx: any) => { + if (tx.type === 'LABEL') { + return + } + allData.push({ + data: tx, + timestamp: tx.transaction.timestamp, + type: 'tx', + }) + }) + console.log(txQueue, txHistory, 'tx') + allData.sort(function (a, b) { + if (a['timestamp'] > b['timestamp']) { + return 1 + } else if (a['timestamp'] < b['timestamp']) { + return -1 + } else { + return 0 + } + }) + setChatData(allData) + }, [messages]) + + const bottom = useRef(null) + + const scrollToBottom = () => { + //@ts-ignore + bottom.current.scrollIntoView({ behavior: "smooth" }) + } + + useEffect(() => { + scrollToBottom() + }, [messages]); + + const handleCreateGroup = async () => { + if (!user) { + toast.warning('You need to login or sign up first.') + return + } + + await toast.promise( + new Promise(async (resolve, reject) => { + await createNewGroup(`pid_${safeAddress}`, 'safe') + .then((gp) => { + setGroup(gp) + resolve(gp) + }) + .catch((error) => { + reject(new Error(error)) + console.log(error) + }) + }), + { + pending: 'Creating...', + success: 'Group created 👌', + error: 'Encountered error 🤯', + }, + ) + } + + const handleGetGroup = async () => { + if (!user) { + toast.warning('You need to login or sign up first.') + return + } + + await toast.promise( + new Promise(async (resolve, reject) => { + await getGroup(`pid_${safeAddress}`) + .then((gp) => { + setGroup(gp) + resolve(gp) + }) + .catch((error) => console.log(error)) + }), + { + pending: 'Creating...', + success: 'Group created 👌', + error: 'Encountered error 🤯', + }, + ) + } + + useEffect(() => { + if (user) { + handleGetGroup() + } + }, [user]) + + return ( +
+
+
+ {chatData.map((item: any, i) => + item.type === 'message' ? ( + + ) : ( + + ), + )} +
+
+
+
+ setMessage(e.target.value)} + required + /> + +
+
+ + {!group ? ( + <> + + + + ) : null} +
+
+ ) +} + +//@ts-ignore +const Message: any = ({ msg, owner, isOwner, data, timeStamp }) => ( +
+
{ + console.log(data) + }} + > +
+ {timeStamp} + {isOwner ? 'You' : owner} + {msg} +
+
+
+) + +export default Chat \ No newline at end of file diff --git a/src/components/chat/join.tsx b/src/components/chat/join.tsx new file mode 100644 index 00000000000..ea0c133aa23 --- /dev/null +++ b/src/components/chat/join.tsx @@ -0,0 +1,32 @@ +import { toast } from 'react-toastify' +import useSafeAddress from '@/hooks/useSafeAddress' +import { joinGroup } from '../../services/chat' + +const Join: any = ({}) => { + const safeAddress = useSafeAddress() + + const handleJoin = async () => { + await toast.promise( + new Promise(async (resolve, reject) => { + await joinGroup(`pid_${safeAddress}`) + .then((user) => { + console.log(user) + resolve(user) + }) + .catch((err) => { + console.log(err) + reject(err) + }) + }), + { + pending: 'Signing up...', + success: 'Signned up successful 👌', + error: 'Error, maybe you should login instead? 🤯', + }, + ) + } + + return +} + +export default Join diff --git a/src/components/chat/login.tsx b/src/components/chat/login.tsx new file mode 100644 index 00000000000..18547d8efc0 --- /dev/null +++ b/src/components/chat/login.tsx @@ -0,0 +1,66 @@ +import { toast } from 'react-toastify' +import useWallet from '@/hooks/wallets/useWallet' +import { loginWithCometChat, signUpWithCometChat } from '../../services/chat' +import useSafeAddress from '@/hooks/useSafeAddress' +import { useEffect } from 'react' + +//@ts-ignore +const Login = ({ setCurrentUser }) => { + const wallet = useWallet() + const safeAddress = useSafeAddress() + + useEffect(() => { + handleLogin(); + }, [wallet, safeAddress]) + + const handleLogin = async () => { + await toast.promise( + new Promise(async (resolve, reject) => { + await loginWithCometChat(wallet?.address!) + .then((user) => { + setCurrentUser(user) + console.log(user) + }) + .catch((err) => { + console.log(err) + reject() + }) + }), + { + pending: 'Signing in...', + success: 'Logged in successful 👌', + error: 'Error, are you signed up? 🤯', + }, + ) + } + + const handleSignup = async () => { + await toast.promise( + new Promise(async (resolve, reject) => { + await signUpWithCometChat(wallet?.address!) + .then((user) => { + console.log(user) + resolve(user) + }) + .catch((err) => { + console.log(err) + reject(err) + }) + }), + { + pending: 'Signing up...', + success: 'Signned up successful 👌', + error: 'Error, maybe you should login instead? 🤯', + }, + ) + } + + return ( +
+ + +
+ ) +} + +export default Login diff --git a/src/components/chat/styles.module.css b/src/components/chat/styles.module.css new file mode 100644 index 00000000000..5a6b2dd4d8d --- /dev/null +++ b/src/components/chat/styles.module.css @@ -0,0 +1,74 @@ +.chatfullheight { + height: 100%; + overflow: hidden; + overflow-y: scroll; +} + +.messagecontainer { + background: #8080801f; + padding: 14px; + display: flex; + flex-flow: column; + border-radius: 14px; + width: fit-content; + max-width: 100%; +} + +.ownerstylingchat { + font-weight: 600; + word-break: break-all; +} + +.flexmessagewrapper { + gap: 6px; + display: flex; + flex-flow: column; +} + +.chatsendbuttons { + position: sticky; + bottom: 0; + background: var(--color-background-paper); + padding-top: 6px; +} + +.formchatbuttonswrapper { + display: flex; + flex-flow: row; + gap: 8px; +} + +.inputmessage { + background: transparent; + border: 1px solid var(--color-border-light); + border-radius: 14px; + padding: 10px 14px; + font-family: 'DM Sans'; + color: var(--color-text-primary); + outline: none; + font-size: 16px; + width: 100%; +} + +.submitbuttonchat { + padding: 9px 14px; + border-radius: 14px; + border: 0; + background: #2f92f5; + color: #fff; + font-size: 25px; +} + + +/* for groupslist*/ +.container{ + border: 1px solid white; + padding: 1em; + margin-top: 1em; +} + +.group{ + border: 1px solid white; + padding: 1em; + margin-top: 1em; +} \ No newline at end of file diff --git a/src/components/common/ChainSwitcher/index.tsx b/src/components/common/ChainSwitcher/index.tsx index cfe0ff09fd7..f054127e33b 100644 --- a/src/components/common/ChainSwitcher/index.tsx +++ b/src/components/common/ChainSwitcher/index.tsx @@ -32,10 +32,8 @@ const ChainSwitcher = ({ fullWidth }: { fullWidth?: boolean }): ReactElement | n if (!isWrongChain) return null return ( - ) } diff --git a/src/components/common/ChainSwitcher/styles.module.css b/src/components/common/ChainSwitcher/styles.module.css index 54f510d4fb4..38000faaba8 100644 --- a/src/components/common/ChainSwitcher/styles.module.css +++ b/src/components/common/ChainSwitcher/styles.module.css @@ -1,6 +1,5 @@ -.circle { - width: 0.8em; - height: 0.8em; - border-radius: 50%; - margin-left: 0.2em; +.chainswitchbutton { + background-color: rgb(47, 37, 39); + color: rgb(255, 95, 114); + border-color: transparent; } diff --git a/src/components/common/ConnectWallet/ConnectionCenter.tsx b/src/components/common/ConnectWallet/ConnectionCenter.tsx index 21c53bd88dd..e8fb606ec87 100644 --- a/src/components/common/ConnectWallet/ConnectionCenter.tsx +++ b/src/components/common/ConnectWallet/ConnectionCenter.tsx @@ -32,15 +32,9 @@ const ConnectionCenter = (): ReactElement => { return ( <> - - - - Not connected -
- palette.error.main }}> + Connect wallet -
diff --git a/src/components/common/ConnectWallet/styles.module.css b/src/components/common/ConnectWallet/styles.module.css index 592eb7841f3..e640c775ac5 100644 --- a/src/components/common/ConnectWallet/styles.module.css +++ b/src/components/common/ConnectWallet/styles.module.css @@ -8,6 +8,10 @@ align-items: center; text-align: left; gap: var(--space-1); + padding: 4px 11px; + height: 38px; + background: var(--color-background-paper); + border-radius: 6px; } .popoverContainer { diff --git a/src/components/common/Footer/index.tsx b/src/components/common/Footer/index.tsx deleted file mode 100644 index e6227fb676e..00000000000 --- a/src/components/common/Footer/index.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import type { SyntheticEvent, ReactElement } from 'react' -import { Link, Typography } from '@mui/material' -import { useRouter } from 'next/router' -import css from './styles.module.css' -import { useAppDispatch } from '@/store' -import { openCookieBanner } from '@/store/popupSlice' -import { AppRoutes } from '@/config/routes' -import packageJson from '../../../../package.json' -import AppstoreButton from '../AppStoreButton' -import ExternalLink from '../ExternalLink' - -const footerPages = [AppRoutes.welcome, AppRoutes.settings.index] - -const Footer = (): ReactElement | null => { - const router = useRouter() - const dispatch = useAppDispatch() - - if (!footerPages.some((path) => router.pathname.startsWith(path))) { - return null - } - - const onCookieClick = (e: SyntheticEvent) => { - e.preventDefault() - dispatch(openCookieBanner({})) - } - - return ( -
-
    -
  • - ©2022–{new Date().getFullYear()} Safe Ecosystem Foundation -
  • -
  • - - Terms - -
  • -
  • - - Privacy - -
  • -
  • - - Licenses - -
  • -
  • - - Imprint - -
  • -
  • - - Cookie Policy - -  —  - - Preferences - -
  • -
  • - - v{packageJson.version} - -
  • -
  • - -
  • -
-
- ) -} - -export default Footer diff --git a/src/components/common/Footer/styles.module.css b/src/components/common/Footer/styles.module.css deleted file mode 100644 index 9df4569424f..00000000000 --- a/src/components/common/Footer/styles.module.css +++ /dev/null @@ -1,32 +0,0 @@ -.container { - padding: var(--space-2); - font-size: 13px; -} - -.container ul { - display: flex; - flex-wrap: wrap; - list-style: none; - margin: 0; - padding: 0; - justify-content: center; - row-gap: 0.2em; - column-gap: var(--space-2); - align-items: center; -} - -.container li { - padding: 0; - margin: 0; -} - -.container li:not(:last-of-type):after { - content: '|'; - margin-left: var(--space-2); -} - -@media (max-width: 600px) { - .container li:not(:last-of-type):after { - visibility: hidden; - } -} diff --git a/src/components/common/Header/index.tsx b/src/components/common/Header/index.tsx index ba890cb1739..946d9e80505 100644 --- a/src/components/common/Header/index.tsx +++ b/src/components/common/Header/index.tsx @@ -8,11 +8,9 @@ import css from './styles.module.css' import ChainSwitcher from '@/components/common/ChainSwitcher' import ConnectWallet from '@/components/common/ConnectWallet' import NetworkSelector from '@/components/common/NetworkSelector' -import SafeTokenWidget, { getSafeTokenAddress } from '@/components/common/SafeTokenWidget' import NotificationCenter from '@/components/notification-center/NotificationCenter' import { AppRoutes } from '@/config/routes' import useChainId from '@/hooks/useChainId' -import SafeLogo from '@/public/images/logo.svg' import Link from 'next/link' import useSafeAddress from '@/hooks/useSafeAddress' @@ -23,7 +21,6 @@ type HeaderProps = { const Header = ({ onMenuToggle }: HeaderProps): ReactElement => { const chainId = useChainId() const safeAddress = useSafeAddress() - const showSafeToken = safeAddress && !!getSafeTokenAddress(chainId) const router = useRouter() // Logo link: if on Dashboard, link to Welcome, otherwise to the root (which redirects to either Dashboard or Welcome) @@ -44,7 +41,7 @@ const Header = ({ onMenuToggle }: HeaderProps): ReactElement => { @@ -53,12 +50,6 @@ const Header = ({ onMenuToggle }: HeaderProps): ReactElement => { - {showSafeToken && ( -
- -
- )} -
diff --git a/src/components/common/Header/styles.module.css b/src/components/common/Header/styles.module.css index 3dc91216613..a186ff4e3db 100644 --- a/src/components/common/Header/styles.module.css +++ b/src/components/common/Header/styles.module.css @@ -6,14 +6,13 @@ align-items: center; position: relative; border-radius: 0 !important; - background-color: var(--color-background-paper); + background-color: var(--color-background-main); border-bottom: 1px solid var(--color-border-light); } .element { - padding: 0 var(--space-2); + padding: 0 6px; height: 100%; - border-right: 1px solid var(--color-border-light); display: flex; flex-direction: column; justify-content: center; @@ -24,6 +23,7 @@ flex: 1; border: none; align-items: flex-start; + padding-left: var(--space-3); } .logo svg { @@ -38,7 +38,6 @@ .networkSelector { padding-right: 0; - padding-left: 0; border-right: none; } @@ -53,18 +52,10 @@ } @media (max-width: 600px) { - .element { - padding: 0 var(--space-1); - } - .menuButton { padding-left: var(--space-2); } - .networkSelector { - padding-right: 0; - } - .hideMobile { display: none; } diff --git a/src/components/common/MainNavTabs/index.tsx b/src/components/common/MainNavTabs/index.tsx new file mode 100644 index 00000000000..da6b22c9dc8 --- /dev/null +++ b/src/components/common/MainNavTabs/index.tsx @@ -0,0 +1,36 @@ +import Link from 'next/link' +import { Tab, Tabs, Typography } from '@mui/material' +import { useRouter } from 'next/router' +import type { NavItem } from '@/components/sidebar/SidebarNavigation/config' + +import css from './styles.module.css' + +const MainNavTabs = ({ tabs }: { tabs: NavItem[] }) => { + const router = useRouter() + const activeTab = tabs.map((tab) => tab.href).indexOf(router.pathname) + + return ( + + {tabs.map((tab, idx) => { + return ( + + + {tab.label} + + } + /> + + ) + })} + + ) +} + +export default MainNavTabs diff --git a/src/components/common/MainNavTabs/styles.module.css b/src/components/common/MainNavTabs/styles.module.css new file mode 100644 index 00000000000..2cf32fc0ba8 --- /dev/null +++ b/src/components/common/MainNavTabs/styles.module.css @@ -0,0 +1,28 @@ +.tabs { + overflow: initial; + width: 100%; +} + +.tabs :global .MuiTabs-scroller { + margin: auto; + width: 100% +} + +.tabs :global .MuiTabs-scroller .MuiTabs-indicator { + background-color: primary.text; +} + +.tab { + opacity: 1; + padding: 0; + min-width: 0; + margin-right: 24px; + position: relative; + z-index: 2; +} + +.label { + text-transform: none; + padding-bottom: 20px; + font-size: 16px; +} diff --git a/src/components/common/NetworkSelector/index.tsx b/src/components/common/NetworkSelector/index.tsx index 15d527a5bde..677ad5600b4 100644 --- a/src/components/common/NetworkSelector/index.tsx +++ b/src/components/common/NetworkSelector/index.tsx @@ -70,7 +70,7 @@ const NetworkSelector = (): ReactElement => { {configs.map((chain) => { return ( - + ) })} diff --git a/src/components/common/NetworkSelector/styles.module.css b/src/components/common/NetworkSelector/styles.module.css index 76f06608f7c..46cbd1b3799 100644 --- a/src/components/common/NetworkSelector/styles.module.css +++ b/src/components/common/NetworkSelector/styles.module.css @@ -14,12 +14,21 @@ .select :global .MuiSelect-select { padding-right: 40px !important; - padding-left: 16px; height: 100%; display: flex; align-items: center; } +.ChainIndicator { + height: 38px; + font-size: 14px; + justify-content: center; + align-items: center; + display: flex; + padding: 8px 16px; + border-radius: 6px; +} + .select :global .MuiSvgIcon-root { margin-right: var(--space-2); } diff --git a/src/components/common/PageLayout/SideDrawer.tsx b/src/components/common/PageLayout/SideDrawer.tsx index fb91ae2d776..4434b72a16d 100644 --- a/src/components/common/PageLayout/SideDrawer.tsx +++ b/src/components/common/PageLayout/SideDrawer.tsx @@ -27,6 +27,7 @@ export const isNoSidebarRoute = (pathname: string): boolean => { AppRoutes.newSafe.load, AppRoutes.welcome, AppRoutes.index, + ].includes(pathname) } @@ -46,6 +47,7 @@ const SideDrawer = ({ isOpen, onToggle }: SideDrawerProps): ReactElement => { <> onToggle(false)} diff --git a/src/components/common/PageLayout/index.tsx b/src/components/common/PageLayout/index.tsx index 0193e500918..8d209048b2e 100644 --- a/src/components/common/PageLayout/index.tsx +++ b/src/components/common/PageLayout/index.tsx @@ -1,13 +1,19 @@ import { useState, type ReactElement } from 'react' import classnames from 'classnames' +import { + Grid, +} from '@mui/material' +import SafeList from '@/components/sidebar/SafeList' + import Header from '@/components/common//Header' import css from './styles.module.css' import SafeLoadingError from '../SafeLoadingError' -import Footer from '../Footer' import SideDrawer, { isNoSidebarRoute } from './SideDrawer' import PsaBanner from '../PsaBanner' +import StickyNav from '@/components/dashboard/Overview/Overview' + const PageLayout = ({ pathname, children }: { pathname: string; children: ReactElement }): ReactElement => { const [isSidebarOpen, setSidebarOpen] = useState(!isNoSidebarRoute(pathname)) @@ -17,16 +23,24 @@ const PageLayout = ({ pathname, children }: { pathname: string; children: ReactE
- + -
-
- {children} -
- -
-
+ + + + + +
+
+
+ +
+ {children} +
+
+
+
) } diff --git a/src/components/common/PageLayout/styles.module.css b/src/components/common/PageLayout/styles.module.css index 030a6f910a8..49025e9e99d 100644 --- a/src/components/common/PageLayout/styles.module.css +++ b/src/components/common/PageLayout/styles.module.css @@ -6,9 +6,13 @@ z-index: 1201; } +.stickynav { + padding: 24px 24px 0 24px; + width: 100%; +} + .main { background-color: var(--color-background-main); - padding-left: 230px; padding-top: var(--header-height); min-height: 100vh; display: flex; @@ -16,6 +20,22 @@ transition: padding 225ms cubic-bezier(0, 0, 0.2, 1) 0ms; } +.gridsidecontainer { + padding: 0; + background-color: var(--color-background-main); +} + +.sidebar { + margin-left: 48px; + margin-top: 108px; + height: calc(100vh - 84px - 24px); + background: var(--color-background-paper); + padding: 0!important; + border-radius: 6px; + position: sticky; + top: 84px; +} + .mainNoSidebar { padding-left: 0; } @@ -69,7 +89,23 @@ background-color: var(--color-background-light); } +@media (min-width: 901px) { + .mainview { + width: calc(100vw - 364px); + padding-left: 0!important; + } + .drawermo { + display:none; + } +} + @media (max-width: 900px) { + .mainview { + width: 100%; + } + .sidebar { + display: none; + } .main { padding-left: 0; } diff --git a/src/components/dashboard/FeaturedApps/FeaturedApps.tsx b/src/components/dashboard/FeaturedApps/FeaturedApps.tsx index 4f93a150c6e..9a4db55bc86 100644 --- a/src/components/dashboard/FeaturedApps/FeaturedApps.tsx +++ b/src/components/dashboard/FeaturedApps/FeaturedApps.tsx @@ -26,9 +26,6 @@ export const FeaturedApps = (): ReactElement | null => { return ( - - Connect & transact - {featuredApps?.map((app) => ( diff --git a/src/components/dashboard/GovernanceSection/GovernanceSection.tsx b/src/components/dashboard/GovernanceSection/GovernanceSection.tsx deleted file mode 100644 index b8606366e08..00000000000 --- a/src/components/dashboard/GovernanceSection/GovernanceSection.tsx +++ /dev/null @@ -1,147 +0,0 @@ -import { useRef } from 'react' -import { Typography, Card, Box, Alert, IconButton, Link, SvgIcon } from '@mui/material' -import { WidgetBody } from '@/components/dashboard/styled' -import ExpandMoreIcon from '@mui/icons-material/ExpandMore' -import Accordion from '@mui/material/Accordion' -import AccordionSummary from '@mui/material/AccordionSummary' -import AccordionDetails from '@mui/material/AccordionDetails' -import css from './styles.module.css' -import { useBrowserPermissions } from '@/hooks/safe-apps/permissions' -import { useRemoteSafeApps } from '@/hooks/safe-apps/useRemoteSafeApps' -import { SafeAppsTag, SAFE_APPS_SUPPORT_CHAT_URL } from '@/config/constants' -import { useDarkMode } from '@/hooks/useDarkMode' -import { OpenInNew } from '@mui/icons-material' -import NetworkError from '@/public/images/common/network-error.svg' -import useChainId from '@/hooks/useChainId' -import { getSafeTokenAddress } from '@/components/common/SafeTokenWidget' -import SafeAppIframe from '@/components/safe-apps/AppFrame/SafeAppIframe' -import type { UseAppCommunicatorHandlers } from '@/components/safe-apps/AppFrame/useAppCommunicator' -import useAppCommunicator from '@/components/safe-apps/AppFrame/useAppCommunicator' -import { useCurrentChain } from '@/hooks/useChains' -import useGetSafeInfo from '@/components/safe-apps/AppFrame/useGetSafeInfo' -import type { SafeAppData } from '@gnosis.pm/safe-react-gateway-sdk' -import useSafeInfo from '@/hooks/useSafeInfo' -import { fetchSafeAppFromManifest } from '@/services/safe-apps/manifest' -import useAsync from '@/hooks/useAsync' -import { getOrigin } from '@/components/safe-apps/utils' - -// A fallback component when the Safe App fails to load -const WidgetLoadErrorFallback = () => ( - - - - - Couldn't load governance widgets - - - - You can try to reload the page and in case the problem persists, please reach out to us via{' '} - - Discord - - - - - - -) - -// A mini Safe App frame with a minimal set of communication handlers -const MiniAppFrame = ({ app, title }: { app: SafeAppData; title: string }) => { - const chain = useCurrentChain() - const isDarkMode = useDarkMode() - const theme = isDarkMode ? 'dark' : 'light' - const { getAllowedFeaturesList } = useBrowserPermissions() - const iframeRef = useRef(null) - - const [, error] = useAsync(() => { - if (!chain?.chainId) return - return fetchSafeAppFromManifest(app.url, chain.chainId) - }, [app.url, chain?.chainId]) - - // Initialize the app communicator - useAppCommunicator(iframeRef, app, chain, { - onGetSafeInfo: useGetSafeInfo(), - } as Partial as UseAppCommunicatorHandlers) - - return error ? ( - - ) : ( - - ) -} - -// Entire section for the governance widgets -const GovernanceSection = () => { - const [matchingApps, errorFetchingClaimingSafeApp] = useRemoteSafeApps(SafeAppsTag.SAFE_CLAIMING_APP) - const claimingApp = matchingApps?.[0] - const fetchingSafeClaimingApp = !claimingApp && !errorFetchingClaimingSafeApp - const { safeLoading } = useSafeInfo() - - return ( - - - - - } - > -
- - Governance - - - Use your SAFE tokens to vote on important proposals or participate in forum discussions. - -
-
- - ({ padding: `0 ${spacing(3)}` })}> - {claimingApp || fetchingSafeClaimingApp ? ( - - - {claimingApp && !safeLoading ? ( - - ) : ( - - - Loading section... - - - )} - - - ) : ( - - There was an error fetching the Governance section. Please reload the page. - - )} - -
- ) -} - -// Prevent `GovernanceSection` hooks from needlessly being called -const GovernanceSectionWrapper = () => { - const chainId = useChainId() - if (!getSafeTokenAddress(chainId)) { - return null - } - - return -} - -export default GovernanceSectionWrapper diff --git a/src/components/dashboard/GovernanceSection/styles.module.css b/src/components/dashboard/GovernanceSection/styles.module.css deleted file mode 100644 index 5ea0cd9502a..00000000000 --- a/src/components/dashboard/GovernanceSection/styles.module.css +++ /dev/null @@ -1,66 +0,0 @@ -.accordion { - box-shadow: none; - border: none; - background: transparent; -} - -.accordion :global .MuiAccordionSummary-root, -.accordion :global .MuiAccordionDetails-root { - padding: 0; -} - -.accordion:hover :global .MuiAccordionSummary-root, -.accordion :global .MuiAccordionSummary-root:hover, -.accordion :global .Mui-expanded.MuiAccordionSummary-root { - background: inherit; -} - -.accordion :global .MuiAccordionSummary-root { - pointer-events: none; -} - -.accordion :global .MuiAccordionSummary-expandIconWrapper { - pointer-events: auto; -} - -.widgetWrapper { - border: none; - height: 300px; -} - -/* iframe sm breakpoint + paddings */ -@media (max-width: 662px) { - .widgetWrapper { - height: 624px; - } -} - -.loadErrorCard { - display: flex; - justify-content: center; - align-items: center; - padding: var(--space-2); - text-align: center; - flex-grow: 1; -} - -.loadErrorCard:last-of-type { - min-width: 300px; -} - -.loadErrorMsgContainer { - height: 100%; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: var(--space-2); - max-width: 80%; -} - -.loadErroricon { - font-size: 54px; - position: relative; - left: 3px; - top: 3px; -} diff --git a/src/components/dashboard/HomeSidebar/ownerlist.tsx b/src/components/dashboard/HomeSidebar/ownerlist.tsx new file mode 100644 index 00000000000..9e26c18be88 --- /dev/null +++ b/src/components/dashboard/HomeSidebar/ownerlist.tsx @@ -0,0 +1,20 @@ +import type { NextPage } from 'next' +import { Grid, Paper, SvgIcon, Tooltip, Typography } from '@mui/material' +import { OwnerList } from '@/components/settings/owner/OwnerList' +import useSafeInfo from '@/hooks/useSafeInfo' +import useIsGranted from '@/hooks/useIsGranted' + +const Owner: NextPage = () => { + const { safe } = useSafeInfo() + const isGranted = useIsGranted() + + return ( + <> + + + + + ) +} + +export default Owner diff --git a/src/components/dashboard/Overview/Overview.tsx b/src/components/dashboard/Overview/Overview.tsx index 59a77c1babd..59a22d541dd 100644 --- a/src/components/dashboard/Overview/Overview.tsx +++ b/src/components/dashboard/Overview/Overview.tsx @@ -1,5 +1,4 @@ import type { ReactElement } from 'react' -import { useMemo } from 'react' import { useRouter } from 'next/router' import Link from 'next/link' import styled from '@emotion/styled' @@ -10,17 +9,22 @@ import { useCurrentChain } from '@/hooks/useChains' import SafeIcon from '@/components/common/SafeIcon' import ChainIndicator from '@/components/common/ChainIndicator' import EthHashInfo from '@/components/common/EthHashInfo' -import { AppRoutes } from '@/config/routes' import useSafeAddress from '@/hooks/useSafeAddress' -import useCollectibles from '@/hooks/useCollectibles' -import type { UrlObject } from 'url' -import { useVisibleBalances } from '@/hooks/useVisibleBalances' + +import { AppRoutes } from '@/config/routes' + +import { navItems } from '@/components/sidebar/SidebarNavigation/config' +import MainNavTabs from '@/components/common/MainNavTabs' const IdenticonContainer = styled.div` position: relative; margin-bottom: var(--space-2); ` +const StyledCard = styled(Card)` + padding-bottom: 0; +` + const StyledText = styled(Typography)` margin-top: 8px; font-size: 24px; @@ -36,7 +40,7 @@ const NetworkLabelContainer = styled.div` bottom: auto; } ` - + const ValueSkeleton = () => const SkeletonOverview = ( @@ -78,93 +82,43 @@ const SkeletonOverview = (
) +const footerPages = [AppRoutes.home, AppRoutes.balances.index, AppRoutes.addressBook, AppRoutes.apps, AppRoutes.settings.index, AppRoutes.transactions.index] -const Overview = (): ReactElement => { +const StickyNav = (): ReactElement | null => { const router = useRouter() const safeAddress = useSafeAddress() const { safe, safeLoading } = useSafeInfo() - const { balances } = useVisibleBalances() - const [nfts] = useCollectibles() const chain = useCurrentChain() const { chainId } = chain || {} - const assetsLink: UrlObject = { - pathname: AppRoutes.balances.index, - query: { safe: router.query.safe }, - } - const nftsLink: UrlObject = { - pathname: AppRoutes.balances.nfts, - query: { safe: router.query.safe }, + if (!footerPages.some((path) => router.pathname.startsWith(path))) { + return null } - // Native token is always returned even when its balance is 0 - const tokenCount = useMemo(() => balances.items.filter((token) => token.balance !== '0').length, [balances]) - const nftsCount = useMemo(() => (nfts ? `${nfts.next ? '>' : ''}${nfts.results.length}` : ''), [nfts]) - return ( - - Overview - - {safeLoading ? ( SkeletonOverview ) : ( - + - - - - - - - - - - - - - - Tokens - - {tokenCount} - - - - - - - - - NFTs - - {nftsCount || } - - - - - + - - - - - + - + + )} ) } -export default Overview +export default StickyNav diff --git a/src/components/dashboard/index.tsx b/src/components/dashboard/index.tsx index 626499d1fd1..1335433630a 100644 --- a/src/components/dashboard/index.tsx +++ b/src/components/dashboard/index.tsx @@ -1,12 +1,17 @@ import type { ReactElement } from 'react' import { Grid } from '@mui/material' -import PendingTxsList from '@/components/dashboard/PendingTxs/PendingTxsList' -import Overview from '@/components/dashboard/Overview/Overview' +import styled from '@emotion/styled' import { FeaturedApps } from '@/components/dashboard/FeaturedApps/FeaturedApps' -import SafeAppsDashboardSection from '@/components/dashboard/SafeAppsDashboardSection/SafeAppsDashboardSection' -import GovernanceSection from '@/components/dashboard/GovernanceSection/GovernanceSection' import CreationDialog from '@/components/dashboard/CreationDialog' import { useRouter } from 'next/router' +import Owner from '@/components/dashboard/HomeSidebar/ownerlist' +import Home from '@/pages/chat/chat' + +const StyledGrid = styled(Grid)` + display: flex; + flex-flow: column; + gap: 24px; +` const Dashboard = (): ReactElement => { const router = useRouter() @@ -15,25 +20,14 @@ const Dashboard = (): ReactElement => { return ( <> - - - - - - + + - + + - - - - - - - - - + {showCreationModal ? : null} diff --git a/src/components/notification-center/NotificationCenter/styles.module.css b/src/components/notification-center/NotificationCenter/styles.module.css index 82d3f956272..782deba0474 100644 --- a/src/components/notification-center/NotificationCenter/styles.module.css +++ b/src/components/notification-center/NotificationCenter/styles.module.css @@ -1,7 +1,9 @@ .bell { display: flex; justify-content: center; - padding: 4px; + background: var(--color-background-paper); + padding: 11px; + border-radius: 6px; } .bell svg path { diff --git a/src/components/sidebar/SafeList/index.tsx b/src/components/sidebar/SafeList/index.tsx index 571cef06bc4..f3b3a786f73 100644 --- a/src/components/sidebar/SafeList/index.tsx +++ b/src/components/sidebar/SafeList/index.tsx @@ -24,7 +24,6 @@ import SafeListItem from '@/components/sidebar/SafeListItem' import { AppRoutes } from '@/config/routes' import css from './styles.module.css' import { sameAddress } from '@/utils/addresses' -import ChainIndicator from '@/components/common/ChainIndicator' import useSafeInfo from '@/hooks/useSafeInfo' import Track from '@/components/common/Track' import { OVERVIEW_EVENTS } from '@/services/analytics/events/overview' @@ -41,7 +40,7 @@ export const _shouldExpandSafeList = ({ ownedSafesOnChain: string[] addedSafesOnChain: AddedSafesOnChain }): boolean => { - let shouldExpand = false + let shouldExpand = false const addedAddressesOnChain = Object.keys(addedSafesOnChain) @@ -56,7 +55,7 @@ export const _shouldExpandSafeList = ({ return shouldExpand } -const MAX_EXPANDED_SAFES = 3 +const MAX_EXPANDED_SAFES = 99 const NO_SAFE_MESSAGE = 'Create a new safe or add' const SafeList = ({ closeDrawer }: { closeDrawer?: () => void }): ReactElement => { @@ -78,14 +77,15 @@ const SafeList = ({ closeDrawer }: { closeDrawer?: () => void }): ReactElement = return (
- +

My Safes - +

{!isWelcomePage && (