From c34e66aa938083ee0f49cd044f6bb0314ac57eb2 Mon Sep 17 00:00:00 2001 From: Nabarun Date: Wed, 13 Nov 2024 13:32:27 +0000 Subject: [PATCH] Integrate wallet IFrame for payments (#42) Part of [Service provider auctions for web deployments](https://www.notion.so/Service-provider-auctions-for-web-deployments-104a6b22d47280dbad51d28aa3a91d75) - Replace wallet connect with iframe to display wallet in modal for deployment payments ![image](/attachments/b253d833-0730-45b7-8b65-b0af6d24678a) Co-authored-by: Isha Co-authored-by: Adw8 Co-authored-by: IshaVenikar Reviewed-on: https://git.vdb.to/cerc-io/snowballtools-base/pulls/42 Co-authored-by: Nabarun Co-committed-by: Nabarun --- build-webapp.sh | 1 + packages/deployer/.gitignore | 2 + packages/deployer/deploy-frontend.sh | 1 + .../application-deployment-request.yml | 18 -- .../deployer/records/application-record.yml | 8 - .../application-deployment-request.yml | 25 -- .../staging-records/application-record.yml | 8 - packages/frontend/.env.example | 1 + .../Dialog/ChangeStateToProductionDialog.tsx | 5 + .../projects/create/AccountsDropdown.tsx | 60 +++++ .../components/projects/create/Configure.tsx | 213 +++++++++++------- .../projects/create/ConnectWallet.tsx | 47 ---- .../projects/create/IFrameModal.tsx | 88 ++++++++ .../src/context/WalletConnectContext.tsx | 210 ----------------- packages/frontend/src/index.tsx | 23 +- .../src/pages/org-slug/projects/Id.tsx | 4 +- .../pages/org-slug/projects/create/layout.tsx | 126 +++++------ .../pages/org-slug/projects/id/Overview.tsx | 15 +- .../org-slug/projects/id/settings/General.tsx | 1 - packages/frontend/src/utils/constants.ts | 1 + 20 files changed, 377 insertions(+), 480 deletions(-) create mode 100644 packages/deployer/.gitignore delete mode 100644 packages/deployer/records/application-deployment-request.yml delete mode 100644 packages/deployer/records/application-record.yml delete mode 100644 packages/deployer/staging-records/application-deployment-request.yml delete mode 100644 packages/deployer/staging-records/application-record.yml create mode 100644 packages/frontend/src/components/projects/create/AccountsDropdown.tsx delete mode 100644 packages/frontend/src/components/projects/create/ConnectWallet.tsx create mode 100644 packages/frontend/src/components/projects/create/IFrameModal.tsx delete mode 100644 packages/frontend/src/context/WalletConnectContext.tsx diff --git a/build-webapp.sh b/build-webapp.sh index e6476e6f..b289f28f 100755 --- a/build-webapp.sh +++ b/build-webapp.sh @@ -17,6 +17,7 @@ VITE_GITHUB_IMAGE_UPLOAD_PWA_TEMPLATE_REPO = 'LACONIC_HOSTED_CONFIG_github_image VITE_GITHUB_NEXT_APP_TEMPLATE_REPO = 'LACONIC_HOSTED_CONFIG_github_next_app_templaterepo' VITE_WALLET_CONNECT_ID = 'LACONIC_HOSTED_CONFIG_wallet_connect_id' VITE_LACONICD_CHAIN_ID = 'LACONIC_HOSTED_CONFIG_laconicd_chain_id' +VITE_WALLET_IFRAME_URL = 'LACONIC_HOSTED_CONFIG_wallet_iframe_url' VITE_LIT_RELAY_API_KEY = 'LACONIC_HOSTED_CONFIG_lit_relay_api_key' VITE_BUGSNAG_API_KEY = 'LACONIC_HOSTED_CONFIG_bugsnag_api_key' VITE_PASSKEY_WALLET_RPID = 'LACONIC_HOSTED_CONFIG_passkey_wallet_rpid' diff --git a/packages/deployer/.gitignore b/packages/deployer/.gitignore new file mode 100644 index 00000000..ffaf3b27 --- /dev/null +++ b/packages/deployer/.gitignore @@ -0,0 +1,2 @@ +records/* +staging-records/* diff --git a/packages/deployer/deploy-frontend.sh b/packages/deployer/deploy-frontend.sh index 6365ada2..edbafa6d 100755 --- a/packages/deployer/deploy-frontend.sh +++ b/packages/deployer/deploy-frontend.sh @@ -135,6 +135,7 @@ record: LACONIC_HOSTED_CONFIG_github_next_app_templaterepo: laconic-templates/starter.nextjs-react-tailwind LACONIC_HOSTED_CONFIG_wallet_connect_id: 63cad7ba97391f63652161f484670e15 LACONIC_HOSTED_CONFIG_laconicd_chain_id: laconic-testnet-2 + LACONIC_HOSTED_CONFIG_wallet_iframe_url: https://wallet.laconic.com meta: note: Added @ $CURRENT_DATE_TIME repository: "$REPO_URL" diff --git a/packages/deployer/records/application-deployment-request.yml b/packages/deployer/records/application-deployment-request.yml deleted file mode 100644 index 2b53d579..00000000 --- a/packages/deployer/records/application-deployment-request.yml +++ /dev/null @@ -1,18 +0,0 @@ -record: - type: ApplicationDeploymentRequest - version: '1.0.0' - name: deploy-frontend@1.0.0 - application: lrn://vaasl/applications/deploy-frontend@1.0.0 - dns: deploy - config: - env: - LACONIC_HOSTED_CONFIG_server_url: https://deploy-backend.apps.vaasl.io - LACONIC_HOSTED_CONFIG_github_clientid: Ov23liaet4yc0KX0iM1c - LACONIC_HOSTED_CONFIG_github_pwa_templaterepo: laconic-templates/test-progressive-web-app - LACONIC_HOSTED_CONFIG_github_image_upload_templaterepo: laconic-templates/image-upload-pwa-example - LACONIC_HOSTED_CONFIG_github_next_app_templaterepo: laconic-templates/starter.nextjs-react-tailwind - LACONIC_HOSTED_CONFIG_wallet_connect_id: 63cad7ba97391f63652161f484670e15 - meta: - note: Added by Snowball @ Thu Apr 4 14:49:41 UTC 2024 - repository: "https://git.vdb.to/cerc-io/snowballtools-base" - repository_ref: 351db16336eacc3e1f9119ceb8d1282b8e27a27e diff --git a/packages/deployer/records/application-record.yml b/packages/deployer/records/application-record.yml deleted file mode 100644 index 9e23075f..00000000 --- a/packages/deployer/records/application-record.yml +++ /dev/null @@ -1,8 +0,0 @@ -record: - type: ApplicationRecord - version: 0.0.2 - repository_ref: 351db16336eacc3e1f9119ceb8d1282b8e27a27e - repository: ["https://git.vdb.to/cerc-io/snowballtools-base"] - app_type: webapp - name: deploy-frontend - app_version: 1.0.0 diff --git a/packages/deployer/staging-records/application-deployment-request.yml b/packages/deployer/staging-records/application-deployment-request.yml deleted file mode 100644 index fd7ee288..00000000 --- a/packages/deployer/staging-records/application-deployment-request.yml +++ /dev/null @@ -1,25 +0,0 @@ -record: - type: ApplicationDeploymentRequest - version: '1.0.0' - name: staging-snowballtools-base-frontend@0.0.0 - application: crn://staging-snowballtools/applications/staging-snowballtools-base-frontend@0.0.0 - dns: dashboard.staging.apps.snowballtools.com - config: - env: - LACONIC_HOSTED_CONFIG_server_url: https://snowballtools-base-api.staging.apps.snowballtools.com - LACONIC_HOSTED_CONFIG_github_clientid: Ov23liOaoahRTYd4nSCV - LACONIC_HOSTED_CONFIG_github_templaterepo: snowball-tools/test-progressive-web-app - LACONIC_HOSTED_CONFIG_github_pwa_templaterepo: snowball-tools/test-progressive-web-app - LACONIC_HOSTED_CONFIG_github_image_upload_templaterepo: snowball-tools/image-upload-pwa-example - LACONIC_HOSTED_CONFIG_github_next_app_templaterepo: snowball-tools/starter.nextjs-react-tailwind - LACONIC_HOSTED_CONFIG_wallet_connect_id: eda9ba18042a5ea500f358194611ece2 - LACONIC_HOSTED_CONFIG_lit_relay_api_key: 15DDD969-E75F-404D-AAD9-58A37C4FD354_snowball - LACONIC_HOSTED_CONFIG_aplchemy_api_key: THvPart_gqI5x02RNYSBntlmwA66I_qc - LACONIC_HOSTED_CONFIG_bugsnag_api_key: 8c480cd5386079f9dd44f9581264a073 - LACONIC_HOSTED_CONFIG_passkey_wallet_rpid: dashboard.staging.apps.snowballtools.com - LACONIC_HOSTED_CONFIG_turnkey_api_base_url: https://api.turnkey.com - LACONIC_HOSTED_CONFIG_turnkey_organization_id: 5049ae99-5bca-40b3-8317-504384d4e591 - meta: - note: Added by Snowball @ Mon Jun 24 23:51:48 UTC 2024 - repository: "https://git.vdb.to/cerc-io/snowballtools-base" - repository_ref: 61e3e88a6c9d57e95441059369ee5a46f5c07601 diff --git a/packages/deployer/staging-records/application-record.yml b/packages/deployer/staging-records/application-record.yml deleted file mode 100644 index fa9b4089..00000000 --- a/packages/deployer/staging-records/application-record.yml +++ /dev/null @@ -1,8 +0,0 @@ -record: - type: ApplicationRecord - version: 0.0.1 - repository_ref: 61e3e88a6c9d57e95441059369ee5a46f5c07601 - repository: ["https://git.vdb.to/cerc-io/snowballtools-base"] - app_type: webapp - name: staging-snowballtools-base-frontend - app_version: 0.0.0 diff --git a/packages/frontend/.env.example b/packages/frontend/.env.example index 88f6bc1f..62241552 100644 --- a/packages/frontend/.env.example +++ b/packages/frontend/.env.example @@ -15,3 +15,4 @@ VITE_PASSKEY_WALLET_RPID= VITE_TURNKEY_API_BASE_URL= VITE_LACONICD_CHAIN_ID= +VITE_WALLET_IFRAME_URL= diff --git a/packages/frontend/src/components/projects/Dialog/ChangeStateToProductionDialog.tsx b/packages/frontend/src/components/projects/Dialog/ChangeStateToProductionDialog.tsx index efb9c2fa..be68a445 100644 --- a/packages/frontend/src/components/projects/Dialog/ChangeStateToProductionDialog.tsx +++ b/packages/frontend/src/components/projects/Dialog/ChangeStateToProductionDialog.tsx @@ -47,6 +47,11 @@ export const ChangeStateToProductionDialog = ({ handleCancel={handleCancel} open={open} handleConfirm={handleConfirm} + confirmButtonTitle={ + isConfirmButtonLoading + ? 'Redeploying' + : 'Redeploy' + } confirmButtonProps={{ disabled: isConfirmButtonLoading, rightIcon: isConfirmButtonLoading ? ( diff --git a/packages/frontend/src/components/projects/create/AccountsDropdown.tsx b/packages/frontend/src/components/projects/create/AccountsDropdown.tsx new file mode 100644 index 00000000..4ef0a61f --- /dev/null +++ b/packages/frontend/src/components/projects/create/AccountsDropdown.tsx @@ -0,0 +1,60 @@ +import { + Select, + Option, + Spinner, +} from '@snowballtools/material-tailwind-react-fork'; + +const AccountsDropdown = ({ + accounts, + isDataReceived, + onAccountChange, +}: { + accounts: string[]; + isDataReceived: boolean; + onAccountChange: (selectedAccount: string) => void; +}) => { + return ( +
+ {isDataReceived ? ( + !accounts.length ? ( +
+

+ No accounts found. Please visit{' '} + + store.laconic.com + {' '} + to create a wallet. +

+
+ ) : ( +
+ +
+ ) + ) : ( +
+ +
+ )} +
+ ); +}; + +export default AccountsDropdown; diff --git a/packages/frontend/src/components/projects/create/Configure.tsx b/packages/frontend/src/components/projects/create/Configure.tsx index 4dae836b..c541cd6d 100644 --- a/packages/frontend/src/components/projects/create/Configure.tsx +++ b/packages/frontend/src/components/projects/create/Configure.tsx @@ -20,10 +20,14 @@ import { Button } from '../../shared/Button'; import { Input } from 'components/shared/Input'; import { useToast } from 'components/shared/Toast'; import { useGQLClient } from '../../../context/GQLClientContext'; +import IFrameModal from './IFrameModal'; import EnvironmentVariablesForm from 'pages/org-slug/projects/id/settings/EnvironmentVariablesForm'; import { EnvironmentVariablesFormValues } from 'types/types'; -import ConnectWallet from './ConnectWallet'; -import { useWalletConnectClient } from 'context/WalletConnectContext'; +import { + VITE_LACONICD_CHAIN_ID, + VITE_WALLET_IFRAME_URL, +} from 'utils/constants'; +import AccountsDropdown from './AccountsDropdown'; type ConfigureDeploymentFormValues = { option: string; @@ -36,16 +40,18 @@ type ConfigureFormValues = ConfigureDeploymentFormValues & EnvironmentVariablesFormValues; const DEFAULT_MAX_PRICE = '10000'; +const TX_APPROVAL_TIMEOUT_MS = 60000; const Configure = () => { - const { signClient, session, accounts } = useWalletConnectClient(); - const [isLoading, setIsLoading] = useState(false); const [deployers, setDeployers] = useState([]); const [selectedAccount, setSelectedAccount] = useState(); + const [accounts, setAccounts] = useState([]); const [selectedDeployer, setSelectedDeployer] = useState(); const [isPaymentLoading, setIsPaymentLoading] = useState(false); const [isPaymentDone, setIsPaymentDone] = useState(false); + const [isFrameVisible, setIsFrameVisible] = useState(false); + const [isAccountsDataReceived, setIsAccountsDataReceived] = useState(false); const [searchParams] = useSearchParams(); const templateId = searchParams.get('templateId'); @@ -182,7 +188,7 @@ const Configure = () => { let amount: string; let senderAddress: string; - let txHash: string; + let txHash: string | null = null; if (createFormData.option === 'LRN' && !deployer?.minimumPayment) { toast({ id: 'no-payment-required', @@ -196,7 +202,7 @@ const Configure = () => { } else { if (!selectedAccount) return; - senderAddress = selectedAccount.split(':')[2]; + senderAddress = selectedAccount; if (createFormData.option === 'LRN') { amount = deployer?.minimumPayment!; @@ -208,27 +214,40 @@ const Configure = () => { const amountToBePaid = amount.replace(/\D/g, '').toString(); - const txHashResponse = await cosmosSendTokensHandler( - selectedAccount, - amountToBePaid, - ); + txHash = await cosmosSendTokensHandler(senderAddress, amountToBePaid); - if (!txHashResponse) { - console.error('Tx not successful'); - return; + if (!txHash) { + toast({ + id: 'unsuccessful-tx', + title: 'Transaction rejected', + variant: 'error', + onDismiss: dismiss, + }); + setIsFrameVisible(false); + setIsPaymentLoading(false); + throw new Error('Transaction rejected'); } - txHash = txHashResponse; + // Validate transaction hash + const isTxHashValid = await verifyTx(senderAddress, txHash, amountToBePaid); + setIsPaymentLoading(false); - const isTxHashValid = await verifyTx( - senderAddress, - txHash, - amountToBePaid.toString(), - ); - - if (isTxHashValid === false) { - console.error('Invalid Tx hash', txHash); - return; + if (isTxHashValid) { + toast({ + id: 'payment-successful', + title: 'Payment successful', + variant: 'success', + onDismiss: dismiss, + }); + setIsPaymentDone(true); + } else { + toast({ + id: 'invalid-tx-hash', + title: 'Transaction validation failed', + variant: 'error', + onDismiss: dismiss, + }); + throw new Error('Transaction validation failed'); } } @@ -248,7 +267,7 @@ const Configure = () => { createFormData, environmentVariables, senderAddress, - txHash, + txHash!, ); await client.getEnvironmentVariables(projectId); @@ -270,14 +289,14 @@ const Configure = () => { `/${orgSlug}/projects/create/deploy?projectId=${projectId}`, ); } - } catch (error) { - console.error(error); + } catch (error: any) { toast({ id: 'error-deploying-app', title: 'Error deploying app', variant: 'error', onDismiss: dismiss, }); + throw new Error(error); } }, [client, createProject, dismiss, toast], @@ -302,72 +321,95 @@ const Configure = () => { const cosmosSendTokensHandler = useCallback( async (selectedAccount: string, amount: string) => { - if (!signClient || !session || !selectedAccount) { - return; + if (!selectedAccount) { + throw new Error('Account not selected'); } - const chainId = selectedAccount.split(':')[1]; - const senderAddress = selectedAccount.split(':')[2]; + const senderAddress = selectedAccount; const snowballAddress = await client.getAddress(); + let timeoutId; try { setIsPaymentDone(false); setIsPaymentLoading(true); - toast({ - id: 'sending-payment-request', - title: 'Check your wallet and approve payment request', - variant: 'loading', - onDismiss: dismiss, + await requestTx(senderAddress, snowballAddress, amount); + + const txHash = await new Promise((resolve, reject) => { + const handleTxStatus = async (event: MessageEvent) => { + if (event.origin !== VITE_WALLET_IFRAME_URL) return; + + if (event.data.type === 'TRANSACTION_RESPONSE') { + const txResponse = event.data.data; + resolve(txResponse); + } else if (event.data.type === 'ERROR') { + console.error('Error from wallet:', event.data.message); + reject(new Error('Transaction failed')); + toast({ + id: 'error-transaction', + title: 'Error during transaction', + variant: 'error', + onDismiss: dismiss, + }); + } + setIsFrameVisible(false); + + window.removeEventListener('message', handleTxStatus); + }; + + window.addEventListener('message', handleTxStatus); + + // Set a timeout, consider unsuccessful after 1 min + timeoutId = setTimeout(() => { + reject(new Error('Transaction timeout')); + window.removeEventListener('message', handleTxStatus); + toast({ + id: 'transaction-timeout', + title: 'The transaction request timed out. Please try again', + variant: 'error', + onDismiss: dismiss, + }); + setIsFrameVisible(false); + setIsPaymentLoading(false); + }, TX_APPROVAL_TIMEOUT_MS); }); - - const result: { signature: string } = await signClient.request({ - topic: session.topic, - chainId: `cosmos:${chainId}`, - request: { - method: 'cosmos_sendTokens', - params: [ - { - from: senderAddress, - to: snowballAddress, - value: amount, - }, - ], - }, - }); - - if (!result) { - throw new Error('Error completing transaction'); - } - - toast({ - id: 'payment-successful', - title: 'Payment successful', - variant: 'success', - onDismiss: dismiss, - }); - - setIsPaymentDone(true); - - return result.signature; - } catch (error: any) { - console.error('Error sending tokens', error); - - toast({ - id: 'error-sending-tokens', - title: 'Error sending tokens', - variant: 'error', - onDismiss: dismiss, - }); - - setIsPaymentDone(false); + return txHash; + } catch (error) { + console.error('Error in transaction:', error); + throw new Error('Error in transaction'); } finally { - setIsPaymentLoading(false); + clearTimeout(timeoutId); } }, - [session, signClient, toast], + [client, dismiss, toast], ); + const requestTx = async ( + sender: string, + recipient: string, + amount: string, + ) => { + const iframe = document.getElementById('walletIframe') as HTMLIFrameElement; + + if (!iframe.contentWindow) { + console.error('Iframe not found or not loaded'); + throw new Error('Iframe not found or not loaded'); + } + + iframe.contentWindow.postMessage( + { + type: 'REQUEST_TX', + chainId: VITE_LACONICD_CHAIN_ID, + fromAddress: sender, + toAddress: recipient, + amount, + }, + VITE_WALLET_IFRAME_URL, + ); + + setIsFrameVisible(true); + }; + useEffect(() => { fetchDeployers(); }, []); @@ -534,10 +576,11 @@ const Configure = () => { ) : ( <> - - Connect to your wallet - - + {accounts.length > 0 && (
); diff --git a/packages/frontend/src/components/projects/create/ConnectWallet.tsx b/packages/frontend/src/components/projects/create/ConnectWallet.tsx deleted file mode 100644 index 3b37fb54..00000000 --- a/packages/frontend/src/components/projects/create/ConnectWallet.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { Select, Option } from '@snowballtools/material-tailwind-react-fork'; - -import { Button } from '../../shared/Button'; -import { useWalletConnectClient } from 'context/WalletConnectContext'; - -const ConnectWallet = ({ - onAccountChange, -}: { - onAccountChange: (selectedAccount: string) => void; -}) => { - const { onConnect, accounts } = useWalletConnectClient(); - - const handleConnect = async () => { - await onConnect(); - }; - - return ( -
- {accounts.length === 0 ? ( -
- -
- ) : ( -
- -
- )} -
- ); -}; - -export default ConnectWallet; diff --git a/packages/frontend/src/components/projects/create/IFrameModal.tsx b/packages/frontend/src/components/projects/create/IFrameModal.tsx new file mode 100644 index 00000000..cb4d2d82 --- /dev/null +++ b/packages/frontend/src/components/projects/create/IFrameModal.tsx @@ -0,0 +1,88 @@ +import { useCallback, useEffect } from 'react'; + +import { Box, Modal } from '@mui/material'; + +import { + VITE_LACONICD_CHAIN_ID, + VITE_WALLET_IFRAME_URL, +} from 'utils/constants'; + +const IFrameModal = ({ + setAccounts, + setIsDataReceived, + isVisible, +}: { + setAccounts: (accounts: string[]) => void; + setIsDataReceived: (isReceived: boolean) => void; + isVisible: boolean; +}) => { + useEffect(() => { + const handleMessage = (event: MessageEvent) => { + if (event.origin !== VITE_WALLET_IFRAME_URL) return; + + setIsDataReceived(true); + if (event.data.type === 'WALLET_ACCOUNTS_DATA') { + setAccounts(event.data.data); + } else if (event.data.type === 'ERROR') { + console.error('Error from wallet:', event.data.message); + } + }; + + window.addEventListener('message', handleMessage); + + return () => { + window.removeEventListener('message', handleMessage); + }; + }, []); + + const getDataFromWallet = useCallback(() => { + const iframe = document.getElementById('walletIframe') as HTMLIFrameElement; + + if (!iframe.contentWindow) { + console.error('Iframe not found or not loaded'); + return; + } + + iframe.contentWindow.postMessage( + { + type: 'REQUEST_WALLET_ACCOUNTS', + chainId: VITE_LACONICD_CHAIN_ID, + }, + VITE_WALLET_IFRAME_URL, + ); + }, []); + + return ( + + + + + + ); +}; + +export default IFrameModal; diff --git a/packages/frontend/src/context/WalletConnectContext.tsx b/packages/frontend/src/context/WalletConnectContext.tsx deleted file mode 100644 index e5dc4fc9..00000000 --- a/packages/frontend/src/context/WalletConnectContext.tsx +++ /dev/null @@ -1,210 +0,0 @@ -import { - createContext, - useCallback, - useContext, - useEffect, - useRef, - useState, -} from 'react'; - -import SignClient from '@walletconnect/sign-client'; -import { getSdkError } from '@walletconnect/utils'; -import { SessionTypes } from '@walletconnect/types'; - -import { walletConnectModal } from '../utils/web3modal'; -import { - VITE_LACONICD_CHAIN_ID, - VITE_WALLET_CONNECT_ID, -} from 'utils/constants'; - -interface ClientInterface { - signClient: SignClient | undefined; - session: SessionTypes.Struct | undefined; - loadingSession: boolean; - onConnect: () => Promise; - onDisconnect: () => Promise; - onSessionDelete: () => void; - accounts: { address: string }[]; -} - -const ClientContext = createContext({} as ClientInterface); - -export const useWalletConnectClient = () => { - return useContext(ClientContext); -}; - -export const WalletConnectClientProvider = ({ - children, -}: { - children: JSX.Element; -}) => { - const [signClient, setSignClient] = useState(); - const [session, setSession] = useState(); - const [loadingSession, setLoadingSession] = useState(true); - const [accounts, setAccounts] = useState<{ address: string }[]>([]); - - const isSignClientInitializing = useRef(false); - - const onSessionConnect = useCallback(async (session: SessionTypes.Struct) => { - setSession(session); - }, []); - - const subscribeToEvents = useCallback( - async (client: SignClient) => { - client.on('session_update', ({ topic, params }) => { - const { namespaces } = params; - const currentSession = client.session.get(topic); - const updatedSession = { ...currentSession, namespaces }; - setSession(updatedSession); - }); - }, - [setSession], - ); - - const onConnect = async () => { - const proposalNamespace = { - cosmos: { - methods: ['cosmos_sendTokens'], - chains: [`cosmos:${VITE_LACONICD_CHAIN_ID}`], - events: [], - }, - }; - - try { - const { uri, approval } = await signClient!.connect({ - requiredNamespaces: proposalNamespace, - }); - - if (uri) { - walletConnectModal.openModal({ uri }); - const session = await approval(); - onSessionConnect(session); - walletConnectModal.closeModal(); - } - } catch (e) { - console.error(e); - } - }; - - const onDisconnect = useCallback(async () => { - if (typeof signClient === 'undefined') { - throw new Error('WalletConnect is not initialized'); - } - if (typeof session === 'undefined') { - throw new Error('Session is not connected'); - } - - await signClient.disconnect({ - topic: session.topic, - reason: getSdkError('USER_DISCONNECTED'), - }); - - onSessionDelete(); - }, [signClient, session]); - - const onSessionDelete = () => { - setAccounts([]); - setSession(undefined); - }; - - const checkPersistedState = useCallback( - async (signClient: SignClient) => { - if (typeof signClient === 'undefined') { - throw new Error('WalletConnect is not initialized'); - } - - if (typeof session !== 'undefined') return; - if (signClient.session.length) { - const lastKeyIndex = signClient.session.keys.length - 1; - const previousSsession = signClient.session.get( - signClient.session.keys[lastKeyIndex], - ); - - await onSessionConnect(previousSsession); - return previousSsession; - } - }, - [session, onSessionConnect], - ); - - const createClient = useCallback(async () => { - isSignClientInitializing.current = true; - try { - const signClient = await SignClient.init({ - projectId: VITE_WALLET_CONNECT_ID, - metadata: { - name: 'Deploy App', - description: '', - url: window.location.href, - icons: ['https://avatars.githubusercontent.com/u/92608123'], - }, - }); - - setSignClient(signClient); - await checkPersistedState(signClient); - await subscribeToEvents(signClient); - setLoadingSession(false); - } catch (e) { - console.error('error in createClient', e); - } - isSignClientInitializing.current = false; - }, [setSignClient, checkPersistedState, subscribeToEvents]); - - useEffect(() => { - if (!signClient && !isSignClientInitializing.current) { - createClient(); - } - }, [signClient, createClient]); - - useEffect(() => { - const populateAccounts = async () => { - if (!session) { - return; - } - if (!session.namespaces['cosmos']) { - console.log('Accounts for cosmos namespace not found'); - return; - } - - const cosmosAddresses = session.namespaces['cosmos'].accounts; - - const cosmosAccounts = cosmosAddresses.map((address) => ({ - address, - })); - - const allAccounts = cosmosAccounts; - - setAccounts(allAccounts); - }; - - populateAccounts(); - }, [session]); - - useEffect(() => { - if (!signClient) { - return; - } - - signClient.on('session_delete', onSessionDelete); - - return () => { - signClient.off('session_delete', onSessionDelete); - }; - }); - - return ( - - {children} - - ); -}; diff --git a/packages/frontend/src/index.tsx b/packages/frontend/src/index.tsx index 787cef61..862eaf67 100644 --- a/packages/frontend/src/index.tsx +++ b/packages/frontend/src/index.tsx @@ -4,10 +4,9 @@ import assert from 'assert'; import { GQLClient } from 'gql-client'; import { ThemeProvider } from '@snowballtools/material-tailwind-react-fork'; - -import './index.css'; import '@fontsource/inter'; import '@fontsource-variable/jetbrains-mono'; + import App from './App'; import reportWebVitals from './reportWebVitals'; import { GQLClientProvider } from './context/GQLClientContext'; @@ -16,7 +15,7 @@ import { Toaster } from 'components/shared/Toast'; import { LogErrorBoundary } from 'utils/log-error'; import { BASE_URL } from 'utils/constants'; import Web3ModalProvider from './context/Web3Provider'; -import { WalletConnectClientProvider } from 'context/WalletConnectContext'; +import './index.css'; console.log(`v-0.0.9`); @@ -32,16 +31,14 @@ const gqlClient = new GQLClient({ gqlEndpoint }); root.render( - - - - - - - - - - + + + + + + + + , ); diff --git a/packages/frontend/src/pages/org-slug/projects/Id.tsx b/packages/frontend/src/pages/org-slug/projects/Id.tsx index 9abce252..fd8ce0e7 100644 --- a/packages/frontend/src/pages/org-slug/projects/Id.tsx +++ b/packages/frontend/src/pages/org-slug/projects/Id.tsx @@ -60,9 +60,9 @@ const Id = () => { fetchProject(id); }, [id]); - const onUpdate = async () => { + const onUpdate = useCallback(async () => { await fetchProject(id); - }; + }, [fetchProject, id]); return (
diff --git a/packages/frontend/src/pages/org-slug/projects/create/layout.tsx b/packages/frontend/src/pages/org-slug/projects/create/layout.tsx index 32c13b78..ab63c951 100644 --- a/packages/frontend/src/pages/org-slug/projects/create/layout.tsx +++ b/packages/frontend/src/pages/org-slug/projects/create/layout.tsx @@ -1,12 +1,14 @@ import { ComponentPropsWithoutRef } from 'react'; import { Link, Outlet, useParams } from 'react-router-dom'; +import { useMediaQuery } from 'usehooks-ts'; + +import * as Dialog from '@radix-ui/react-dialog'; import { Heading } from 'components/shared/Heading'; import { WavyBorder } from 'components/shared/WavyBorder'; import { Button } from 'components/shared/Button'; import { CrossIcon } from 'components/shared/CustomIcon'; import { cn } from 'utils/classnames'; -import * as Dialog from '@radix-ui/react-dialog'; export interface CreateProjectLayoutProps extends ComponentPropsWithoutRef<'section'> {} @@ -16,6 +18,7 @@ export const CreateProjectLayout = ({ ...props }: CreateProjectLayoutProps) => { const { orgSlug } = useParams(); + const isDesktopView = useMediaQuery('(min-width: 720px)'); // md: const closeBtnLink = `/${orgSlug}`; @@ -28,72 +31,69 @@ export const CreateProjectLayout = ({ ); - return ( - <> - {/* Desktop */} - + ) : ( + // Mobile + // Setting modal={false} so even if modal is active on desktop, it doesn't block clicks + + + {/* Not using since modal={false} disables it and its content will not show */} +
+ + {/* Heading */} +
+ {heading} + + +
- {/* Mobile */} - {/* Setting modal={false} so even if modal is active on desktop, it doesn't block clicks */} - - - {/* Not using since modal={false} disables it and its content will not show */} -
- - {/* Heading */} -
- {heading} - - -
+ {/* Border */} + - {/* Border */} - - - {/* Page content */} -
- -
-
-
-
-
- + {/* Page content */} +
+ +
+
+
+
+
); }; diff --git a/packages/frontend/src/pages/org-slug/projects/id/Overview.tsx b/packages/frontend/src/pages/org-slug/projects/id/Overview.tsx index a129250f..7732a6e6 100644 --- a/packages/frontend/src/pages/org-slug/projects/id/Overview.tsx +++ b/packages/frontend/src/pages/org-slug/projects/id/Overview.tsx @@ -24,6 +24,7 @@ import { Domain, DomainStatus } from 'gql-client'; import { AuctionCard } from 'components/projects/project/overview/Activity/AuctionCard'; const COMMITS_PER_PAGE = 4; +const PROJECT_UPDATE_WAIT_MS = 5000; const OverviewTabPanel = () => { const { octokit } = useOctokit(); @@ -33,8 +34,7 @@ const OverviewTabPanel = () => { const [liveDomain, setLiveDomain] = useState(); const client = useGQLClient(); - - const { project } = useOutletContext(); + const { project, onUpdate } = useOutletContext(); useEffect(() => { setFetchingActivities(true); @@ -96,7 +96,16 @@ const OverviewTabPanel = () => { }; fetchRepoActivity(); - }, [octokit, project]); + }, [project.repository]); + + useEffect(() => { + onUpdate(); + const timerId = setInterval(() => { + onUpdate(); + }, PROJECT_UPDATE_WAIT_MS); + + return () => clearInterval(timerId); + }, [onUpdate]); useEffect(() => { const fetchLiveProdDomain = async () => { diff --git a/packages/frontend/src/pages/org-slug/projects/id/settings/General.tsx b/packages/frontend/src/pages/org-slug/projects/id/settings/General.tsx index 4b886151..7a257d8d 100644 --- a/packages/frontend/src/pages/org-slug/projects/id/settings/General.tsx +++ b/packages/frontend/src/pages/org-slug/projects/id/settings/General.tsx @@ -18,7 +18,6 @@ const GeneralTabPanel = () => { const client = useGQLClient(); const { toast } = useToast(); const { project, onUpdate } = useOutletContext(); - console.log(project); const [transferOrganizations, setTransferOrganizations] = useState< SelectOption[] diff --git a/packages/frontend/src/utils/constants.ts b/packages/frontend/src/utils/constants.ts index 446d9d58..046dd444 100644 --- a/packages/frontend/src/utils/constants.ts +++ b/packages/frontend/src/utils/constants.ts @@ -12,3 +12,4 @@ export const VITE_WALLET_CONNECT_ID = import.meta.env.VITE_WALLET_CONNECT_ID; export const VITE_BUGSNAG_API_KEY = import.meta.env.VITE_BUGSNAG_API_KEY; export const VITE_LIT_RELAY_API_KEY = import.meta.env.VITE_LIT_RELAY_API_KEY; export const VITE_LACONICD_CHAIN_ID = import.meta.env.VITE_LACONICD_CHAIN_ID; +export const VITE_WALLET_IFRAME_URL = import.meta.env.VITE_WALLET_IFRAME_URL;