Skip to content

Commit

Permalink
Merge branch 'master' into 1118-enhancement
Browse files Browse the repository at this point in the history
  • Loading branch information
jeesunikim authored Nov 12, 2024
2 parents 893ba55 + d7f5b80 commit 069cea1
Show file tree
Hide file tree
Showing 10 changed files with 260 additions and 94 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
node-version: 22
- name: Install dependencies
run: npm install -g yarn && yarn
- name: Install Playwright Browsers
Expand Down
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v20
v22
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM node:20
FROM node:22

ENV NEXT_TELEMETRY_DISABLED 1
ENV PORT 80
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"author": "Stellar Development Foundation <[email protected]>",
"license": "Apache-2.0",
"engines": {
"node": ">=20.0.0"
"node": ">=22.0.0"
},
"scripts": {
"dev": "export NEXT_PUBLIC_COMMIT_HASH=$(git rev-parse --short HEAD) && next dev",
Expand Down
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 @@ -10,6 +10,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 { PrettyJsonTextarea } from "@/components/PrettyJsonTextarea";
import { SavedItemTimestampAndDelete } from "@/components/SavedItemTimestampAndDelete";
import { CopyJsonPayloadButton } from "@/components/CopyJsonPayloadButton";

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
Loading

0 comments on commit 069cea1

Please sign in to comment.