Compare commits
No commits in common. "main" and "zach/ui-fixes" have entirely different histories.
main
...
zach/ui-fi
@ -17,7 +17,6 @@ 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_GITHUB_NEXT_APP_TEMPLATE_REPO = 'LACONIC_HOSTED_CONFIG_github_next_app_templaterepo'
|
||||||
VITE_WALLET_CONNECT_ID = 'LACONIC_HOSTED_CONFIG_wallet_connect_id'
|
VITE_WALLET_CONNECT_ID = 'LACONIC_HOSTED_CONFIG_wallet_connect_id'
|
||||||
VITE_LACONICD_CHAIN_ID = 'LACONIC_HOSTED_CONFIG_laconicd_chain_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_LIT_RELAY_API_KEY = 'LACONIC_HOSTED_CONFIG_lit_relay_api_key'
|
||||||
VITE_BUGSNAG_API_KEY = 'LACONIC_HOSTED_CONFIG_bugsnag_api_key'
|
VITE_BUGSNAG_API_KEY = 'LACONIC_HOSTED_CONFIG_bugsnag_api_key'
|
||||||
VITE_PASSKEY_WALLET_RPID = 'LACONIC_HOSTED_CONFIG_passkey_wallet_rpid'
|
VITE_PASSKEY_WALLET_RPID = 'LACONIC_HOSTED_CONFIG_passkey_wallet_rpid'
|
||||||
|
1
packages/deployer/.gitignore
vendored
1
packages/deployer/.gitignore
vendored
@ -1 +0,0 @@
|
|||||||
records/*.yml
|
|
@ -135,7 +135,6 @@ record:
|
|||||||
LACONIC_HOSTED_CONFIG_github_next_app_templaterepo: laconic-templates/starter.nextjs-react-tailwind
|
LACONIC_HOSTED_CONFIG_github_next_app_templaterepo: laconic-templates/starter.nextjs-react-tailwind
|
||||||
LACONIC_HOSTED_CONFIG_wallet_connect_id: 63cad7ba97391f63652161f484670e15
|
LACONIC_HOSTED_CONFIG_wallet_connect_id: 63cad7ba97391f63652161f484670e15
|
||||||
LACONIC_HOSTED_CONFIG_laconicd_chain_id: laconic-testnet-2
|
LACONIC_HOSTED_CONFIG_laconicd_chain_id: laconic-testnet-2
|
||||||
LACONIC_HOSTED_CONFIG_wallet_iframe_url: https://wallet.laconic.com
|
|
||||||
meta:
|
meta:
|
||||||
note: Added @ $CURRENT_DATE_TIME
|
note: Added @ $CURRENT_DATE_TIME
|
||||||
repository: "$REPO_URL"
|
repository: "$REPO_URL"
|
||||||
|
18
packages/deployer/records/application-deployment-request.yml
Normal file
18
packages/deployer/records/application-deployment-request.yml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
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
|
8
packages/deployer/records/application-record.yml
Normal file
8
packages/deployer/records/application-record.yml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
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
|
@ -1,6 +1,7 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
source .env
|
source .env
|
||||||
|
echo "Using REGISTRY_BOND_ID: $REGISTRY_BOND_ID"
|
||||||
echo "Using DEPLOYER_LRN: $DEPLOYER_LRN"
|
echo "Using DEPLOYER_LRN: $DEPLOYER_LRN"
|
||||||
|
|
||||||
# Generate application-deployment-removal-request.yml
|
# Generate application-deployment-removal-request.yml
|
||||||
|
@ -0,0 +1,25 @@
|
|||||||
|
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
|
8
packages/deployer/staging-records/application-record.yml
Normal file
8
packages/deployer/staging-records/application-record.yml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
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
|
@ -15,4 +15,3 @@ VITE_PASSKEY_WALLET_RPID=
|
|||||||
VITE_TURNKEY_API_BASE_URL=
|
VITE_TURNKEY_API_BASE_URL=
|
||||||
|
|
||||||
VITE_LACONICD_CHAIN_ID=
|
VITE_LACONICD_CHAIN_ID=
|
||||||
VITE_WALLET_IFRAME_URL=
|
|
||||||
|
@ -47,7 +47,6 @@ export const ChangeStateToProductionDialog = ({
|
|||||||
handleCancel={handleCancel}
|
handleCancel={handleCancel}
|
||||||
open={open}
|
open={open}
|
||||||
handleConfirm={handleConfirm}
|
handleConfirm={handleConfirm}
|
||||||
confirmButtonTitle={isConfirmButtonLoading ? 'Redeploying' : 'Redeploy'}
|
|
||||||
confirmButtonProps={{
|
confirmButtonProps={{
|
||||||
disabled: isConfirmButtonLoading,
|
disabled: isConfirmButtonLoading,
|
||||||
rightIcon: isConfirmButtonLoading ? (
|
rightIcon: isConfirmButtonLoading ? (
|
||||||
|
@ -1,60 +0,0 @@
|
|||||||
import {
|
|
||||||
Select,
|
|
||||||
Option,
|
|
||||||
Spinner,
|
|
||||||
} from '@snowballtools/material-tailwind-react-fork';
|
|
||||||
|
|
||||||
const AccountsDropdown = ({
|
|
||||||
accounts,
|
|
||||||
isDataReceived,
|
|
||||||
onAccountChange,
|
|
||||||
}: {
|
|
||||||
accounts: string[];
|
|
||||||
isDataReceived: boolean;
|
|
||||||
onAccountChange: (selectedAccount: string) => void;
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<div className="p-6 bg-slate-100 dark:bg-overlay3 rounded-lg mb-6 shadow-md">
|
|
||||||
{isDataReceived ? (
|
|
||||||
!accounts.length ? (
|
|
||||||
<div className="text-center">
|
|
||||||
<p className="text-gray-700 dark:text-gray-300 mb-4">
|
|
||||||
No accounts found. Please visit{' '}
|
|
||||||
<a
|
|
||||||
href="https://store.laconic.com"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="text-blue-600 underline dark:text-blue-400"
|
|
||||||
>
|
|
||||||
store.laconic.com
|
|
||||||
</a>{' '}
|
|
||||||
to create a wallet.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div>
|
|
||||||
<Select
|
|
||||||
label="Select Account"
|
|
||||||
defaultValue={accounts[0]}
|
|
||||||
onChange={(value) => value && onAccountChange(value)}
|
|
||||||
className="dark:bg-overlay2 dark:text-foreground"
|
|
||||||
aria-label="Wallet Account Selector"
|
|
||||||
>
|
|
||||||
{accounts.map((account, index) => (
|
|
||||||
<Option key={index} value={account}>
|
|
||||||
{account}
|
|
||||||
</Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
) : (
|
|
||||||
<div className="flex items-center justify-center h-12">
|
|
||||||
<Spinner className="h-6 w-6" />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AccountsDropdown;
|
|
@ -20,14 +20,10 @@ import { Button } from '../../shared/Button';
|
|||||||
import { Input } from 'components/shared/Input';
|
import { Input } from 'components/shared/Input';
|
||||||
import { useToast } from 'components/shared/Toast';
|
import { useToast } from 'components/shared/Toast';
|
||||||
import { useGQLClient } from '../../../context/GQLClientContext';
|
import { useGQLClient } from '../../../context/GQLClientContext';
|
||||||
import IFrameModal from './IFrameModal';
|
|
||||||
import EnvironmentVariablesForm from 'pages/org-slug/projects/id/settings/EnvironmentVariablesForm';
|
import EnvironmentVariablesForm from 'pages/org-slug/projects/id/settings/EnvironmentVariablesForm';
|
||||||
import { EnvironmentVariablesFormValues } from 'types/types';
|
import { EnvironmentVariablesFormValues } from 'types/types';
|
||||||
import {
|
import ConnectWallet from './ConnectWallet';
|
||||||
VITE_LACONICD_CHAIN_ID,
|
import { useWalletConnectClient } from 'context/WalletConnectContext';
|
||||||
VITE_WALLET_IFRAME_URL,
|
|
||||||
} from 'utils/constants';
|
|
||||||
import AccountsDropdown from './AccountsDropdown';
|
|
||||||
|
|
||||||
type ConfigureDeploymentFormValues = {
|
type ConfigureDeploymentFormValues = {
|
||||||
option: string;
|
option: string;
|
||||||
@ -40,18 +36,16 @@ type ConfigureFormValues = ConfigureDeploymentFormValues &
|
|||||||
EnvironmentVariablesFormValues;
|
EnvironmentVariablesFormValues;
|
||||||
|
|
||||||
const DEFAULT_MAX_PRICE = '10000';
|
const DEFAULT_MAX_PRICE = '10000';
|
||||||
const TX_APPROVAL_TIMEOUT_MS = 60000;
|
|
||||||
|
|
||||||
const Configure = () => {
|
const Configure = () => {
|
||||||
|
const { signClient, session, accounts } = useWalletConnectClient();
|
||||||
|
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [deployers, setDeployers] = useState<Deployer[]>([]);
|
const [deployers, setDeployers] = useState<Deployer[]>([]);
|
||||||
const [selectedAccount, setSelectedAccount] = useState<string>();
|
const [selectedAccount, setSelectedAccount] = useState<string>();
|
||||||
const [accounts, setAccounts] = useState<string[]>([]);
|
|
||||||
const [selectedDeployer, setSelectedDeployer] = useState<Deployer>();
|
const [selectedDeployer, setSelectedDeployer] = useState<Deployer>();
|
||||||
const [isPaymentLoading, setIsPaymentLoading] = useState(false);
|
const [isPaymentLoading, setIsPaymentLoading] = useState(false);
|
||||||
const [isPaymentDone, setIsPaymentDone] = useState(false);
|
const [isPaymentDone, setIsPaymentDone] = useState(false);
|
||||||
const [isFrameVisible, setIsFrameVisible] = useState(false);
|
|
||||||
const [isAccountsDataReceived, setIsAccountsDataReceived] = useState(false);
|
|
||||||
|
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const templateId = searchParams.get('templateId');
|
const templateId = searchParams.get('templateId');
|
||||||
@ -188,7 +182,7 @@ const Configure = () => {
|
|||||||
|
|
||||||
let amount: string;
|
let amount: string;
|
||||||
let senderAddress: string;
|
let senderAddress: string;
|
||||||
let txHash: string | null = null;
|
let txHash: string;
|
||||||
if (createFormData.option === 'LRN' && !deployer?.minimumPayment) {
|
if (createFormData.option === 'LRN' && !deployer?.minimumPayment) {
|
||||||
toast({
|
toast({
|
||||||
id: 'no-payment-required',
|
id: 'no-payment-required',
|
||||||
@ -202,7 +196,7 @@ const Configure = () => {
|
|||||||
} else {
|
} else {
|
||||||
if (!selectedAccount) return;
|
if (!selectedAccount) return;
|
||||||
|
|
||||||
senderAddress = selectedAccount;
|
senderAddress = selectedAccount.split(':')[2];
|
||||||
|
|
||||||
if (createFormData.option === 'LRN') {
|
if (createFormData.option === 'LRN') {
|
||||||
amount = deployer?.minimumPayment!;
|
amount = deployer?.minimumPayment!;
|
||||||
@ -214,44 +208,27 @@ const Configure = () => {
|
|||||||
|
|
||||||
const amountToBePaid = amount.replace(/\D/g, '').toString();
|
const amountToBePaid = amount.replace(/\D/g, '').toString();
|
||||||
|
|
||||||
txHash = await cosmosSendTokensHandler(senderAddress, amountToBePaid);
|
const txHashResponse = await cosmosSendTokensHandler(
|
||||||
|
selectedAccount,
|
||||||
|
amountToBePaid,
|
||||||
|
);
|
||||||
|
|
||||||
if (!txHash) {
|
if (!txHashResponse) {
|
||||||
toast({
|
console.error('Tx not successful');
|
||||||
id: 'unsuccessful-tx',
|
return;
|
||||||
title: 'Transaction rejected',
|
|
||||||
variant: 'error',
|
|
||||||
onDismiss: dismiss,
|
|
||||||
});
|
|
||||||
setIsFrameVisible(false);
|
|
||||||
setIsPaymentLoading(false);
|
|
||||||
throw new Error('Transaction rejected');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate transaction hash
|
txHash = txHashResponse;
|
||||||
|
|
||||||
const isTxHashValid = await verifyTx(
|
const isTxHashValid = await verifyTx(
|
||||||
senderAddress,
|
senderAddress,
|
||||||
txHash,
|
txHash,
|
||||||
amountToBePaid,
|
amountToBePaid.toString(),
|
||||||
);
|
);
|
||||||
setIsPaymentLoading(false);
|
|
||||||
|
|
||||||
if (isTxHashValid) {
|
if (isTxHashValid === false) {
|
||||||
toast({
|
console.error('Invalid Tx hash', txHash);
|
||||||
id: 'payment-successful',
|
return;
|
||||||
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');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -271,7 +248,7 @@ const Configure = () => {
|
|||||||
createFormData,
|
createFormData,
|
||||||
environmentVariables,
|
environmentVariables,
|
||||||
senderAddress,
|
senderAddress,
|
||||||
txHash!,
|
txHash,
|
||||||
);
|
);
|
||||||
|
|
||||||
await client.getEnvironmentVariables(projectId);
|
await client.getEnvironmentVariables(projectId);
|
||||||
@ -293,14 +270,14 @@ const Configure = () => {
|
|||||||
`/${orgSlug}/projects/create/deploy?projectId=${projectId}`,
|
`/${orgSlug}/projects/create/deploy?projectId=${projectId}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
toast({
|
toast({
|
||||||
id: 'error-deploying-app',
|
id: 'error-deploying-app',
|
||||||
title: 'Error deploying app',
|
title: 'Error deploying app',
|
||||||
variant: 'error',
|
variant: 'error',
|
||||||
onDismiss: dismiss,
|
onDismiss: dismiss,
|
||||||
});
|
});
|
||||||
throw new Error(error);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[client, createProject, dismiss, toast],
|
[client, createProject, dismiss, toast],
|
||||||
@ -325,95 +302,72 @@ const Configure = () => {
|
|||||||
|
|
||||||
const cosmosSendTokensHandler = useCallback(
|
const cosmosSendTokensHandler = useCallback(
|
||||||
async (selectedAccount: string, amount: string) => {
|
async (selectedAccount: string, amount: string) => {
|
||||||
if (!selectedAccount) {
|
if (!signClient || !session || !selectedAccount) {
|
||||||
throw new Error('Account not selected');
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const senderAddress = selectedAccount;
|
const chainId = selectedAccount.split(':')[1];
|
||||||
|
const senderAddress = selectedAccount.split(':')[2];
|
||||||
const snowballAddress = await client.getAddress();
|
const snowballAddress = await client.getAddress();
|
||||||
let timeoutId;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setIsPaymentDone(false);
|
setIsPaymentDone(false);
|
||||||
setIsPaymentLoading(true);
|
setIsPaymentLoading(true);
|
||||||
|
|
||||||
await requestTx(senderAddress, snowballAddress, amount);
|
toast({
|
||||||
|
id: 'sending-payment-request',
|
||||||
const txHash = await new Promise<string | null>((resolve, reject) => {
|
title: 'Check your wallet and approve payment request',
|
||||||
const handleTxStatus = async (event: MessageEvent) => {
|
variant: 'loading',
|
||||||
if (event.origin !== VITE_WALLET_IFRAME_URL) return;
|
onDismiss: dismiss,
|
||||||
|
|
||||||
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);
|
|
||||||
});
|
});
|
||||||
return txHash;
|
|
||||||
} catch (error) {
|
const result: { signature: string } = await signClient.request({
|
||||||
console.error('Error in transaction:', error);
|
topic: session.topic,
|
||||||
throw new Error('Error in transaction');
|
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);
|
||||||
} finally {
|
} finally {
|
||||||
clearTimeout(timeoutId);
|
setIsPaymentLoading(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[client, dismiss, toast],
|
[session, signClient, 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(() => {
|
useEffect(() => {
|
||||||
fetchDeployers();
|
fetchDeployers();
|
||||||
}, []);
|
}, []);
|
||||||
@ -580,11 +534,10 @@ const Configure = () => {
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<AccountsDropdown
|
<Heading as="h4" className="md:text-lg font-medium mb-3">
|
||||||
accounts={accounts}
|
Connect to your wallet
|
||||||
onAccountChange={onAccountChange}
|
</Heading>
|
||||||
isDataReceived={isAccountsDataReceived}
|
<ConnectWallet onAccountChange={onAccountChange} />
|
||||||
/>
|
|
||||||
{accounts.length > 0 && (
|
{accounts.length > 0 && (
|
||||||
<div>
|
<div>
|
||||||
<Button
|
<Button
|
||||||
@ -616,12 +569,6 @@ const Configure = () => {
|
|||||||
)}
|
)}
|
||||||
</form>
|
</form>
|
||||||
</FormProvider>
|
</FormProvider>
|
||||||
|
|
||||||
<IFrameModal
|
|
||||||
setAccounts={setAccounts}
|
|
||||||
setIsDataReceived={setIsAccountsDataReceived}
|
|
||||||
isVisible={isFrameVisible}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -0,0 +1,47 @@
|
|||||||
|
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 (
|
||||||
|
<div className="p-4 bg-slate-100 dark:bg-overlay3 rounded-lg mb-6">
|
||||||
|
{accounts.length === 0 ? (
|
||||||
|
<div>
|
||||||
|
<Button type={'button'} onClick={handleConnect}>
|
||||||
|
Connect Wallet
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div>
|
||||||
|
<Select
|
||||||
|
label="Select Account"
|
||||||
|
defaultValue={accounts[0].address}
|
||||||
|
onChange={(value) => {
|
||||||
|
value && onAccountChange(value);
|
||||||
|
}}
|
||||||
|
className="dark:bg-overlay2 dark:text-foreground"
|
||||||
|
>
|
||||||
|
{accounts.map((account, index) => (
|
||||||
|
<Option key={index} value={account.address}>
|
||||||
|
{account.address.split(':').slice(1).join(':')}
|
||||||
|
</Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ConnectWallet;
|
@ -1,88 +0,0 @@
|
|||||||
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 (
|
|
||||||
<Modal open={isVisible} disableEscapeKeyDown keepMounted>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
position: 'absolute',
|
|
||||||
top: '50%',
|
|
||||||
left: '50%',
|
|
||||||
transform: 'translate(-50%, -50%)',
|
|
||||||
width: '90%',
|
|
||||||
maxWidth: '1200px',
|
|
||||||
height: '600px',
|
|
||||||
maxHeight: '80vh',
|
|
||||||
overflow: 'auto',
|
|
||||||
boxShadow: 24,
|
|
||||||
borderRadius: '8px',
|
|
||||||
outline: 'none',
|
|
||||||
bgcolor: 'background.paper',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<iframe
|
|
||||||
onLoad={getDataFromWallet}
|
|
||||||
id="walletIframe"
|
|
||||||
src={`${VITE_WALLET_IFRAME_URL}/wallet-embed`}
|
|
||||||
width="100%"
|
|
||||||
height="100%"
|
|
||||||
sandbox="allow-scripts allow-same-origin"
|
|
||||||
className="border rounded-md shadow-sm"
|
|
||||||
></iframe>
|
|
||||||
</Box>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default IFrameModal;
|
|
@ -1,8 +1,6 @@
|
|||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { SegmentedControls } from 'components/shared/SegmentedControls';
|
import { SegmentedControls } from 'components/shared/SegmentedControls';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useMediaQuery } from 'usehooks-ts';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
GithubIcon,
|
GithubIcon,
|
||||||
LockIcon,
|
LockIcon,
|
||||||
@ -10,7 +8,7 @@ import {
|
|||||||
TemplateIconType,
|
TemplateIconType,
|
||||||
} from 'components/shared/CustomIcon';
|
} from 'components/shared/CustomIcon';
|
||||||
import { relativeTimeISO } from 'utils/time';
|
import { relativeTimeISO } from 'utils/time';
|
||||||
import templates from 'assets/templates';
|
import { useMediaQuery } from 'usehooks-ts';
|
||||||
|
|
||||||
export const MockConnectGitCard = () => {
|
export const MockConnectGitCard = () => {
|
||||||
const [segmentedControlsValue, setSegmentedControlsValue] =
|
const [segmentedControlsValue, setSegmentedControlsValue] =
|
||||||
@ -48,6 +46,21 @@ export const MockConnectGitCard = () => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const TEMPLATE_CONTENT = [
|
||||||
|
{
|
||||||
|
name: 'Web app',
|
||||||
|
icon: 'web',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Progressive Web App (PWA)',
|
||||||
|
icon: 'pwa',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Next.js + React + TailwindCSS',
|
||||||
|
icon: 'next-app',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const renderContent = useMemo(() => {
|
const renderContent = useMemo(() => {
|
||||||
if (segmentedControlsValue === 'import') {
|
if (segmentedControlsValue === 'import') {
|
||||||
return (
|
return (
|
||||||
@ -65,7 +78,7 @@ export const MockConnectGitCard = () => {
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 relative z-0">
|
<div className="grid grid-cols-1 lg:grid-cols-2 relative z-0">
|
||||||
{templates.map((template, index) => (
|
{TEMPLATE_CONTENT.map((template, index) => (
|
||||||
<MockTemplateCard key={index} {...template} />
|
<MockTemplateCard key={index} {...template} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
@ -92,7 +92,7 @@ const DeploymentDetailsCard = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchDeploymentLogs = useCallback(async () => {
|
const fetchDeploymentLogs = async () => {
|
||||||
setDeploymentLogs('Loading logs...');
|
setDeploymentLogs('Loading logs...');
|
||||||
handleOpenDialog();
|
handleOpenDialog();
|
||||||
const statusUrl = `${deployment.deployer.deployerApiUrl}/${deployment.applicationDeploymentRequestId}`;
|
const statusUrl = `${deployment.deployer.deployerApiUrl}/${deployment.applicationDeploymentRequestId}`;
|
||||||
@ -108,7 +108,7 @@ const DeploymentDetailsCard = ({
|
|||||||
);
|
);
|
||||||
setDeploymentLogs(logsRes);
|
setDeploymentLogs(logsRes);
|
||||||
}
|
}
|
||||||
}, [deployment.deployer, deployment.applicationDeploymentRequestId]);
|
};
|
||||||
|
|
||||||
const renderDeploymentStatus = useCallback(
|
const renderDeploymentStatus = useCallback(
|
||||||
(className?: string) => {
|
(className?: string) => {
|
||||||
@ -127,7 +127,7 @@ const DeploymentDetailsCard = ({
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[deployment.status, deployment.commitHash, fetchDeploymentLogs],
|
[deployment.status, deployment.commitHash],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -17,7 +17,6 @@ const GitSelectionSection = ({
|
|||||||
<div className="grow">Github</div>
|
<div className="grow">Github</div>
|
||||||
<div>{'>'}</div>
|
<div>{'>'}</div>
|
||||||
</div>
|
</div>
|
||||||
{/*
|
|
||||||
<div
|
<div
|
||||||
className="flex gap-4 border-b-2 border-gray-200 cursor-pointer p-1"
|
className="flex gap-4 border-b-2 border-gray-200 cursor-pointer p-1"
|
||||||
onClick={() => {}}
|
onClick={() => {}}
|
||||||
@ -26,7 +25,6 @@ const GitSelectionSection = ({
|
|||||||
<div className="grow">Gitea</div>
|
<div className="grow">Gitea</div>
|
||||||
<div>{'>'}</div>
|
<div>{'>'}</div>
|
||||||
</div>
|
</div>
|
||||||
*/}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -4,7 +4,10 @@ import { cloneIcon } from 'utils/cloneIcon';
|
|||||||
import { PWAIcon } from './PWAIcon';
|
import { PWAIcon } from './PWAIcon';
|
||||||
import { WebAppIcon } from './WebAppIcon';
|
import { WebAppIcon } from './WebAppIcon';
|
||||||
|
|
||||||
const TEMPLATE_ICONS = ['pwa', 'web'] as const;
|
const TEMPLATE_ICONS = [
|
||||||
|
'pwa',
|
||||||
|
'web'
|
||||||
|
] as const;
|
||||||
export type TemplateIconType = (typeof TEMPLATE_ICONS)[number];
|
export type TemplateIconType = (typeof TEMPLATE_ICONS)[number];
|
||||||
|
|
||||||
export interface TemplateIconProps extends CustomIconProps {
|
export interface TemplateIconProps extends CustomIconProps {
|
||||||
|
@ -92,20 +92,10 @@ export const Sidebar = ({ mobileOpen }: SidebarProps) => {
|
|||||||
</a>
|
</a>
|
||||||
</Tabs.Trigger>
|
</Tabs.Trigger>
|
||||||
<Tabs.Trigger icon={<QuestionMarkRoundIcon />} value="">
|
<Tabs.Trigger icon={<QuestionMarkRoundIcon />} value="">
|
||||||
<a
|
<a className="cursor-pointer font-mono">DOCUMENTATION</a>
|
||||||
className="cursor-pointer font-mono"
|
|
||||||
href="https://store.laconic.com/pages/instruction-faq"
|
|
||||||
>
|
|
||||||
DOCUMENTATION
|
|
||||||
</a>
|
|
||||||
</Tabs.Trigger>
|
</Tabs.Trigger>
|
||||||
<Tabs.Trigger icon={<LifeBuoyIcon />} value="">
|
<Tabs.Trigger icon={<LifeBuoyIcon />} value="">
|
||||||
<a
|
<a className="cursor-pointer font-mono">SUPPORT</a>
|
||||||
className="cursor-pointer font-mono"
|
|
||||||
href="https://discord.com/invite/ukhbBemyxY"
|
|
||||||
>
|
|
||||||
SUPPORT
|
|
||||||
</a>
|
|
||||||
</Tabs.Trigger>
|
</Tabs.Trigger>
|
||||||
</Tabs.List>
|
</Tabs.List>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
210
packages/frontend/src/context/WalletConnectContext.tsx
Normal file
210
packages/frontend/src/context/WalletConnectContext.tsx
Normal file
@ -0,0 +1,210 @@
|
|||||||
|
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<void>;
|
||||||
|
onDisconnect: () => Promise<void>;
|
||||||
|
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<SignClient>();
|
||||||
|
const [session, setSession] = useState<SessionTypes.Struct>();
|
||||||
|
const [loadingSession, setLoadingSession] = useState(true);
|
||||||
|
const [accounts, setAccounts] = useState<{ address: string }[]>([]);
|
||||||
|
|
||||||
|
const isSignClientInitializing = useRef<boolean>(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 (
|
||||||
|
<ClientContext.Provider
|
||||||
|
value={{
|
||||||
|
signClient,
|
||||||
|
onConnect,
|
||||||
|
onDisconnect,
|
||||||
|
onSessionDelete,
|
||||||
|
loadingSession,
|
||||||
|
session,
|
||||||
|
accounts,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</ClientContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
@ -4,9 +4,10 @@ import assert from 'assert';
|
|||||||
import { GQLClient } from 'gql-client';
|
import { GQLClient } from 'gql-client';
|
||||||
|
|
||||||
import { ThemeProvider } from '@snowballtools/material-tailwind-react-fork';
|
import { ThemeProvider } from '@snowballtools/material-tailwind-react-fork';
|
||||||
|
|
||||||
|
import './index.css';
|
||||||
import '@fontsource/inter';
|
import '@fontsource/inter';
|
||||||
import '@fontsource-variable/jetbrains-mono';
|
import '@fontsource-variable/jetbrains-mono';
|
||||||
|
|
||||||
import App from './App';
|
import App from './App';
|
||||||
import reportWebVitals from './reportWebVitals';
|
import reportWebVitals from './reportWebVitals';
|
||||||
import { GQLClientProvider } from './context/GQLClientContext';
|
import { GQLClientProvider } from './context/GQLClientContext';
|
||||||
@ -15,7 +16,7 @@ import { Toaster } from 'components/shared/Toast';
|
|||||||
import { LogErrorBoundary } from 'utils/log-error';
|
import { LogErrorBoundary } from 'utils/log-error';
|
||||||
import { BASE_URL } from 'utils/constants';
|
import { BASE_URL } from 'utils/constants';
|
||||||
import Web3ModalProvider from './context/Web3Provider';
|
import Web3ModalProvider from './context/Web3Provider';
|
||||||
import './index.css';
|
import { WalletConnectClientProvider } from 'context/WalletConnectContext';
|
||||||
|
|
||||||
console.log(`v-0.0.9`);
|
console.log(`v-0.0.9`);
|
||||||
|
|
||||||
@ -31,14 +32,16 @@ const gqlClient = new GQLClient({ gqlEndpoint });
|
|||||||
root.render(
|
root.render(
|
||||||
<LogErrorBoundary>
|
<LogErrorBoundary>
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<ThemeProvider>
|
<WalletConnectClientProvider>
|
||||||
<Web3ModalProvider>
|
<ThemeProvider>
|
||||||
<GQLClientProvider client={gqlClient}>
|
<Web3ModalProvider>
|
||||||
<App />
|
<GQLClientProvider client={gqlClient}>
|
||||||
<Toaster />
|
<App />
|
||||||
</GQLClientProvider>
|
<Toaster />
|
||||||
</Web3ModalProvider>
|
</GQLClientProvider>
|
||||||
</ThemeProvider>
|
</Web3ModalProvider>
|
||||||
|
</ThemeProvider>
|
||||||
|
</WalletConnectClientProvider>
|
||||||
</React.StrictMode>
|
</React.StrictMode>
|
||||||
</LogErrorBoundary>,
|
</LogErrorBoundary>,
|
||||||
);
|
);
|
||||||
|
@ -60,9 +60,9 @@ const Id = () => {
|
|||||||
fetchProject(id);
|
fetchProject(id);
|
||||||
}, [id]);
|
}, [id]);
|
||||||
|
|
||||||
const onUpdate = useCallback(async () => {
|
const onUpdate = async () => {
|
||||||
await fetchProject(id);
|
await fetchProject(id);
|
||||||
}, [fetchProject, id]);
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full">
|
<div className="h-full">
|
||||||
@ -118,11 +118,9 @@ const Id = () => {
|
|||||||
<Tabs.Trigger value="deployments">
|
<Tabs.Trigger value="deployments">
|
||||||
<Link to="deployments">Deployments</Link>
|
<Link to="deployments">Deployments</Link>
|
||||||
</Tabs.Trigger>
|
</Tabs.Trigger>
|
||||||
{/*
|
|
||||||
<Tabs.Trigger value="integrations">
|
<Tabs.Trigger value="integrations">
|
||||||
<Link to="integrations">Integrations</Link>
|
<Link to="integrations">Integrations</Link>
|
||||||
</Tabs.Trigger>
|
</Tabs.Trigger>
|
||||||
*/}
|
|
||||||
<Tabs.Trigger value="settings">
|
<Tabs.Trigger value="settings">
|
||||||
<Link to="settings">Settings</Link>
|
<Link to="settings">Settings</Link>
|
||||||
</Tabs.Trigger>
|
</Tabs.Trigger>
|
||||||
|
@ -1,14 +1,12 @@
|
|||||||
import { ComponentPropsWithoutRef } from 'react';
|
import { ComponentPropsWithoutRef } from 'react';
|
||||||
import { Link, Outlet, useParams } from 'react-router-dom';
|
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 { Heading } from 'components/shared/Heading';
|
||||||
import { WavyBorder } from 'components/shared/WavyBorder';
|
import { WavyBorder } from 'components/shared/WavyBorder';
|
||||||
import { Button } from 'components/shared/Button';
|
import { Button } from 'components/shared/Button';
|
||||||
import { CrossIcon } from 'components/shared/CustomIcon';
|
import { CrossIcon } from 'components/shared/CustomIcon';
|
||||||
import { cn } from 'utils/classnames';
|
import { cn } from 'utils/classnames';
|
||||||
|
import * as Dialog from '@radix-ui/react-dialog';
|
||||||
|
|
||||||
export interface CreateProjectLayoutProps
|
export interface CreateProjectLayoutProps
|
||||||
extends ComponentPropsWithoutRef<'section'> {}
|
extends ComponentPropsWithoutRef<'section'> {}
|
||||||
@ -18,7 +16,6 @@ export const CreateProjectLayout = ({
|
|||||||
...props
|
...props
|
||||||
}: CreateProjectLayoutProps) => {
|
}: CreateProjectLayoutProps) => {
|
||||||
const { orgSlug } = useParams();
|
const { orgSlug } = useParams();
|
||||||
const isDesktopView = useMediaQuery('(min-width: 720px)'); // md:
|
|
||||||
|
|
||||||
const closeBtnLink = `/${orgSlug}`;
|
const closeBtnLink = `/${orgSlug}`;
|
||||||
|
|
||||||
@ -31,69 +28,72 @@ export const CreateProjectLayout = ({
|
|||||||
</Heading>
|
</Heading>
|
||||||
);
|
);
|
||||||
|
|
||||||
return isDesktopView ? (
|
return (
|
||||||
// Desktop
|
<>
|
||||||
<section
|
{/* Desktop */}
|
||||||
{...props}
|
<section
|
||||||
className={cn(
|
{...props}
|
||||||
'dark:bg-background h-full flex-col hidden md:flex',
|
className={cn(
|
||||||
className,
|
'dark:bg-background h-full flex-col hidden md:flex',
|
||||||
)}
|
className,
|
||||||
>
|
)}
|
||||||
<div className="sticky top-0">
|
>
|
||||||
<div className="flex px-6 py-4 dark:bg-overlay bg-base-bg items-center gap-4">
|
<div className="sticky top-0">
|
||||||
{heading}
|
<div className="flex px-6 py-4 dark:bg-overlay bg-base-bg items-center gap-4">
|
||||||
|
{heading}
|
||||||
|
|
||||||
{/* Cannot save btn as variable since responsive variant don't work with compoundVariant */}
|
{/* Cannot save btn as variable since responsive variant don't work with compoundVariant */}
|
||||||
<Link to={closeBtnLink}>
|
<Link to={closeBtnLink}>
|
||||||
<Button
|
<Button
|
||||||
iconOnly
|
iconOnly
|
||||||
variant="primary"
|
variant="primary"
|
||||||
leftIcon={<CrossIcon />}
|
leftIcon={<CrossIcon />}
|
||||||
aria-label="close"
|
aria-label="close"
|
||||||
/>
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
|
</div>
|
||||||
|
<WavyBorder />
|
||||||
</div>
|
</div>
|
||||||
<WavyBorder />
|
|
||||||
</div>
|
<section className="px-6 h-full flex-1 py-6 overflow-y-auto">
|
||||||
<section className="px-6 h-full flex-1 py-6 overflow-y-auto">
|
<Outlet />
|
||||||
<Outlet />
|
</section>
|
||||||
</section>
|
</section>
|
||||||
</section>
|
|
||||||
) : (
|
|
||||||
// Mobile
|
|
||||||
// Setting modal={false} so even if modal is active on desktop, it doesn't block clicks
|
|
||||||
<Dialog.Root modal={false} open={true}>
|
|
||||||
<Dialog.Portal>
|
|
||||||
{/* Not using <Dialog.Overlay> since modal={false} disables it and its content will not show */}
|
|
||||||
<div className="bg-base-canvas fixed inset-0 md:hidden overflow-y-auto p-1">
|
|
||||||
<Dialog.Content className="min-h-full overflow-hidden rounded-2xl bg-base-bg shadow-card focus:outline-none">
|
|
||||||
{/* Heading */}
|
|
||||||
<div className="flex px-6 py-4 h-20 items-center gap-4">
|
|
||||||
{heading}
|
|
||||||
<Dialog.Close asChild>
|
|
||||||
<Link to={closeBtnLink}>
|
|
||||||
<Button
|
|
||||||
iconOnly
|
|
||||||
variant="tertiary"
|
|
||||||
leftIcon={<CrossIcon />}
|
|
||||||
aria-label="close"
|
|
||||||
size="sm"
|
|
||||||
/>
|
|
||||||
</Link>
|
|
||||||
</Dialog.Close>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Border */}
|
{/* Mobile */}
|
||||||
<WavyBorder />
|
{/* Setting modal={false} so even if modal is active on desktop, it doesn't block clicks */}
|
||||||
|
<Dialog.Root modal={false} open={true}>
|
||||||
|
<Dialog.Portal>
|
||||||
|
{/* Not using <Dialog.Overlay> since modal={false} disables it and its content will not show */}
|
||||||
|
<div className="bg-base-canvas fixed inset-0 md:hidden overflow-y-auto p-1">
|
||||||
|
<Dialog.Content className="min-h-full overflow-hidden rounded-2xl bg-base-bg shadow-card focus:outline-none">
|
||||||
|
{/* Heading */}
|
||||||
|
<div className="flex px-6 py-4 h-20 items-center gap-4">
|
||||||
|
{heading}
|
||||||
|
<Dialog.Close asChild>
|
||||||
|
<Link to={closeBtnLink}>
|
||||||
|
<Button
|
||||||
|
iconOnly
|
||||||
|
variant="tertiary"
|
||||||
|
leftIcon={<CrossIcon />}
|
||||||
|
aria-label="close"
|
||||||
|
size="sm"
|
||||||
|
/>
|
||||||
|
</Link>
|
||||||
|
</Dialog.Close>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Page content */}
|
{/* Border */}
|
||||||
<div className="px-4 py-6">
|
<WavyBorder />
|
||||||
<Outlet />
|
|
||||||
</div>
|
{/* Page content */}
|
||||||
</Dialog.Content>
|
<div className="px-4 py-6">
|
||||||
</div>
|
<Outlet />
|
||||||
</Dialog.Portal>
|
</div>
|
||||||
</Dialog.Root>
|
</Dialog.Content>
|
||||||
|
</div>
|
||||||
|
</Dialog.Portal>
|
||||||
|
</Dialog.Root>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -24,7 +24,6 @@ import { Domain, DomainStatus } from 'gql-client';
|
|||||||
import { AuctionCard } from 'components/projects/project/overview/Activity/AuctionCard';
|
import { AuctionCard } from 'components/projects/project/overview/Activity/AuctionCard';
|
||||||
|
|
||||||
const COMMITS_PER_PAGE = 4;
|
const COMMITS_PER_PAGE = 4;
|
||||||
const PROJECT_UPDATE_WAIT_MS = 5000;
|
|
||||||
|
|
||||||
const OverviewTabPanel = () => {
|
const OverviewTabPanel = () => {
|
||||||
const { octokit } = useOctokit();
|
const { octokit } = useOctokit();
|
||||||
@ -34,7 +33,8 @@ const OverviewTabPanel = () => {
|
|||||||
const [liveDomain, setLiveDomain] = useState<Domain>();
|
const [liveDomain, setLiveDomain] = useState<Domain>();
|
||||||
|
|
||||||
const client = useGQLClient();
|
const client = useGQLClient();
|
||||||
const { project, onUpdate } = useOutletContext<OutletContextType>();
|
|
||||||
|
const { project } = useOutletContext<OutletContextType>();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setFetchingActivities(true);
|
setFetchingActivities(true);
|
||||||
@ -96,16 +96,7 @@ const OverviewTabPanel = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
fetchRepoActivity();
|
fetchRepoActivity();
|
||||||
}, [project.repository]);
|
}, [octokit, project]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
onUpdate();
|
|
||||||
const timerId = setInterval(() => {
|
|
||||||
onUpdate();
|
|
||||||
}, PROJECT_UPDATE_WAIT_MS);
|
|
||||||
|
|
||||||
return () => clearInterval(timerId);
|
|
||||||
}, [onUpdate]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchLiveProdDomain = async () => {
|
const fetchLiveProdDomain = async () => {
|
||||||
|
@ -10,7 +10,7 @@ import {
|
|||||||
} from 'components/shared/Tabs';
|
} from 'components/shared/Tabs';
|
||||||
import {
|
import {
|
||||||
BranchStrokeIcon,
|
BranchStrokeIcon,
|
||||||
//CollaboratorsIcon,
|
CollaboratorsIcon,
|
||||||
GearIcon,
|
GearIcon,
|
||||||
GlobeIcon,
|
GlobeIcon,
|
||||||
SwitchIcon,
|
SwitchIcon,
|
||||||
@ -37,11 +37,11 @@ const tabsData = [
|
|||||||
icon: <SwitchIcon />,
|
icon: <SwitchIcon />,
|
||||||
value: 'environment-variables',
|
value: 'environment-variables',
|
||||||
},
|
},
|
||||||
// {
|
{
|
||||||
// label: 'Collaborators',
|
label: 'Collaborators',
|
||||||
// icon: <CollaboratorsIcon />,
|
icon: <CollaboratorsIcon />,
|
||||||
// value: 'collaborators',
|
value: 'collaborators',
|
||||||
// },
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const SettingsTabPanel = () => {
|
const SettingsTabPanel = () => {
|
||||||
|
@ -73,17 +73,17 @@ const EnvironmentVariablesForm = () => {
|
|||||||
<div className="flex gap-2 p-2">
|
<div className="flex gap-2 p-2">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
label="Production"
|
label="Production"
|
||||||
labelProps={{ className: 'text-gray-900 dark:text-white' }}
|
labelProps={{ className: "text-gray-900 dark:text-white" }}
|
||||||
{...register('environment.production')}
|
{...register('environment.production')}
|
||||||
/>
|
/>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
label="Preview"
|
label="Preview"
|
||||||
labelProps={{ className: 'text-gray-900 dark:text-white' }}
|
labelProps={{ className: "text-gray-900 dark:text-white" }}
|
||||||
{...register('environment.preview')}
|
{...register('environment.preview')}
|
||||||
/>
|
/>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
label="Development"
|
label="Development"
|
||||||
labelProps={{ className: 'text-gray-900 dark:text-white' }}
|
labelProps={{ className: "text-gray-900 dark:text-white" }}
|
||||||
{...register('environment.development')}
|
{...register('environment.development')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -18,6 +18,7 @@ const GeneralTabPanel = () => {
|
|||||||
const client = useGQLClient();
|
const client = useGQLClient();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const { project, onUpdate } = useOutletContext<OutletContextType>();
|
const { project, onUpdate } = useOutletContext<OutletContextType>();
|
||||||
|
console.log(project);
|
||||||
|
|
||||||
const [transferOrganizations, setTransferOrganizations] = useState<
|
const [transferOrganizations, setTransferOrganizations] = useState<
|
||||||
SelectOption[]
|
SelectOption[]
|
||||||
|
@ -63,7 +63,10 @@ const Config = () => {
|
|||||||
return (
|
return (
|
||||||
<ProjectSettingContainer headingText="Setup domain name">
|
<ProjectSettingContainer headingText="Setup domain name">
|
||||||
<p className="text-blue-gray-500">
|
<p className="text-blue-gray-500">
|
||||||
Add the following records to your domain.
|
Add the following records to your domain.
|
||||||
|
<a href="https://www.namecheap.com/" target="_blank" rel="noreferrer">
|
||||||
|
<span className="underline">Go to NameCheap</span>
|
||||||
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<Table>
|
<Table>
|
||||||
@ -79,13 +82,13 @@ const Config = () => {
|
|||||||
<Table.Row>
|
<Table.Row>
|
||||||
<Table.RowHeaderCell>A</Table.RowHeaderCell>
|
<Table.RowHeaderCell>A</Table.RowHeaderCell>
|
||||||
<Table.Cell>@</Table.Cell>
|
<Table.Cell>@</Table.Cell>
|
||||||
<Table.Cell>IP.OF.THE.SP</Table.Cell>
|
<Table.Cell>56.49.19.21</Table.Cell>
|
||||||
</Table.Row>
|
</Table.Row>
|
||||||
|
|
||||||
<Table.Row>
|
<Table.Row>
|
||||||
<Table.RowHeaderCell>CNAME</Table.RowHeaderCell>
|
<Table.RowHeaderCell>CNAME</Table.RowHeaderCell>
|
||||||
<Table.Cell>subdomain</Table.Cell>
|
<Table.Cell>www</Table.Cell>
|
||||||
<Table.Cell>domain.of.the.sp</Table.Cell>
|
<Table.Cell>cname.snowballtools.xyz</Table.Cell>
|
||||||
</Table.Row>
|
</Table.Row>
|
||||||
</Table.Body>
|
</Table.Body>
|
||||||
</Table>
|
</Table>
|
||||||
|
@ -49,6 +49,9 @@ const AddDomain = () => {
|
|||||||
<div className=" w-2/3 mx-auto">
|
<div className=" w-2/3 mx-auto">
|
||||||
<div className="bg-blue-gray-50 dark:bg-overlay rounded-lg mt-6 mb-10">
|
<div className="bg-blue-gray-50 dark:bg-overlay rounded-lg mt-6 mb-10">
|
||||||
<div className="flex justify-start gap-3 p-5">
|
<div className="flex justify-start gap-3 p-5">
|
||||||
|
<i className="bg-gray-100 dark:bg-overlay dark:text-foreground w-12 h-12 rounded-lg">
|
||||||
|
^
|
||||||
|
</i>
|
||||||
<Typography
|
<Typography
|
||||||
className="my-auto w-1/3 dark:text-foreground"
|
className="my-auto w-1/3 dark:text-foreground"
|
||||||
variant="h5"
|
variant="h5"
|
||||||
|
@ -12,4 +12,3 @@ 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_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_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_LACONICD_CHAIN_ID = import.meta.env.VITE_LACONICD_CHAIN_ID;
|
||||||
export const VITE_WALLET_IFRAME_URL = import.meta.env.VITE_WALLET_IFRAME_URL;
|
|
||||||
|
Loading…
Reference in New Issue
Block a user