diff --git a/package.json b/package.json index 9f562364..6d35a7be 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "git-info": "rm -rf src/generated/ && mkdir src/generated/ && echo export default \"{\\\"commitHash\\\": \\\"$(git rev-parse --short HEAD)\\\", \\\"version\\\": \\\"$(git describe --tags --always)\\\"};\" > src/generated/gitInfo.ts" }, "dependencies": { - "@stellar/design-system": "^2.0.0-beta.9", + "@stellar/design-system": "^2.0.0-beta.10", "@stellar/stellar-sdk": "^11.3.0", "@tanstack/react-query": "^5.28.8", "@tanstack/react-query-devtools": "^5.28.8", diff --git a/src/app/(sidebar)/account/create/page.tsx b/src/app/(sidebar)/account/create/page.tsx index f2686ada..802a131c 100644 --- a/src/app/(sidebar)/account/create/page.tsx +++ b/src/app/(sidebar)/account/create/page.tsx @@ -1,29 +1,78 @@ "use client"; -import { useState } from "react"; -import { useRouter } from "next/navigation"; +import { useCallback, useEffect, useState } from "react"; import { Card, Text, Button } from "@stellar/design-system"; import { Keypair } from "@stellar/stellar-sdk"; -import { useIsTestingNetwork } from "@/hooks/useIsTestingNetwork"; -import { Routes } from "@/constants/routes"; import { useStore } from "@/store/useStore"; +import { useFriendBot } from "@/query/useFriendBot"; +import { useQueryClient } from "@tanstack/react-query"; + +import { useIsTestingNetwork } from "@/hooks/useIsTestingNetwork"; + import { GenerateKeypair } from "@/components/GenerateKeypair"; import { ExpandBox } from "@/components/ExpandBox"; +import { SuccessMsg } from "@/components/FriendBot/SuccessMsg"; +import { ErrorMsg } from "@/components/FriendBot/ErrorMsg"; import "../styles.scss"; export default function CreateAccount() { - const { account } = useStore(); - const router = useRouter(); + const { account, network } = useStore(); const [secretKey, setSecretKey] = useState(""); + const [showAlert, setShowAlert] = useState(false); + const queryClient = useQueryClient(); const IS_TESTING_NETWORK = useIsTestingNetwork(); + const IS_CUSTOM_NETWORK_WITH_HORIZON = + network.id === "custom" && network.horizonUrl; + + const resetQuery = useCallback( + () => + queryClient.resetQueries({ + queryKey: ["friendBot"], + }), + [queryClient], + ); + + const resetStates = useCallback(() => { + account.reset(); + resetQuery(); + }, [resetQuery]); + + const { error, isError, isFetching, isLoading, isSuccess, refetch } = + useFriendBot({ + network, + publicKey: account.publicKey!, + }); + + useEffect(() => { + if (isError || isSuccess) { + setShowAlert(true); + } + }, [isError, isSuccess]); + + useEffect(() => { + if ( + account.registeredNetwork?.id && + account.registeredNetwork.id !== network.id + ) { + resetStates(); + setShowAlert(false); + } + }, [account.registeredNetwork, network.id]); const generateKeypair = () => { + resetStates(); + const keypair = Keypair.random(); - account.updatePublicKey(keypair.publicKey()); + if (IS_TESTING_NETWORK) { + account.updateKeypair(keypair.publicKey(), keypair.secret()); + } else { + account.updateKeypair(keypair.publicKey()); + } + setSecretKey(keypair.secret()); }; @@ -43,30 +92,60 @@ export default function CreateAccount() {
- - {IS_TESTING_NETWORK ? ( + + {IS_TESTING_NETWORK || IS_CUSTOM_NETWORK_WITH_HORIZON ? ( ) : null}
- -
- -
-
+ {Boolean(account.publicKey) && ( + +
+ +
+
+ )} + + { + setShowAlert(false); + }} + /> + + { + setShowAlert(false); + }} + /> ); } diff --git a/src/app/(sidebar)/account/fund/page.tsx b/src/app/(sidebar)/account/fund/page.tsx index e2c38715..6e98b26f 100644 --- a/src/app/(sidebar)/account/fund/page.tsx +++ b/src/app/(sidebar)/account/fund/page.tsx @@ -1,17 +1,19 @@ "use client"; import { useEffect, useState } from "react"; -import { Alert, Card, Input, Text, Button } from "@stellar/design-system"; +import { Card, Input, Text, Button } from "@stellar/design-system"; import Link from "next/link"; import { Routes } from "@/constants/routes"; -import { shortenStellarAddress } from "@/helpers/shortenStellarAddress"; import { useIsTestingNetwork } from "@/hooks/useIsTestingNetwork"; import { useFriendBot } from "@/query/useFriendBot"; import { useStore } from "@/store/useStore"; import { validate } from "@/validate"; +import { SuccessMsg } from "@/components/FriendBot/SuccessMsg"; +import { ErrorMsg } from "@/components/FriendBot/ErrorMsg"; + import "../styles.scss"; export default function FundAccount() { @@ -23,11 +25,28 @@ export default function FundAccount() { const IS_TESTING_NETWORK = useIsTestingNetwork(); - const { error, isError, isLoading, isSuccess, refetch, isFetchedAfterMount } = - useFriendBot({ - network: network.id, - publicKey: generatedPublicKey, - }); + const { + error, + isError, + isFetching, + isLoading, + isSuccess, + refetch, + isFetchedAfterMount, + } = useFriendBot({ + network, + publicKey: generatedPublicKey, + }); + + useEffect(() => { + if ( + account.registeredNetwork?.id && + account.registeredNetwork.id !== network.id + ) { + account.reset(); + setShowAlert(false); + } + }, [account.registeredNetwork, network.id]); useEffect(() => { if (isError || isSuccess) { @@ -60,13 +79,12 @@ export default function FundAccount() { { setGeneratedPublicKey(e.target.value); - const error = validate.publicKey(e.target.value); setInlineErrorMessage(error || ""); }} @@ -78,8 +96,8 @@ export default function FundAccount() { + + + + + + + The transaction signer lets you add signatures to a Stellar + transaction. Signatures are used in the network to prove that the + account is authorized to perform the operations in the transaction. + + + For simple transactions, you only need one signature from the + correct account. Some advanced transactions may require more than + one signature if there are multiple source accounts or signing keys. + + + + ); + }; + + const renderOverviewView = () => { + if (!tx) { + return null; + } + + const REQUIRED_FIELDS = [ + { + label: "Signing for", + value: network.passphrase, + }, + { + label: "Transaction Envelope XDR", + value: txEnv, + }, + { + label: "Transaction Hash", + value: tx.hash().toString("hex"), + }, + ]; + + let mergedFields; + + if (tx instanceof FeeBumpTransaction) { + mergedFields = [...REQUIRED_FIELDS, ...FEE_BUMP_TX_FIELDS(tx)]; + } else { + mergedFields = [...REQUIRED_FIELDS, ...TX_FIELDS(tx)]; + } + + return ( + <> + +
+ {mergedFields.map((field) => { + const className = + field.value.toString().length >= MIN_LENGTH_FOR_FULL_WIDTH_FIELD + ? "full-width" + : "half-width"; + + if (field.label.includes("XDR")) { + return ( +
+ +
+ ); + } else { + return ( +
+ +
+ ); + } + })} +
+
+ + ); + }; + + return ( +
+
+ + {isTxImported ? "Transaction Overview" : "Sign Transaction"} + +
+ {isTxValid && tx ? renderOverviewView() : rendeImportView()} +
+ ); } diff --git a/src/components/FormElements/PubKeyPicker.tsx b/src/components/FormElements/PubKeyPicker.tsx index becca362..e76c2133 100644 --- a/src/components/FormElements/PubKeyPicker.tsx +++ b/src/components/FormElements/PubKeyPicker.tsx @@ -21,6 +21,7 @@ export const PubKeyPicker = ({ placeholder = "Ex: GCEXAMPLE5HWNK4AYSTEQ4UWDKHTCKADVS2AHF3UI2ZMO3DPUSM6Q4UG", value, error, + readOnly, onChange, ...props }: PubKeyPickerProps) => ( @@ -32,6 +33,7 @@ export const PubKeyPicker = ({ placeholder={placeholder} value={value} error={error} + readOnly={readOnly} onChange={onChange} {...props} /> diff --git a/src/components/FormElements/TextPicker.tsx b/src/components/FormElements/TextPicker.tsx index 2adf54f0..7e90cba9 100644 --- a/src/components/FormElements/TextPicker.tsx +++ b/src/components/FormElements/TextPicker.tsx @@ -8,8 +8,9 @@ interface TextPickerProps extends Omit { label: string; value: string; placeholder?: string; - error: string | undefined; - onChange: (e: React.ChangeEvent) => void; + error?: string | undefined; + readOnly?: boolean; + onChange?: (e: React.ChangeEvent) => void; } export const TextPicker = ({ @@ -20,18 +21,18 @@ export const TextPicker = ({ value, error, onChange, + readOnly, ...props -}: TextPickerProps) => { - return ( - - ); -}; +}: TextPickerProps) => ( + +); diff --git a/src/components/FormElements/XdrPicker.tsx b/src/components/FormElements/XdrPicker.tsx index a75ee44d..54196830 100644 --- a/src/components/FormElements/XdrPicker.tsx +++ b/src/components/FormElements/XdrPicker.tsx @@ -5,11 +5,13 @@ interface XdrPickerProps extends Omit { id: string; fieldSize?: "sm" | "md" | "lg"; labelSuffix?: string | React.ReactNode; - label: string; + label: string | React.ReactNode; value: string; placeholder?: string; - error: string | undefined; - onChange: (e: React.ChangeEvent) => void; + error?: string | undefined; + success?: string | React.ReactNode; + onChange?: (e: React.ChangeEvent) => void; + readOnly?: boolean; } export const XdrPicker = ({ @@ -19,21 +21,23 @@ export const XdrPicker = ({ label, value, error, + success, onChange, + readOnly, ...props -}: XdrPickerProps) => { - return ( -