Skip to content

Commit

Permalink
Saved Keypairs: show balance or fund account button (#1147)
Browse files Browse the repository at this point in the history
  • Loading branch information
quietbits authored Nov 12, 2024
1 parent ca5870c commit d7f5b80
Show file tree
Hide file tree
Showing 6 changed files with 256 additions and 90 deletions.
282 changes: 201 additions & 81 deletions src/app/(sidebar)/account/saved/page.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,33 @@
"use client";

import { useCallback, useEffect, useState } from "react";
import { Alert, Text, Card, Input, Icon, Button } from "@stellar/design-system";
import {
Alert,
Text,
Card,
Input,
Icon,
Button,
Loader,
Badge,
} from "@stellar/design-system";

import { Box } from "@/components/layout/Box";
import { InputSideElement } from "@/components/InputSideElement";
import { SaveKeypairModal } from "@/components/SaveKeypairModal";
import { SavedItemTimestampAndDelete } from "@/components/SavedItemTimestampAndDelete";

import { useStore } from "@/store/useStore";
import { localStorageSavedKeypairs } from "@/helpers/localStorageSavedKeypairs";
import { NetworkOptions } from "@/constants/settings";
import { arrayItem } from "@/helpers/arrayItem";
import { getNetworkHeaders } from "@/helpers/getNetworkHeaders";
import { getNetworkById } from "@/helpers/getNetworkById";

import { useStore } from "@/store/useStore";
import { useIsTestingNetwork } from "@/hooks/useIsTestingNetwork";
import { useFriendBot } from "@/query/useFriendBot";
import { useAccountInfo } from "@/query/useAccountInfo";

import { NetworkType, SavedKeypair } from "@/types/types";
import { arrayItem } from "@/helpers/arrayItem";

export default function SavedKeypairs() {
const { network, selectNetwork, updateIsDynamicNetworkSelect } = useStore();
Expand All @@ -37,79 +50,8 @@ export default function SavedKeypairs() {
updateSavedKeypairs();
}, [updateSavedKeypairs]);

const SavedKeypair = ({ keypair }: { keypair: SavedKeypair }) => {
return (
<Box gap="sm" addlClassName="PageBody__content">
<Input
id={`saved-kp-${keypair.timestamp}-name`}
fieldSize="md"
value={keypair.name}
readOnly
leftElement="Name"
rightElement={
<InputSideElement
variant="button"
placement="right"
onClick={() => {
setCurrentKeypairTimestamp(keypair.timestamp);
}}
icon={<Icon.Edit05 />}
/>
}
/>

<Input
id={`saved-kp-${keypair.timestamp}-pk`}
fieldSize="md"
value={keypair.publicKey}
readOnly
leftElement="Public"
copyButton={{ position: "right" }}
/>

<Input
id={`saved-kp-${keypair.timestamp}-sk`}
fieldSize="md"
value={keypair.secretKey}
readOnly
leftElement="Secret"
copyButton={{ position: "right" }}
isPassword
/>

<Box
gap="lg"
direction="row"
align="center"
justify="end"
addlClassName="Endpoints__urlBar__footer"
>
<SavedItemTimestampAndDelete
timestamp={keypair.timestamp}
onDelete={() => {
const savedKeypairs = localStorageSavedKeypairs.get();
const indexToUpdate = savedKeypairs.findIndex(
(kp) => kp.timestamp === keypair.timestamp,
);

if (indexToUpdate >= 0) {
const updatedList = arrayItem.delete(
savedKeypairs,
indexToUpdate,
);

localStorageSavedKeypairs.set(updatedList);
updateSavedKeypairs();
}
}}
/>
</Box>
</Box>
);
};

const getNetworkById = (networkId: NetworkType) => {
const newNetwork = NetworkOptions.find((n) => n.id === networkId);
const getAndSetNetwork = (networkId: NetworkType) => {
const newNetwork = getNetworkById(networkId);

if (newNetwork) {
updateIsDynamicNetworkSelect(true);
Expand All @@ -132,7 +74,7 @@ export default function SavedKeypairs() {
const newNetworkId =
network.id === "testnet" ? "futurenet" : "testnet";

getNetworkById(newNetworkId);
getAndSetNetwork(newNetworkId);
}}
>
{`You must switch your network to ${otherNetworkLabel} in order to see those saved
Expand All @@ -153,7 +95,27 @@ export default function SavedKeypairs() {
{savedKeypairs.length === 0
? `There are no saved keypairs on ${network.label} network.`
: savedKeypairs.map((kp) => (
<SavedKeypair key={`saved-kp-${kp.timestamp}`} keypair={kp} />
<SavedKeypairItem
key={`saved-kp-${kp.timestamp}`}
keypair={kp}
setCurrentKeypairTimestamp={setCurrentKeypairTimestamp}
onDelete={(keypair) => {
const savedKeypairs = localStorageSavedKeypairs.get();
const indexToUpdate = savedKeypairs.findIndex(
(kp) => kp.timestamp === keypair.timestamp,
);

if (indexToUpdate >= 0) {
const updatedList = arrayItem.delete(
savedKeypairs,
indexToUpdate,
);

localStorageSavedKeypairs.set(updatedList);
updateSavedKeypairs();
}
}}
/>
))}
</>
</Box>
Expand All @@ -173,7 +135,7 @@ export default function SavedKeypairs() {
variant="tertiary"
size="sm"
onClick={() => {
getNetworkById("futurenet");
getAndSetNetwork("futurenet");
}}
>
Switch to Futurenet
Expand All @@ -183,7 +145,7 @@ export default function SavedKeypairs() {
variant="tertiary"
size="sm"
onClick={() => {
getNetworkById("testnet");
getAndSetNetwork("testnet");
}}
>
Switch to Testnet
Expand Down Expand Up @@ -236,3 +198,161 @@ export default function SavedKeypairs() {
</Box>
);
}

const SavedKeypairItem = ({
keypair,
setCurrentKeypairTimestamp,
onDelete,
}: {
keypair: SavedKeypair;
setCurrentKeypairTimestamp: (timestamp: number) => void;
onDelete: (keypair: SavedKeypair) => void;
}) => {
const network = getNetworkById(keypair.network.id);
const publicKey = keypair.publicKey;
const horizonUrl = network?.horizonUrl || "";
const headers = network ? getNetworkHeaders(network, "horizon") : {};

const {
error: friendbotError,
isFetching: isFriendbotFetching,
isLoading: isFriendbotLoading,
isSuccess: isFriendbotSuccess,
refetch: fundWithFriendbot,
} = useFriendBot({
network: network!,
publicKey: publicKey,
key: { type: "saved" },
headers,
});

const {
isFetching: isAccountFetching,
isLoading: isAccountLoading,
error: accountError,
data: accountInfo,
refetch: fetchAccountInfo,
} = useAccountInfo({
publicKey,
horizonUrl,
headers,
});

useEffect(() => {
if (publicKey && horizonUrl) {
fetchAccountInfo();
}
}, [publicKey, horizonUrl, fetchAccountInfo]);

useEffect(() => {
if (isFriendbotSuccess) {
fetchAccountInfo();
}
}, [fetchAccountInfo, isFriendbotSuccess]);

const renderAccountData = () => {
if (
isAccountFetching ||
isAccountLoading ||
isFriendbotFetching ||
isFriendbotLoading
) {
return <Loader />;
}

if (accountError || friendbotError) {
return (
<div className="FieldNote FieldNote--error FieldNote--md">
{accountError?.message || friendbotError?.message}
</div>
);
}

if (accountInfo && !accountInfo.isFunded) {
return (
<Button
variant="tertiary"
size="md"
onClick={() => fundWithFriendbot()}
>
Fund with Friendbot
</Button>
);
}

if (accountInfo?.isFunded) {
const xlmAsset = accountInfo.details.balances.find(
(b: any) => b.asset_type === "native",
);

if (xlmAsset) {
return (
<Badge
variant="secondary"
size="md"
>{`Balance: ${xlmAsset.balance} XLM`}</Badge>
);
}
}

return null;
};

return (
<Box gap="sm" addlClassName="PageBody__content">
<Input
id={`saved-kp-${keypair.timestamp}-name`}
fieldSize="md"
value={keypair.name}
readOnly
leftElement="Name"
rightElement={
<InputSideElement
variant="button"
placement="right"
onClick={() => {
setCurrentKeypairTimestamp(keypair.timestamp);
}}
icon={<Icon.Edit05 />}
/>
}
/>

<Input
id={`saved-kp-${keypair.timestamp}-pk`}
fieldSize="md"
value={keypair.publicKey}
readOnly
leftElement="Public"
copyButton={{ position: "right" }}
/>

<Input
id={`saved-kp-${keypair.timestamp}-sk`}
fieldSize="md"
value={keypair.secretKey}
readOnly
leftElement="Secret"
copyButton={{ position: "right" }}
isPassword
/>

<Box
gap="lg"
direction="row"
align="center"
justify="space-between"
addlClassName="Endpoints__urlBar__footer"
>
<Box gap="md">
<>{renderAccountData()}</>
</Box>

<SavedItemTimestampAndDelete
timestamp={keypair.timestamp}
onDelete={() => onDelete(keypair)}
/>
</Box>
</Box>
);
};
7 changes: 4 additions & 3 deletions src/app/(sidebar)/endpoints/components/SavedEndpointsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import {
Modal,
Text,
} from "@stellar/design-system";
import { stringify } from "lossless-json";

import { TabView } from "@/components/TabView";
import { Box } from "@/components/layout/Box";
import { InputSideElement } from "@/components/InputSideElement";
Expand All @@ -19,13 +21,12 @@ import { ShareUrlButton } from "@/components/ShareUrlButton";
import { PrettyJsonTextarea } from "@/components/PrettyJsonTextarea";
import { SavedItemTimestampAndDelete } from "@/components/SavedItemTimestampAndDelete";

import { NetworkOptions } from "@/constants/settings";
import { Routes } from "@/constants/routes";
import { localStorageSavedEndpointsHorizon } from "@/helpers/localStorageSavedEndpointsHorizon";
import { localStorageSavedRpcMethods } from "@/helpers/localStorageSavedRpcMethods";
import { arrayItem } from "@/helpers/arrayItem";
import { formatTimestamp } from "@/helpers/formatTimestamp";
import { stringify } from "lossless-json";
import { getNetworkById } from "@/helpers/getNetworkById";
import { useStore } from "@/store/useStore";
import {
Network,
Expand Down Expand Up @@ -73,7 +74,7 @@ export const SavedEndpointsPage = () => {
const getNetworkConfig = (
network: LocalStorageSavedNetwork,
): Network | undefined => {
const defaults = NetworkOptions.find((n) => n.id === network.id);
const defaults = getNetworkById(network.id);

switch (network.id) {
case "testnet":
Expand Down
7 changes: 2 additions & 5 deletions src/components/NetworkSelector/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ import { localStorageSavedNetwork } from "@/helpers/localStorageSavedNetwork";
import { delayedAction } from "@/helpers/delayedAction";
import { isEmptyObject } from "@/helpers/isEmptyObject";
import { sanitizeObject } from "@/helpers/sanitizeObject";
import { getNetworkById } from "@/helpers/getNetworkById";

import { AnyObject, EmptyObj, Network, NetworkType } from "@/types/types";
import { AnyObject, EmptyObj, Network } from "@/types/types";

import "./styles.scss";

Expand Down Expand Up @@ -216,10 +217,6 @@ export const NetworkSelector = () => {
});
};

const getNetworkById = (networkId: NetworkType) => {
return NetworkOptions.find((op) => op.id === networkId);
};

const getButtonLabel = () => {
if (activeNetwork.id === "custom") {
return "Switch to Custom Network";
Expand Down
6 changes: 6 additions & 0 deletions src/helpers/getNetworkById.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { NetworkOptions } from "@/constants/settings";
import { NetworkType } from "@/types/types";

export const getNetworkById = (networkId: NetworkType) => {
return NetworkOptions.find((op) => op.id === networkId);
};
Loading

0 comments on commit d7f5b80

Please sign in to comment.