diff --git a/src/components/auth-sign-in/auth-link.tsx b/src/components/auth-sign-in/auth-link.tsx index e208719f553..82da6b514d2 100644 --- a/src/components/auth-sign-in/auth-link.tsx +++ b/src/components/auth-sign-in/auth-link.tsx @@ -1,78 +1,78 @@ -import { Link } from '@mui/material' -import { useState, useEffect } from 'react' -import useWallet from '@/hooks/wallets/useWallet' -import { createWeb3 } from '@/hooks/wallets/web3' -import { authenticateWallet } from './helpers' -import { setCookie } from 'typescript-cookie'; - -export const SignInLink: React.FC<{ - setAuth: any -}> = ({ setAuth }) => { - const [loading, setLoading] = useState(false) - const [nonce, setNonce] = useState('') - - const wallet = useWallet() - - useEffect(() => { - const handleNonce = async() => { - await handleGetNonce() - } - if (wallet?.address) { - handleNonce() - } - }, [wallet?.address]) - - const handleGetNonce = async () => { - try { - const address = wallet?.address - const response = await fetch('/api/nonce', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ address }), - }); - - if (!response.ok) { - throw new Error('Failed to get nonce.'); - } - - const data = await response.json(); - console.log(data, 'data') - setNonce(data.nonce); - } catch (error: any) { - console.error('Error fetching nonce:', error.message); - } - }; - - const handleAuthenticate = async () => { - if (!wallet) return - const address = wallet?.address - const provider = createWeb3(wallet?.provider) - const token = await authenticateWallet(provider, nonce) - - const loginResponse = await fetch('/api/login', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ address, token, nonce }), - }); - - const data = await loginResponse.json(); - setCookie(address, data.token, { path: '/' }); - if (token.length) { - setAuth(true) - setLoading(false) - } - } - - return ( - <> - { nonce ? Verify here : 'loading...' } - - - ) -} - -export default SignInLink +import { Link } from '@mui/material' +import { useState, useEffect } from 'react' +import useWallet from '@/hooks/wallets/useWallet' +import { createWeb3 } from '@/hooks/wallets/web3' +import { authenticateWallet } from './helpers' +import { setCookie } from 'typescript-cookie'; + +export const SignInLink: React.FC<{ + setAuth: any +}> = ({ setAuth }) => { + const [loading, setLoading] = useState(false) + const [nonce, setNonce] = useState('') + + const wallet = useWallet() + + useEffect(() => { + const handleNonce = async() => { + await handleGetNonce() + } + if (wallet?.address) { + handleNonce() + } + }, [wallet?.address]) + + const handleGetNonce = async () => { + try { + const address = wallet?.address + const response = await fetch('/api/nonce', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ address }), + }); + + if (!response.ok) { + throw new Error('Failed to get nonce.'); + } + + const data = await response.json(); + console.log(data, 'data') + setNonce(data.nonce); + } catch (error: any) { + console.error('Error fetching nonce:', error.message); + } + }; + + const handleAuthenticate = async () => { + if (!wallet) return + const address = wallet?.address + const provider = createWeb3(wallet?.provider) + const token = await authenticateWallet(provider, nonce) + + const loginResponse = await fetch('/api/login', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ address, token, nonce }), + }); + + const data = await loginResponse.json(); + setCookie(address, data.token, { path: '/' }); + if (token.length) { + setAuth(true) + setLoading(false) + } + } + + return ( + <> + { nonce ? Verify here : 'loading...' } + + + ) +} + +export default SignInLink diff --git a/src/components/auth-sign-in/helpers.ts b/src/components/auth-sign-in/helpers.ts index 8df91846b54..b0ad268fd7c 100644 --- a/src/components/auth-sign-in/helpers.ts +++ b/src/components/auth-sign-in/helpers.ts @@ -1,34 +1,34 @@ -import { createToken, verifyToken } from '../../utils/did'; -import { - clearToken, - getTokenFromStore, - setTokenInStore, -} from '../../lib/auth'; -import type { Web3Provider } from '@ethersproject/providers'; - -export async function getExistingAuth( - ethersProvider: Web3Provider, - connectedAddress: string, -): Promise { - const token = getTokenFromStore(); - if (!token) return null; - - try { - await verifyToken(token, ethersProvider, connectedAddress); - return token; - } catch (e) { - clearToken(); - return null; - } -} - -export async function authenticateWallet( - ethersProvider: Web3Provider, - msg: string, -): Promise { - console.log(msg, 'msg') - const token = await createToken(ethersProvider, msg); - console.log('token', token) - setTokenInStore(token); - return token; +import { createToken, verifyToken } from '../../utils/did'; +import { + clearToken, + getTokenFromStore, + setTokenInStore, +} from '../../lib/auth'; +import type { Web3Provider } from '@ethersproject/providers'; + +export async function getExistingAuth( + ethersProvider: Web3Provider, + connectedAddress: string, +): Promise { + const token = getTokenFromStore(); + if (!token) return null; + + try { + await verifyToken(token, ethersProvider, connectedAddress); + return token; + } catch (e) { + clearToken(); + return null; + } +} + +export async function authenticateWallet( + ethersProvider: Web3Provider, + msg: string, +): Promise { + console.log(msg, 'msg') + const token = await createToken(ethersProvider, msg); + console.log('token', token) + setTokenInStore(token); + return token; } \ No newline at end of file diff --git a/src/components/chat/AppsSection.tsx b/src/components/chat/AppsSection.tsx new file mode 100644 index 00000000000..3e4e9601a75 --- /dev/null +++ b/src/components/chat/AppsSection.tsx @@ -0,0 +1,27 @@ +import css from '@/components/chat/styles.module.css' +import { useAppDispatch } from "@/store" +import { openModal } from "@/store/modalServiceSlice" +import { Box, Button, Typography } from "@mui/material" +import { modalTypes } from "./modals" + +const AppsSection = () => { + const dispatch = useAppDispatch() + return ( + + + Apps + + + Explore the Safe Apps ecosystem — connect to your favourite web3 applications with your Safe wallet, + securely and efficiently + + {/* */} + + {/* */} + + ) +} + +export default AppsSection \ No newline at end of file diff --git a/src/components/chat/AssetsSection.tsx b/src/components/chat/AssetsSection.tsx new file mode 100644 index 00000000000..767c83f13f2 --- /dev/null +++ b/src/components/chat/AssetsSection.tsx @@ -0,0 +1,29 @@ +import css from '@/components/chat/styles.module.css'; +import { useAppDispatch } from '@/store'; +import { openModal } from '@/store/modalServiceSlice'; +import { Box, Button, Typography } from "@mui/material"; +import { modalTypes } from './modals'; + +const AssetsSection = () => { + const dispatch = useAppDispatch() + return ( + + + Assets + + View all tokens and NFTs the Safe holds. + {/* */} + + {/* */} + + ) +} + +export default AssetsSection \ No newline at end of file diff --git a/src/components/chat/ModalListContextMenu.tsx b/src/components/chat/ModalListContextMenu.tsx index b79ce78754a..0810a841188 100644 --- a/src/components/chat/ModalListContextMenu.tsx +++ b/src/components/chat/ModalListContextMenu.tsx @@ -1,6 +1,8 @@ import ContextMenu from '@/components/common/ContextMenu' -import AddIcon from '@/public/images/common/add.svg' import AddChatIcon from '@/public/images/chat/add-chat-icon.svg' +import AddIcon from '@/public/images/common/add.svg' +import { useAppDispatch } from '@/store' +import { openModal } from '@/store/modalServiceSlice' import { SvgIcon } from '@mui/material' import IconButton from '@mui/material/IconButton' import ListItemIcon from '@mui/material/ListItemIcon' @@ -8,16 +10,16 @@ import ListItemText from '@mui/material/ListItemText' import MenuItem from '@mui/material/MenuItem' import type { MouseEvent } from 'react' import { useState } from 'react' -import { AddFolderModal } from './modals/AddFolderModal' +import { modalTypes } from './modals' enum ModalType { ADDFOLDER = 'Add Folder', ADDSAFE = 'Add Safe', } -const ModalListContextMenu: React.FC<{ createSafe: boolean, setCreateSafe: any }> = ({ createSafe, setCreateSafe }) => { +const ModalListContextMenu = () => { const [anchorEl, setAnchorEl] = useState() - const [addFolder, setAddFolder] = useState(false) + const dispatch = useAppDispatch() const handleOpenContextMenu = (e: MouseEvent) => { setAnchorEl(e.currentTarget) @@ -29,7 +31,6 @@ const ModalListContextMenu: React.FC<{ createSafe: boolean, setCreateSafe: any } return ( <> - {addFolder && setAddFolder(!addFolder)} />} setAddFolder(!addFolder)} + onClick={() => dispatch(openModal({ modalName: modalTypes.addFolderModal, modalProps: '' }))} > @@ -47,7 +48,7 @@ const ModalListContextMenu: React.FC<{ createSafe: boolean, setCreateSafe: any } {ModalType.ADDFOLDER} setCreateSafe(!createSafe)} + onClick={() => dispatch(openModal({ modalName: modalTypes.createSafe, modalProps: '' }))} > diff --git a/src/components/chat/OverviewDisplay.tsx b/src/components/chat/OverviewDisplay.tsx new file mode 100644 index 00000000000..77ec0e8ce41 --- /dev/null +++ b/src/components/chat/OverviewDisplay.tsx @@ -0,0 +1,25 @@ +import useSafeInfo from "@/hooks/useSafeInfo"; +import { Box } from "@mui/material"; +import Members from "../common/Members"; +import TransactionHistory from "../common/TransactionHistory"; +import TransactionQueue from "../common/TransactionQueue"; +import AppsSection from "./AppsSection"; +import AssetsSection from "./AssetsSection"; +import type { Pages } from "./OverviewDrawer"; +import OverviewSection from "./OverviewSection"; + +const OverviewDisplay = ({ page, owners }: { page: Pages, owners: any[] }) => { + const { safe, safeAddress } = useSafeInfo() + const currentView = { + Overview: , + Members: , + TransactionQueue: , + TransactionHistory: , + Assets: , + Apps: + }[page] + + return {currentView} +} + +export default OverviewDisplay \ No newline at end of file diff --git a/src/components/chat/OverviewDrawer.tsx b/src/components/chat/OverviewDrawer.tsx new file mode 100644 index 00000000000..2015f42cf6d --- /dev/null +++ b/src/components/chat/OverviewDrawer.tsx @@ -0,0 +1,226 @@ +import css from '@/components/chat/styles.module.css'; +import AppsIcon from '@/public/images/apps/apps-icon.svg'; +import NftIcon from '@/public/images/common/nft.svg'; +import AssetsIcon from '@/public/images/sidebar/assets.svg'; +import { useAppDispatch } from '@/store'; +import { openModal } from '@/store/modalServiceSlice'; +import MoreAppsIcon from '@mui/icons-material/Apps'; +import HistoryIcon from '@mui/icons-material/History'; +import KeyboardDoubleArrowLeftIcon from '@mui/icons-material/KeyboardDoubleArrowLeft'; +import KeyboardDoubleArrowRightIcon from '@mui/icons-material/KeyboardDoubleArrowRight'; +import ListIcon from '@mui/icons-material/List'; +import MonetizationOnIcon from '@mui/icons-material/MonetizationOn'; +import PeopleIcon from '@mui/icons-material/People'; +import { Box, Button, Divider, List, ListItem, ListItemButton, ListItemIcon, Stack, SvgIcon, useMediaQuery } from '@mui/material'; +import MuiDrawer from '@mui/material/Drawer'; +import IconButton from '@mui/material/IconButton'; +import type { CSSObject, Theme } from '@mui/material/styles'; +import { styled } from '@mui/material/styles'; +import * as React from 'react'; +import { modalTypes } from './modals'; +import OverviewDisplay from './OverviewDisplay'; + +const drawerWidth = 450; + +export enum Pages { + Overview = 'Overview', + Members = 'Members', + TransactionQueue = 'TransactionQueue', + TransactionHistory = 'TransactionHistory', + Assets = 'Assets', + Apps = 'Apps' +} + +const pages = [ + { + name: Pages.Overview, + icon: AppsIcon + }, + { + name: Pages.Members, + icon: PeopleIcon + }, + { + name: Pages.TransactionQueue, + icon: ListIcon + }, + { + name: Pages.TransactionHistory, + icon: HistoryIcon + }, + { + name: Pages.Assets, + icon: MonetizationOnIcon + }, + { + name: Pages.Apps, + icon: MoreAppsIcon + } +] + +const openedMixin = (theme: Theme): CSSObject => ({ + width: drawerWidth, + transition: theme.transitions.create('width', { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.enteringScreen, + }), + overflowX: 'hidden', +}) + +const closedMixin = (theme: Theme): CSSObject => ({ + transition: theme.transitions.create('width', { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.leavingScreen, + }), + overflowX: 'hidden', + overflowY: 'hidden', + width: `calc(${theme.spacing(7)} + 1px)`, + [theme.breakpoints.up('sm')]: { + width: `calc(${theme.spacing(8)} + 1px)`, + }, +}) + +const DrawerFooter = styled('div')(({ theme }) => ({ + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + padding: theme.spacing(0, 1), + // necessary for content to be below app bar + ...theme.mixins.toolbar, +})) + +const Drawer = styled(MuiDrawer, { shouldForwardProp: (prop) => prop !== 'open' })( + ({ theme, open }) => ({ + width: drawerWidth, + flexShrink: 0, + whiteSpace: 'nowrap', + boxSizing: 'border-box', + ...(open && { + ...openedMixin(theme), + '& .MuiDrawer-paper': openedMixin(theme), + }), + ...(!open && { + ...closedMixin(theme), + '& .MuiDrawer-paper': closedMixin(theme), + }), + }), +) + +const OverviewDrawer: React.FC<{ + owners: any[] +}> = ({ owners }) => { + const matches = useMediaQuery('(max-width: 900px)') + const [open, setOpen] = React.useState(false) + const [selectedPage, setSelectedPage] = React.useState(Pages.Overview) + const dispatch = useAppDispatch() + const handleDrawerOpen = () => { + setOpen(true) + }; + + const handleSelectedPage = (name: Pages) => { + handleDrawerOpen() + setSelectedPage(name) + } + + const handleDrawerClose = () => { + setOpen(false) + }; + + return ( + + } + > + + + {pages.map((page, index) => ( + + handleSelectedPage(page.name)} + sx={{ + minHeight: 48, + justifyContent: open ? 'initial' : 'center', + px: 2, + borderRadius: 1.2 + }} + > + + + + + + ))} + + + + {open ? : } + + + + + + + {/* */} + + {/* */} + + + + + + ); +} + +export default OverviewDrawer \ No newline at end of file diff --git a/src/components/chat/OverviewSection.tsx b/src/components/chat/OverviewSection.tsx new file mode 100644 index 00000000000..4b160e3b35d --- /dev/null +++ b/src/components/chat/OverviewSection.tsx @@ -0,0 +1,96 @@ +import css from '@/components/chat/styles.module.css' +import { ThresholdOverview } from '@/components/chat/threshold' +import CopyButton from '@/components/common/CopyButton' +import QrCodeButton from '@/components/sidebar/QrCodeButton' +import { useCurrentChain } from '@/hooks/useChains' +import useSafeInfo from '@/hooks/useSafeInfo' +import AppsIcon from '@/public/images/apps/apps-icon.svg' +import CopyIcon from '@/public/images/common/copy.svg' +import LinkIcon from '@/public/images/common/link.svg' +import { useAppSelector } from '@/store' +import { selectSettings } from '@/store/settingsSlice' +import { getBlockExplorerLink } from '@/utils/chains' +import ellipsisAddress from '@/utils/ellipsisAddress' +import { Box, SvgIcon, Typography } from '@mui/material' +import { grey } from '@mui/material/colors' +import IconButton from '@mui/material/IconButton' +import Tooltip from '@mui/material/Tooltip' + +const OverviewSection = () => { + const { safe, safeAddress } = useSafeInfo() + const ownerLength = safe?.owners?.length || 0 + const threshold = safe.threshold + const settings = useAppSelector(selectSettings) + const chain = useCurrentChain() + const addressCopyText = settings.shortName.copy && chain ? `${chain.shortName}:${safeAddress}` : safeAddress + const blockExplorerLink = chain ? getBlockExplorerLink(chain, safeAddress) : undefined + return ( + + + Overview + + + + Address + + + + {ellipsisAddress(`${safeAddress}`)} + + {/* */} +
+ + + ({ color: palette.border.main })}> + + + + + + + + + + + ({ color: palette.border.main })} + > + + + +
+
+
+ + Network + + {safe?.chainId === '137' + ? 'Polygon' + : safe?.chainId === '1' + ? 'Ethereum' + : safe?.chainId === '10' + ? 'Optimism' + : safe?.chainId === '42161' + ? 'Arbitrum' + : safe?.chainId === '56' + ? 'BNB Smart Chain' + : safe?.chainId === '100' + ? 'Gnosis Chain' + : ''} + + + + + Threshold + + + +
+ ) +} + +export default OverviewSection \ No newline at end of file diff --git a/src/components/chat/SafeList.tsx b/src/components/chat/SafeList.tsx index cd9c4651dff..e1ea5104c90 100644 --- a/src/components/chat/SafeList.tsx +++ b/src/components/chat/SafeList.tsx @@ -1,6 +1,8 @@ import { useAllOwnedSafes } from "@/hooks/useAllOwnedSafes" import useSafeAddress from "@/hooks/useSafeAddress" import useWallet from "@/hooks/wallets/useWallet" +import { useAppDispatch } from "@/store" +import { openModal } from "@/store/modalServiceSlice" import { Box, Button, Tab, Tabs, Toolbar, Typography } from "@mui/material" import { useEffect, useState } from "react" import useConnectWallet from "../common/ConnectWallet/useConnectWallet" @@ -8,6 +10,7 @@ import FormattedName from "../common/FormattedName/FormattedName" import FolderList from "../folder-list" import FolderGroup from "../folder-list/folderGroups" import ModalListContextMenu from "./ModalListContextMenu" +import { modalTypes } from "./modals" import css from './styles.module.css' interface TabPanelProps { @@ -44,7 +47,7 @@ function a11yProps(index: number) { } } -export const SafeList: React.FC<{ createSafe: boolean, setCreateSafe: any }> = ({ createSafe, setCreateSafe }) => { +export const SafeList = () => { //user and safe const wallet = useWallet() const safeAddress = useSafeAddress() @@ -52,6 +55,7 @@ export const SafeList: React.FC<{ createSafe: boolean, setCreateSafe: any }> = ( const handleConnect = useConnectWallet() const allOwnedSafes = useAllOwnedSafes() const [folders, setFolders] = useState([]) + const dispatch = useAppDispatch() const handleChange = (event: React.SyntheticEvent, newValue: number) => { setValue(newValue) localStorage.setItem('tabIndex', newValue.toString()) @@ -93,7 +97,7 @@ export const SafeList: React.FC<{ createSafe: boolean, setCreateSafe: any }> = ( VIEW AS: {wallet?.address ? : Not connected} - + = ( <> : wallet?.address && areAllValuesEmptyArrays(allOwnedSafes) ? - : diff --git a/src/components/chat/chatOverview.tsx b/src/components/chat/chatOverview.tsx index 74d7300a7b4..3dcbdb57100 100644 --- a/src/components/chat/chatOverview.tsx +++ b/src/components/chat/chatOverview.tsx @@ -19,14 +19,13 @@ import { useCurrentChain } from '@/hooks/useChains' import AppsIcon from '@/public/images/apps/apps-icon.svg' import CopyIcon from '@/public/images/common/copy.svg' import LinkIcon from '@/public/images/common/link.svg' -import { useAppSelector } from '@/store' +import { useAppDispatch, useAppSelector } from '@/store' +import { openModal } from '@/store/modalServiceSlice' import { selectSettings } from '@/store/settingsSlice' import { getBlockExplorerLink } from '@/utils/chains' import ellipsisAddress from '@/utils/ellipsisAddress' import IconButton from '@mui/material/IconButton' import Tooltip from '@mui/material/Tooltip' -import { useAppDispatch } from '@/store' -import { openModal } from '@/store/modalServiceSlice' export const ChatOverview: React.FC<{ owners: any[] @@ -42,7 +41,7 @@ export const ChatOverview: React.FC<{ const chain = useCurrentChain() const addressCopyText = settings.shortName.copy && chain ? `${chain.shortName}:${safeAddress}` : safeAddress const blockExplorerLink = chain ? getBlockExplorerLink(chain, safeAddress) : undefined - + return ( <> @@ -146,7 +145,7 @@ export const ChatOverview: React.FC<{ Apps - + Explore the Safe Apps ecosystem — connect to your favourite web3 applications with your Safe wallet, securely and efficiently diff --git a/src/components/chat/chatSection.tsx b/src/components/chat/chatSection.tsx index d2ce6fd6356..4785ba0e6c2 100644 --- a/src/components/chat/chatSection.tsx +++ b/src/components/chat/chatSection.tsx @@ -1,18 +1,18 @@ +import { getExistingAuth } from '@/components/auth-sign-in/helpers' import useSafeAddress from '@/hooks/useSafeAddress' import useTxHistory from '@/hooks/useTxHistory' import useTxQueue from '@/hooks/useTxQueue' +import useOnboard from '@/hooks/wallets/useOnboard' import useWallet from '@/hooks/wallets/useWallet' +import { createWeb3 } from '@/hooks/wallets/web3' import { useAppSelector } from '@/store' import { selectGroup, selectUserItem } from '@/store/chatServiceSlice' -import { Box, List, ListItem, useMediaQuery, ListItemAvatar } from '@mui/material' +import { Box, List, ListItem, ListItemAvatar, useMediaQuery } from '@mui/material' import React, { useCallback, useEffect, useRef, useState } from 'react' import { useDispatch } from 'react-redux' //import { getMessages, listenForMessage } from '../../services/chat' import TxListItemChat from '../transactions/TxListItemChat' import ChatMessage from './chatMessage' -import useOnboard from '@/hooks/wallets/useOnboard' -import { createWeb3 } from '@/hooks/wallets/web3' -import { getExistingAuth } from '@/components/auth-sign-in/helpers' import ChatTextField from './chatTextField' export const ChatSection: React.FC<{ drawerWidth?: number, drawerOpen?: boolean }> = ({ drawerWidth, drawerOpen }) => { @@ -85,7 +85,7 @@ export const ChatSection: React.FC<{ drawerWidth?: number, drawerOpen?: boolean return 0 } }) - if (JSON.stringify(allData) !== JSON.stringify(chatData)) setChatData(allData) + if (JSON.stringify(allData) !== JSON.stringify(chatData)) setChatData(allData) }, [messages, txHistory?.page, txQueue?.page, safeAddress]) @@ -163,10 +163,10 @@ export const ChatSection: React.FC<{ drawerWidth?: number, drawerOpen?: boolean disableGutters > - + - + ) @@ -179,11 +179,11 @@ export const ChatSection: React.FC<{ drawerWidth?: number, drawerOpen?: boolean disableGutters > - + - - + + ) } @@ -195,12 +195,12 @@ export const ChatSection: React.FC<{ drawerWidth?: number, drawerOpen?: boolean alignItems="flex-start" disableGutters > - - - - - - + + + + + + ) } diff --git a/src/components/chat/chatTextField.tsx b/src/components/chat/chatTextField.tsx index 838ca2139a2..003e099c98f 100644 --- a/src/components/chat/chatTextField.tsx +++ b/src/components/chat/chatTextField.tsx @@ -1,12 +1,12 @@ import useSafeAddress from "@/hooks/useSafeAddress"; import { sendMessage } from "@/services/chat"; +import { publish } from "@/services/events"; import SendOutlinedIcon from '@mui/icons-material/SendOutlined'; -import { Button, Divider, InputBase, Paper, Box, Typography } from "@mui/material"; +import { Box, Button, Divider, InputBase, Paper, Typography } from "@mui/material"; import { styled } from '@mui/material/styles'; import React, { useState } from "react"; -import AddNewTxLightningIconButton from "./AddNewTxLightningIconButton"; import SignInLink from "../auth-sign-in/auth-link"; -import { publish } from "@/services/events"; +import AddNewTxLightningIconButton from "./AddNewTxLightningIconButton"; import { getCookie } from "typescript-cookie"; import useWallet from "@/hooks/wallets/useWallet"; @@ -51,7 +51,7 @@ const ChatTextField: React.FC<{ onSubmit={handleSubmit} > - + { @@ -70,7 +70,7 @@ const ChatTextField: React.FC<{ ) : ( - To view and send messages you need to authenticate your account: + To view and send messages you need to authenticate your account: ) } diff --git a/src/components/chat/modals/AddDescription/index.tsx b/src/components/chat/modals/AddDescription/index.tsx index 5e9d557b0d3..83d3b6d7817 100644 --- a/src/components/chat/modals/AddDescription/index.tsx +++ b/src/components/chat/modals/AddDescription/index.tsx @@ -1,46 +1,46 @@ -import ModalDialog from '@/components/common/ModalDialog' -import { Box, Button, DialogContent, TextField } from '@mui/material' -import React, { useState } from 'react' -import { insertDescription } from '@/services/supabase' -import { getCookie } from 'typescript-cookie' -import useWallet from '@/hooks/wallets/useWallet' - -export const AddTxDescription: React.FC<{ - open: boolean - onClose: () => void - id: string - owner: string - updateDescription: React.Dispatch> -}> = ({ open, onClose, id, owner, updateDescription }) => { - const [description, setDescription] = useState('') - const wallet = useWallet() - const auth = getCookie(wallet?.address || '') - - const addDescription = async () => { - await insertDescription(id, description, owner, auth!).then(() => updateDescription(true)) - setDescription('') - onClose() - } - - return ( - - - - - setDescription(e.target.value)} - /> - - - - - - ) -} +import ModalDialog from '@/components/common/ModalDialog' +import { Box, Button, DialogContent, TextField } from '@mui/material' +import React, { useState } from 'react' +import { insertDescription } from '@/services/supabase' +import { getCookie } from 'typescript-cookie' +import useWallet from '@/hooks/wallets/useWallet' + +export const AddTxDescription: React.FC<{ + open: boolean + onClose: () => void + id: string + owner: string + updateDescription: React.Dispatch> +}> = ({ open, onClose, id, owner, updateDescription }) => { + const [description, setDescription] = useState('') + const wallet = useWallet() + const auth = getCookie(wallet?.address || '') + + const addDescription = async () => { + await insertDescription(id, description, owner, auth!).then(() => updateDescription(true)) + setDescription('') + onClose() + } + + return ( + + + + + setDescription(e.target.value)} + /> + + + + + + ) +} diff --git a/src/components/chat/modals/index.tsx b/src/components/chat/modals/index.tsx index e3e97237ec3..579877b34b5 100644 --- a/src/components/chat/modals/index.tsx +++ b/src/components/chat/modals/index.tsx @@ -1,95 +1,101 @@ -import { useAppDispatch, useAppSelector } from "@/store"; -import { selectModalState } from "@/store/modalServiceSlice"; -import { closeModal } from "@/store/modalServiceSlice"; -import { ViewSingleTransactionModal } from "./ViewSingleTransaction"; -import ViewTransactionsModal from "./ViewTransactionsModal"; -import React from 'react' -import ViewCreateSafe from "./CreateSafe"; -import { AddFolderModal } from "./AddFolderModal"; -import ViewAppModal from "./ViewAppModal"; -import ViewAppsModal from "./ViewAppsModal"; -import ViewAssetsModal from "./ViewAssetsModal"; -import ViewSettings from "./ViewSettingsModal"; -import { AddTxDescription } from "./AddDescription"; - -export const modalTypes = { - addTransactionDescription: 'addTransactionDescription', - viewSingleTransaction: 'viewSingleTransaction', - viewTransactions: 'viewTransactionsModal', - createSafe: 'createSafe', - addFolderModal: 'addFolderModal', - appModal: 'appModal', - appsModal: 'appsModal', - assetsModals: 'assetsModals', - settingsModal: 'settingsModal', -} - -export const Modals = () => { - const dispatch = useAppDispatch(); - const activeModalState = useAppSelector((state) => selectModalState(state)); - - return ( - <> - {activeModalState?.activeModal === modalTypes.viewSingleTransaction && ( - dispatch(closeModal())} - /> - )} - {activeModalState?.activeModal === modalTypes.viewTransactions && ( - dispatch(closeModal())} - /> - )} - {activeModalState?.activeModal === modalTypes.createSafe && ( - dispatch(closeModal())} - /> - )} - {activeModalState?.activeModal === modalTypes.addFolderModal && ( - dispatch(closeModal())} - /> - )} - {activeModalState?.activeModal === modalTypes.appModal && ( - dispatch(closeModal())} - /> - )} - {activeModalState?.activeModal === modalTypes.appsModal && ( - dispatch(closeModal())} - /> - )} - {activeModalState?.activeModal === modalTypes.assetsModals && ( - dispatch(closeModal())} - nfts={activeModalState?.modalProps?.nfts} - /> - )} - {activeModalState?.activeModal === modalTypes.addTransactionDescription && ( - dispatch(closeModal())} - id={activeModalState?.modalProps?.id} - owner={activeModalState?.modalProps?.owner} - updateDescription={activeModalState?.modalProps?.updateDescription} - /> - )} - {activeModalState?.activeModal === modalTypes.settingsModal && ( - dispatch(closeModal())} - /> - )} - - ) -} - - +import TokenTransferModal from "@/components/tx/modals/TokenTransferModal"; +import { useAppDispatch, useAppSelector } from "@/store"; +import { closeModal, selectModalState } from "@/store/modalServiceSlice"; +import { AddTxDescription } from "./AddDescription"; +import { AddFolderModal } from "./AddFolderModal"; +import ViewCreateSafe from "./CreateSafe"; +import ViewAppModal from "./ViewAppModal"; +import ViewAppsModal from "./ViewAppsModal"; +import ViewAssetsModal from "./ViewAssetsModal"; +import ViewSettings from "./ViewSettingsModal"; +import { ViewSingleTransactionModal } from "./ViewSingleTransaction"; +import ViewTransactionsModal from "./ViewTransactionsModal"; + +export const modalTypes = { + addTransactionDescription: 'addTransactionDescription', + viewSingleTransaction: 'viewSingleTransaction', + viewTransactions: 'viewTransactionsModal', + createSafe: 'createSafe', + addFolderModal: 'addFolderModal', + appModal: 'appModal', + appsModal: 'appsModal', + assetsModals: 'assetsModals', + settingsModal: 'settingsModal', + tokenTransferModal: 'tokenTransferModal' +} + +export const Modals = () => { + const dispatch = useAppDispatch(); + const activeModalState = useAppSelector((state) => selectModalState(state)); + + return ( + <> + {activeModalState?.activeModal === modalTypes.viewSingleTransaction && ( + dispatch(closeModal())} + /> + )} + {activeModalState?.activeModal === modalTypes.viewTransactions && ( + dispatch(closeModal())} + /> + )} + {activeModalState?.activeModal === modalTypes.createSafe && ( + dispatch(closeModal())} + /> + )} + {activeModalState?.activeModal === modalTypes.addFolderModal && ( + dispatch(closeModal())} + /> + )} + {activeModalState?.activeModal === modalTypes.appModal && ( + dispatch(closeModal())} + /> + )} + {activeModalState?.activeModal === modalTypes.appsModal && ( + dispatch(closeModal())} + /> + )} + {activeModalState?.activeModal === modalTypes.assetsModals && ( + dispatch(closeModal())} + nfts={activeModalState?.modalProps?.nfts} + /> + )} + {activeModalState?.activeModal === modalTypes.addTransactionDescription && ( + dispatch(closeModal())} + id={activeModalState?.modalProps?.id} + owner={activeModalState?.modalProps?.owner} + updateDescription={activeModalState?.modalProps?.updateDescription} + /> + )} + {activeModalState?.activeModal === modalTypes.settingsModal && ( + dispatch(closeModal())} + /> + )} + {activeModalState?.activeModal === modalTypes.tokenTransferModal && ( + dispatch(closeModal())} + initialData={[{ disableSpendingLimit: false }]} + /> + )} + + ) +} + + diff --git a/src/components/transactions/TxSummaryChat/index.tsx b/src/components/transactions/TxSummaryChat/index.tsx index 144b6ec1f62..36327b55240 100644 --- a/src/components/transactions/TxSummaryChat/index.tsx +++ b/src/components/transactions/TxSummaryChat/index.tsx @@ -1,192 +1,192 @@ -import type { Palette } from '@mui/material' -import { Box, CircularProgress, Typography, Divider, Button } from '@mui/material' -import type { ReactElement } from 'react' -import { useState, useEffect } from 'react' -import { type Transaction, TransactionStatus } from '@safe-global/safe-gateway-typescript-sdk' -import TxFullShareLink from '@/components/transactions/TxFullShareLink' -import DateTime from '@/components/common/DateTime' -import TxInfo from '@/components/transactions/TxInfo' -import SignTxButton from '@/components/transactions/SignTxButton' -import ExecuteTxButton from '@/components/transactions/ExecuteTxButton' -import css from './styles.module.css' -import useWallet from '@/hooks/wallets/useWallet' -import { isAwaitingExecution, isMultisigExecutionInfo, isTxQueued } from '@/utils/transaction-guards' -import RejectTxButton from '@/components/transactions/RejectTxButton' -import useTransactionStatus from '@/hooks/useTransactionStatus' -import TxType from '@/components/transactions/TxType' -import TxConfirmations from '../TxConfirmations' -import useIsPending from '@/hooks/useIsPending' -import { useAppDispatch } from '@/store' -import { openModal } from '@/store/modalServiceSlice' -import { modalTypes } from '@/components/chat/modals' -import { readDescription } from '@/services/supabase' -import { getCookie } from 'typescript-cookie' -import useSafeInfo from '@/hooks/useSafeInfo' - -const getStatusColor = (value: TransactionStatus, palette: Palette) => { - switch (value) { - case TransactionStatus.SUCCESS: - return palette.success.main - case TransactionStatus.FAILED: - case TransactionStatus.CANCELLED: - return palette.error.main - case TransactionStatus.AWAITING_CONFIRMATIONS: - case TransactionStatus.AWAITING_EXECUTION: - return palette.warning.main - default: - return palette.primary.main - } -} - -type TxSummaryProps = { - isGrouped?: boolean - item: Transaction -} - -const TxSummaryChat = ({ item, isGrouped }: TxSummaryProps): ReactElement => { - - const [description, setDescription] = useState<{ description: string, owner: string } | null>() - const [update, setUpdate] = useState(false) - const dispatch = useAppDispatch() - const tx = item.transaction - const wallet = useWallet() - const txStatusLabel = useTransactionStatus(tx) - const isPending = useIsPending(tx.id) - const isQueue = isTxQueued(tx.txStatus) - const awaitingExecution = isAwaitingExecution(tx.txStatus) - const nonce = isMultisigExecutionInfo(tx.executionInfo) ? tx.executionInfo.nonce : undefined - const { safe } = useSafeInfo() - const requiredConfirmations = isMultisigExecutionInfo(tx.executionInfo) - ? tx.executionInfo.confirmationsRequired - : undefined - const submittedConfirmations = isMultisigExecutionInfo(tx.executionInfo) - ? tx.executionInfo.confirmationsSubmitted - : undefined - const owners = safe?.owners || [''] - const ownerArray = owners.map((owner) => owner.value) - const auth = getCookie(wallet?.address || '') - const displayConfirmations = isQueue && !!submittedConfirmations && !!requiredConfirmations - - useEffect(() => { - const read = async () => { - const des = readDescription(tx.id, auth!) - return des - } - read().then((res) => res?.length && setDescription(res[0])) - }, [tx?.id, update, auth]) - - return ( - - - - - - {nonce && !isGrouped && TRANSACTION #{nonce}} - - - - - - - - - - - - - - - - {displayConfirmations && ( - - Confirmations - - - - - )} - - - Status - getStatusColor(tx.txStatus, palette)} - > - {isPending && } - - getStatusColor(tx.txStatus, palette)}> - {txStatusLabel} - - - - - - { - auth && description && ownerArray.includes(wallet?.address || '') && - Description - - getStatusColor(tx.txStatus, palette)}> - {description.description} - - - - } - { - auth && !description && ownerArray.includes(wallet?.address || '') && - } - - - - - - - - - - {tx.txInfo.type !== 'Creation' && ( - - - - )} - - - {wallet && isQueue && ( - - - {awaitingExecution ? ( - - ) : ( - - )} - - - - - - )} - - ) -} - -export default TxSummaryChat +import type { Palette } from '@mui/material' +import { Box, CircularProgress, Typography, Divider, Button } from '@mui/material' +import type { ReactElement } from 'react' +import { useState, useEffect } from 'react' +import { type Transaction, TransactionStatus } from '@safe-global/safe-gateway-typescript-sdk' +import TxFullShareLink from '@/components/transactions/TxFullShareLink' +import DateTime from '@/components/common/DateTime' +import TxInfo from '@/components/transactions/TxInfo' +import SignTxButton from '@/components/transactions/SignTxButton' +import ExecuteTxButton from '@/components/transactions/ExecuteTxButton' +import css from './styles.module.css' +import useWallet from '@/hooks/wallets/useWallet' +import { isAwaitingExecution, isMultisigExecutionInfo, isTxQueued } from '@/utils/transaction-guards' +import RejectTxButton from '@/components/transactions/RejectTxButton' +import useTransactionStatus from '@/hooks/useTransactionStatus' +import TxType from '@/components/transactions/TxType' +import TxConfirmations from '../TxConfirmations' +import useIsPending from '@/hooks/useIsPending' +import { useAppDispatch } from '@/store' +import { openModal } from '@/store/modalServiceSlice' +import { modalTypes } from '@/components/chat/modals' +import { readDescription } from '@/services/supabase' +import { getCookie } from 'typescript-cookie' +import useSafeInfo from '@/hooks/useSafeInfo' + +const getStatusColor = (value: TransactionStatus, palette: Palette) => { + switch (value) { + case TransactionStatus.SUCCESS: + return palette.success.main + case TransactionStatus.FAILED: + case TransactionStatus.CANCELLED: + return palette.error.main + case TransactionStatus.AWAITING_CONFIRMATIONS: + case TransactionStatus.AWAITING_EXECUTION: + return palette.warning.main + default: + return palette.primary.main + } +} + +type TxSummaryProps = { + isGrouped?: boolean + item: Transaction +} + +const TxSummaryChat = ({ item, isGrouped }: TxSummaryProps): ReactElement => { + + const [description, setDescription] = useState<{ description: string, owner: string } | null>() + const [update, setUpdate] = useState(false) + const dispatch = useAppDispatch() + const tx = item.transaction + const wallet = useWallet() + const txStatusLabel = useTransactionStatus(tx) + const isPending = useIsPending(tx.id) + const isQueue = isTxQueued(tx.txStatus) + const awaitingExecution = isAwaitingExecution(tx.txStatus) + const nonce = isMultisigExecutionInfo(tx.executionInfo) ? tx.executionInfo.nonce : undefined + const { safe } = useSafeInfo() + const requiredConfirmations = isMultisigExecutionInfo(tx.executionInfo) + ? tx.executionInfo.confirmationsRequired + : undefined + const submittedConfirmations = isMultisigExecutionInfo(tx.executionInfo) + ? tx.executionInfo.confirmationsSubmitted + : undefined + const owners = safe?.owners || [''] + const ownerArray = owners.map((owner) => owner.value) + const auth = getCookie(wallet?.address || '') + const displayConfirmations = isQueue && !!submittedConfirmations && !!requiredConfirmations + + useEffect(() => { + const read = async () => { + const des = readDescription(tx.id, auth!) + return des + } + read().then((res) => res?.length && setDescription(res[0])) + }, [tx?.id, update, auth]) + + return ( + + + + + + {nonce && !isGrouped && TRANSACTION #{nonce}} + + + + + + + + + + + + + + + + {displayConfirmations && ( + + Confirmations + + + + + )} + + + Status + getStatusColor(tx.txStatus, palette)} + > + {isPending && } + + getStatusColor(tx.txStatus, palette)}> + {txStatusLabel} + + + + + + { + auth && description && ownerArray.includes(wallet?.address || '') && + Description + + getStatusColor(tx.txStatus, palette)}> + {description.description} + + + + } + { + auth && !description && ownerArray.includes(wallet?.address || '') && + } + + + + + + + + + + {tx.txInfo.type !== 'Creation' && ( + + + + )} + + + {wallet && isQueue && ( + + + {awaitingExecution ? ( + + ) : ( + + )} + + + + + + )} + + ) +} + +export default TxSummaryChat diff --git a/src/components/tx/TxModal/index.tsx b/src/components/tx/TxModal/index.tsx index 9a114ec35ae..1077fc98c45 100644 --- a/src/components/tx/TxModal/index.tsx +++ b/src/components/tx/TxModal/index.tsx @@ -1,7 +1,7 @@ +import ModalDialog from '@/components/common/ModalDialog' +import type { TxStepperProps } from '@/components/tx/TxStepper/useTxStepper' import type { ReactElement } from 'react' import TxStepper from '../TxStepper' -import type { TxStepperProps } from '@/components/tx/TxStepper/useTxStepper' -import ModalDialog from '@/components/common/ModalDialog' export type TxModalProps = { onClose: () => void diff --git a/src/pages/api/login.ts b/src/pages/api/login.ts index ad259356895..364596f45da 100644 --- a/src/pages/api/login.ts +++ b/src/pages/api/login.ts @@ -1,91 +1,91 @@ -import { createClient } from '@supabase/supabase-js'; -import jwt from 'jsonwebtoken'; - - -const SUPABASE_TABLE_USERS = 'users'; -const SUPABASE_URL = process.env.NEXT_PUBLIC_SUPABASE_PROJECT_URL || ''; -const SUPABASE_SERVICE_ROLE_KEY = process.env.NEXT_PUBLIC_SUPABASE_SERVICE_KEY || ''; - -const handler = async (req: any, res: any) => { - if (req.method !== 'POST') { - return res.status(405).end(); - } - - const { address, signedMessage, nonce } = req.body; - - try { - // 1. Verify the signed message matches the requested address (Add your verification logic here) - - // 2. Select * from public.user table where address matches - const supabase = createClient(SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY); - const { data, error: userError } = await supabase - .from(SUPABASE_TABLE_USERS) - .select() - .eq("address", address) - .single(); - - if (userError) { - return res.status(500).json({ error: `Database error while fetching user data 1 ${userError}` }); - } - - if (data?.auth.genNonce !== nonce) { - return res.status(500).json({ error: 'nonce does not match' }); - } - - // 3. Verify the nonce included in the request matches what's already in public.users table for that address - let authUser; - if (!data?.id) { - const { data: userData, error } = await supabase.auth.admin.createUser({ - email: `${address}@decentra.so`, // we have to have this.. or a phone - user_metadata: { address: address }, - }); - - if (error) { - return res.status(500).json({ error: `Database error while fetching user data 2 ${error}` }); - } - - if (data != null) { - authUser = userData.user; - } - } else { - const { data: userData, error } = await supabase.auth.admin.getUserById( - data.id - ); - - if (error) { - return res.status(500).json({ error: `Database error while fetching user data 3 ${error}` }); - } else { - authUser = userData.user; - } - } - let newNonce = Math.floor(Math.random() * 1000000); - while (newNonce === nonce) { - newNonce = Math.floor(Math.random() * 1000000); - } - - await supabase - .from(SUPABASE_TABLE_USERS) - .update({ - auth: { - genNonce: newNonce, // update the nonce, so it can't be reused - lastAuth: new Date().toISOString(), - lastAuthStatus: "success", - }, - id: authUser?.id, // same uuid as auth.users table - }) - .eq("address", address); // primary key - - const token = jwt.sign({ - address: address, // this will be read by RLS policy - sub: authUser?.id, - aud: 'authenticated' - }, process.env.NEXT_PUBLIC_SUPABASE_JWT! ) - - return res.status(200).json({ token }); - } catch (err: any) { - console.error('Supabase request error:', err.message); - return res.status(500).json({ error: `Something went wrong, ${err}` }); - } -}; - +import { createClient } from '@supabase/supabase-js'; +import jwt from 'jsonwebtoken'; + + +const SUPABASE_TABLE_USERS = 'users'; +const SUPABASE_URL = process.env.NEXT_PUBLIC_SUPABASE_PROJECT_URL || ''; +const SUPABASE_SERVICE_ROLE_KEY = process.env.NEXT_PUBLIC_SUPABASE_SERVICE_KEY || ''; + +const handler = async (req: any, res: any) => { + if (req.method !== 'POST') { + return res.status(405).end(); + } + + const { address, signedMessage, nonce } = req.body; + + try { + // 1. Verify the signed message matches the requested address (Add your verification logic here) + + // 2. Select * from public.user table where address matches + const supabase = createClient(SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY); + const { data, error: userError } = await supabase + .from(SUPABASE_TABLE_USERS) + .select() + .eq("address", address) + .single(); + + if (userError) { + return res.status(500).json({ error: `Database error while fetching user data 1 ${userError}` }); + } + + if (data?.auth.genNonce !== nonce) { + return res.status(500).json({ error: 'nonce does not match' }); + } + + // 3. Verify the nonce included in the request matches what's already in public.users table for that address + let authUser; + if (!data?.id) { + const { data: userData, error } = await supabase.auth.admin.createUser({ + email: `${address}@decentra.so`, // we have to have this.. or a phone + user_metadata: { address: address }, + }); + + if (error) { + return res.status(500).json({ error: `Database error while fetching user data 2 ${error}` }); + } + + if (data != null) { + authUser = userData.user; + } + } else { + const { data: userData, error } = await supabase.auth.admin.getUserById( + data.id + ); + + if (error) { + return res.status(500).json({ error: `Database error while fetching user data 3 ${error}` }); + } else { + authUser = userData.user; + } + } + let newNonce = Math.floor(Math.random() * 1000000); + while (newNonce === nonce) { + newNonce = Math.floor(Math.random() * 1000000); + } + + await supabase + .from(SUPABASE_TABLE_USERS) + .update({ + auth: { + genNonce: newNonce, // update the nonce, so it can't be reused + lastAuth: new Date().toISOString(), + lastAuthStatus: "success", + }, + id: authUser?.id, // same uuid as auth.users table + }) + .eq("address", address); // primary key + + const token = jwt.sign({ + address: address, // this will be read by RLS policy + sub: authUser?.id, + aud: 'authenticated' + }, process.env.NEXT_PUBLIC_SUPABASE_JWT! ) + + return res.status(200).json({ token }); + } catch (err: any) { + console.error('Supabase request error:', err.message); + return res.status(500).json({ error: `Something went wrong, ${err}` }); + } +}; + export default handler; \ No newline at end of file diff --git a/src/pages/api/nonce.ts b/src/pages/api/nonce.ts index a8770837980..2a9a28e6da0 100644 --- a/src/pages/api/nonce.ts +++ b/src/pages/api/nonce.ts @@ -1,41 +1,41 @@ -import { createClient } from '@supabase/supabase-js'; - -const SUPABASE_TABLE_USER = 'users'; -const SUPABASE_URL = process.env.NEXT_PUBLIC_SUPABASE_PROJECT_URL || ''; -const SUPABASE_ANON_KEY = process.env.NEXT_PUBLIC_SUPABASE_API_KEY || ''; - -const nonceHandler = async (req: any, res: any) => { - if (req.method !== 'POST') { - return res.status(405).end(); - } - - const { address } = req.body; - const nonce = Math.floor(Math.random() * 1000000); - - try { - const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY); - const response = await supabase.from(SUPABASE_TABLE_USER).upsert( - [ - { - address: address, - auth: { - genNonce: nonce, - lastAuth: new Date().toISOString(), - lastAuthStatus: "pending", - }, - }, - ], - { - onConflict: "address", - } - ); - - - return res.status(200).json({ nonce, response }); - } catch (err: any) { - console.error('Supabase request error:', err.message); - return res.status(500).json({ error: 'Something went wrong' }); - } -}; - +import { createClient } from '@supabase/supabase-js'; + +const SUPABASE_TABLE_USER = 'users'; +const SUPABASE_URL = process.env.NEXT_PUBLIC_SUPABASE_PROJECT_URL || ''; +const SUPABASE_ANON_KEY = process.env.NEXT_PUBLIC_SUPABASE_API_KEY || ''; + +const nonceHandler = async (req: any, res: any) => { + if (req.method !== 'POST') { + return res.status(405).end(); + } + + const { address } = req.body; + const nonce = Math.floor(Math.random() * 1000000); + + try { + const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY); + const response = await supabase.from(SUPABASE_TABLE_USER).upsert( + [ + { + address: address, + auth: { + genNonce: nonce, + lastAuth: new Date().toISOString(), + lastAuthStatus: "pending", + }, + }, + ], + { + onConflict: "address", + } + ); + + + return res.status(200).json({ nonce, response }); + } catch (err: any) { + console.error('Supabase request error:', err.message); + return res.status(500).json({ error: 'Something went wrong' }); + } +}; + export default nonceHandler; \ No newline at end of file diff --git a/src/pages/chat.tsx b/src/pages/chat.tsx index 0629a92c534..96ecb35d3f3 100644 --- a/src/pages/chat.tsx +++ b/src/pages/chat.tsx @@ -1,6 +1,5 @@ -import { ChatOverview } from '@/components/chat/chatOverview' -import ViewCreateSafe from '@/components/chat/modals/CreateSafe' -import ViewAppModal from '@/components/chat/modals/ViewAppModal' +import { modalTypes } from '@/components/chat/modals' +import OverviewDrawer from '@/components/chat/OverviewDrawer' import { SafeList } from '@/components/chat/SafeList' import FormattedName from '@/components/common/FormattedName/FormattedName' import Identicon from '@/components/common/Identicon' @@ -8,9 +7,9 @@ import { AppRoutes } from '@/config/routes' import useSafeInfo from '@/hooks/useSafeInfo' import useWallet from '@/hooks/wallets/useWallet' import SettingsIcon from '@/public/images/chat/settings-svgrepo-com.svg' -import ViewSidebarIcon from '@/public/images/chat/sidebar-right-svgrepo-com.svg' -import { ArrowBackIos } from '@mui/icons-material' import { useAppDispatch } from '@/store' +import { openModal } from '@/store/modalServiceSlice' +import { ArrowBackIos } from '@mui/icons-material' import { Box, Container, Drawer, @@ -24,12 +23,10 @@ import Head from 'next/head' import Link from 'next/link' import { useRouter } from 'next/router' import React, { useEffect, useState } from 'react' -import { openModal } from '@/store/modalServiceSlice' -import { modalTypes } from '@/components/chat/modals' const ChatWrapper = dynamic(() => import('@/components/chat/ChatWrapper'), { ssr: false }) -const drawerWidth = 360 +const drawerWidth = 450 const Main = styled('div', { shouldForwardProp: (prop) => prop !== 'open' })<{ open?: boolean @@ -97,8 +94,6 @@ const Chat = () => { return ( <> - {app && handleToggleApp()} />} - {createSafe && setCreateSafe(!createSafe)} />} Decentra — Chat @@ -121,7 +116,7 @@ const Chat = () => { variant="permanent" anchor="left" > - + }
@@ -162,11 +157,11 @@ const Chat = () => { onClick={() => dispatch(openModal({ modalName: modalTypes.settingsModal, modalProps: '' }))}> - {matchesDesktop && + {/* {matchesDesktop && {open ? : } - } + } */} } @@ -211,30 +206,29 @@ const Chat = () => {
- {matchesDesktop && - - - - } + + {/* // + // + // */}
) diff --git a/src/pages/safe-list.tsx b/src/pages/safe-list.tsx index b0b2e35be69..2935e967812 100644 --- a/src/pages/safe-list.tsx +++ b/src/pages/safe-list.tsx @@ -1,21 +1,11 @@ import MobileChatFooter from "@/components/chat/mobileChatFooter" -import ViewCreateSafe from "@/components/chat/modals/CreateSafe" import { SafeList } from "@/components/chat/SafeList" -import { useRouter } from "next/router" -import React, { useEffect, useState } from "react" +import React from "react" const SafePage: React.FC = () => { - const router = useRouter() - const [createSafe, setCreateSafe] = useState(false) - useEffect(() => { - if (router.asPath.includes('chain')) { - setCreateSafe(true) - } - }, [router.asPath]) return ( <> - {createSafe && setCreateSafe(!createSafe)} />} - + ) diff --git a/src/services/supabase/index.tsx b/src/services/supabase/index.tsx index fa0cfb20c67..ae78b0bc040 100644 --- a/src/services/supabase/index.tsx +++ b/src/services/supabase/index.tsx @@ -1,66 +1,66 @@ -import { createClient } from '@supabase/supabase-js' - -const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_PROJECT_URL! -const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_API_KEY! -const supabase = createClient(supabaseUrl, supabaseKey) - - -export const insertDescription = async (id: string, description: string, owner: string, auth: string) => { - let supabaseAuthenticated = createClient(supabaseUrl, supabaseKey, - { - global: { - headers: { - Authorization: `Bearer ${auth}`, - }, - }, - // This was in the documentation but doesn't work at all - // See: https://supabase.com/docs/guides/realtime/extensions/postgres-changes#custom-tokens - // realtime: { - // headers: { - // apikey: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, - // }, - // params: { - // apikey: accessToken, - // }, - // }, - } - ); - supabaseAuthenticated.realtime.accessToken = auth - if (!supabaseAuthenticated) { - throw new Error('Supabase is not initialized') - } - if (!id || !description || !owner) { - throw new Error('Invalid arguments') - } - try { - const { data, error } = await supabaseAuthenticated - .from('descriptions') - .insert([ - { - id: id, - description: description, - owner: owner, - }, - ]) - .select() - } catch (error) { - console.log(error, 'error') - } -} - -export const readDescription = async (id: string, auth: string) => { - let supabaseAuthenticated = createClient(supabaseUrl, supabaseKey, { - global: { - headers: { - Authorization: `Bearer ${auth}`, - }, - }, - }); - let { data: descriptions, error } = await supabaseAuthenticated - .from('descriptions') - .select('owner, description') - .eq('id', id) - - console.log(descriptions, 'descriptions') - return descriptions +import { createClient } from '@supabase/supabase-js' + +const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_PROJECT_URL! +const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_API_KEY! +const supabase = createClient(supabaseUrl, supabaseKey) + + +export const insertDescription = async (id: string, description: string, owner: string, auth: string) => { + let supabaseAuthenticated = createClient(supabaseUrl, supabaseKey, + { + global: { + headers: { + Authorization: `Bearer ${auth}`, + }, + }, + // This was in the documentation but doesn't work at all + // See: https://supabase.com/docs/guides/realtime/extensions/postgres-changes#custom-tokens + // realtime: { + // headers: { + // apikey: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, + // }, + // params: { + // apikey: accessToken, + // }, + // }, + } + ); + supabaseAuthenticated.realtime.accessToken = auth + if (!supabaseAuthenticated) { + throw new Error('Supabase is not initialized') + } + if (!id || !description || !owner) { + throw new Error('Invalid arguments') + } + try { + const { data, error } = await supabaseAuthenticated + .from('descriptions') + .insert([ + { + id: id, + description: description, + owner: owner, + }, + ]) + .select() + } catch (error) { + console.log(error, 'error') + } +} + +export const readDescription = async (id: string, auth: string) => { + let supabaseAuthenticated = createClient(supabaseUrl, supabaseKey, { + global: { + headers: { + Authorization: `Bearer ${auth}`, + }, + }, + }); + let { data: descriptions, error } = await supabaseAuthenticated + .from('descriptions') + .select('owner, description') + .eq('id', id) + + console.log(descriptions, 'descriptions') + return descriptions } \ No newline at end of file diff --git a/src/utils/did.ts b/src/utils/did.ts index 31f88352616..434f2dbbc43 100644 --- a/src/utils/did.ts +++ b/src/utils/did.ts @@ -1,76 +1,76 @@ -import type { providers } from 'ethers'; -import { Base64 } from 'js-base64'; - -import { getSignature, verifySignature } from './ethereumHelpers'; - -const WELCOME_MESSAGE = `Welcome to Decentra! - -Please sign this message to prove that you own this address. - -This request will not trigger a blockchain transaction or cost any gas fees. - -Your authentication status will reset after 7 days. - -`; - -type Claim = { - timestamp: Date; - authexpiration: Date; - walletaddress: string; - nonce: string; -}; - -export async function createToken( - provider: providers.Web3Provider, - msg: string, -): Promise { - const signer = provider.getSigner(); - const address = await signer.getAddress(); - const timestamp = +new Date(); - - const claim = { - timestamp, - walletaddress: address, - nonce: msg, - }; - - const serializedClaim = JSON.stringify(claim); - const msgToSign = `${WELCOME_MESSAGE}${serializedClaim}`; - - const proof = await getSignature(provider, msgToSign); - - return Base64.encode(JSON.stringify([proof, serializedClaim])); -} - -export async function verifyToken( - token: string, - provider: providers.JsonRpcProvider, - connectedAddress?: string, -): Promise { - - const rawToken = Base64.decode(token); - - const [proof, rawClaim] = JSON.parse(rawToken); - - const claim: Claim = JSON.parse(rawClaim); - - const claimant = claim.walletaddress; - console.log('claimant', claimant, claim) - - if (connectedAddress != null && claimant !== connectedAddress) { - console.log('Connected address ≠ claim issuer', claimant, connectedAddress) - throw new Error( - `Connected address (${connectedAddress}) ≠ claim issuer (${claimant}).`, - ); - } - - const msgToVerify = `${WELCOME_MESSAGE}${rawClaim}`; - const valid = await verifySignature(claimant, msgToVerify, proof, provider); - - if (!valid) { - console.log('Invalid Signature') - throw new Error('Invalid Signature'); - } - - return claim; -} +import type { providers } from 'ethers'; +import { Base64 } from 'js-base64'; + +import { getSignature, verifySignature } from './ethereumHelpers'; + +const WELCOME_MESSAGE = `Welcome to Decentra! + +Please sign this message to prove that you own this address. + +This request will not trigger a blockchain transaction or cost any gas fees. + +Your authentication status will reset after 7 days. + +`; + +type Claim = { + timestamp: Date; + authexpiration: Date; + walletaddress: string; + nonce: string; +}; + +export async function createToken( + provider: providers.Web3Provider, + msg: string, +): Promise { + const signer = provider.getSigner(); + const address = await signer.getAddress(); + const timestamp = +new Date(); + + const claim = { + timestamp, + walletaddress: address, + nonce: msg, + }; + + const serializedClaim = JSON.stringify(claim); + const msgToSign = `${WELCOME_MESSAGE}${serializedClaim}`; + + const proof = await getSignature(provider, msgToSign); + + return Base64.encode(JSON.stringify([proof, serializedClaim])); +} + +export async function verifyToken( + token: string, + provider: providers.JsonRpcProvider, + connectedAddress?: string, +): Promise { + + const rawToken = Base64.decode(token); + + const [proof, rawClaim] = JSON.parse(rawToken); + + const claim: Claim = JSON.parse(rawClaim); + + const claimant = claim.walletaddress; + console.log('claimant', claimant, claim) + + if (connectedAddress != null && claimant !== connectedAddress) { + console.log('Connected address ≠ claim issuer', claimant, connectedAddress) + throw new Error( + `Connected address (${connectedAddress}) ≠ claim issuer (${claimant}).`, + ); + } + + const msgToVerify = `${WELCOME_MESSAGE}${rawClaim}`; + const valid = await verifySignature(claimant, msgToVerify, proof, provider); + + if (!valid) { + console.log('Invalid Signature') + throw new Error('Invalid Signature'); + } + + return claim; +}