Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Creat and Fund Account Page #788

Merged
merged 10 commits into from
Mar 20, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v18
v18
2 changes: 1 addition & 1 deletion src/app/(sidebar)/account/create/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export default function CreateAccount() {
</Button>
</div>
</div>
<ExpandBox isExpanded={Boolean(account.publicKey && secretKey)}>
<ExpandBox isExpanded={Boolean(account.publicKey)}>
<GenerateKeypair
publicKey={account.publicKey}
secretKey={secretKey}
Expand Down
120 changes: 119 additions & 1 deletion src/app/(sidebar)/account/fund/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,123 @@
"use client";

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

import { shortenStellarAddress } from "@/helpers/shortenStellarAddress";
import { validatePublicKey } from "@/helpers/validatePublicKey";
import { useFriendBot } from "@/query/useFriendBot";
import { useStore } from "@/store/useStore";

import "../styles.scss";

export default function FundAccount() {
return <div>Fund Account</div>;
const { account, network } = useStore();

const [showAlert, setShowAlert] = useState<boolean>(false);
const [generatedPublicKey, setGeneratedPublicKey] = useState<string>("");
const [inlineErrorMessage, setInlineErrorMessage] = useState<string>("");

const { error, isError, isLoading, isSuccess, refetch, isFetchedAfterMount } =
Copy link
Contributor Author

@jeesunikim jeesunikim Mar 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isFetchAfterMount is needed to disable the cache

useFriendBot({
network: network.id,
publicKey: generatedPublicKey,
});

useEffect(() => {
if (isError || isSuccess) {
setShowAlert(true);
}
}, [isError, isSuccess]);

return (
<div className="Account">
jeesunikim marked this conversation as resolved.
Show resolved Hide resolved
<Card>
<div className="Account__card">
<div className="CardText">
<Text size="lg" as="h1" weight="medium">
Friendbot: fund a {network.id} network account
</Text>

<Text size="sm" as="p">
The friendbot is a horizon API endpoint that will fund an account
with 10,000 lumens on the {network.id} network.
</Text>
</div>

<Input
id="generate-keypair-publickey"
fieldSize="md"
label="Public Key"
value={generatedPublicKey}
onChange={(e) => {
setGeneratedPublicKey(e.target.value);
jeesunikim marked this conversation as resolved.
Show resolved Hide resolved

const error = validatePublicKey(e.target.value);
setInlineErrorMessage(error);
}}
placeholder="Ex: GCEXAMPLE5HWNK4AYSTEQ4UWDKHTCKADVS2AHF3UI2ZMO3DPUSM6Q4UG"
error={inlineErrorMessage}
/>

<div className="Account__CTA">
<Button
disabled={!generatedPublicKey || Boolean(inlineErrorMessage)}
size="md"
variant={isFetchedAfterMount && isError ? "error" : "secondary"}
isLoading={isLoading}
onClick={() => {
if (!inlineErrorMessage) {
refetch();
}
}}
>
Get lumens
</Button>

<Button
disabled={!account.publicKey}
jeesunikim marked this conversation as resolved.
Show resolved Hide resolved
size="md"
variant="tertiary"
onClick={() => {
setInlineErrorMessage("");
setGeneratedPublicKey(account.publicKey);
}}
>
Fill in with generated key
</Button>
</div>
</div>
</Card>

{showAlert && isFetchedAfterMount && isSuccess && (
<Alert
placement="inline"
variant="primary"
actionLabel="View on stellar.expert"
actionLink={`https://stellar.expert/explorer/${network.id}/account/${account.publicKey}`}
onClose={() => {
setShowAlert(false);
}}
>
<Text size="md" as="span" weight="medium">
jeesunikim marked this conversation as resolved.
Show resolved Hide resolved
Successfully funded {shortenStellarAddress(account.publicKey)} on{" "}
{network.id}
</Text>
</Alert>
)}
{showAlert && isFetchedAfterMount && isError && (
<Alert
placement="inline"
variant="error"
onClose={() => {
setShowAlert(false);
}}
>
<Text size="md" as="span" weight="medium">
jeesunikim marked this conversation as resolved.
Show resolved Hide resolved
{error.message}
</Text>
</Alert>
)}
</div>
);
}
37 changes: 37 additions & 0 deletions src/app/(sidebar)/account/styles.scss
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
@use "../../../styles/utils.scss" as *;

.Account {
display: flex;
flex-direction: column;
gap: pxToRem(12px);

&__card {
display: flex;
flex-direction: column;
Expand All @@ -11,6 +15,10 @@
align-self: flex-start;
}

.Text--purple {
color: var(--sds-clr-lilac-11);
}

jeesunikim marked this conversation as resolved.
Show resolved Hide resolved
&__CTA {
display: flex;
gap: pxToRem(18px) pxToRem(8px);
Expand All @@ -26,4 +34,33 @@
cursor: pointer;
}
}

&__alertbox {
display: flex;
padding: pxToRem(12px);
gap: pxToRem(12px);

.Text {
margin-bottom: 0;
}

&__content {
display: flex;
flex-direction: column;
gap: pxToRem(12px);
}

&__icon {
padding: pxToRem(9.5px);
background-color: var(--sds-clr-gray-02);
border: 1px solid var(--sds-clr-gray-06);
border-radius: 6px;
align-self: flex-start;
}

&__CTA {
display: flex;
gap: pxToRem(12px);
}
}
jeesunikim marked this conversation as resolved.
Show resolved Hide resolved
}
60 changes: 21 additions & 39 deletions src/components/FormElements/PubKeyPicker.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from "react";
jeesunikim marked this conversation as resolved.
Show resolved Hide resolved
import { StrKey } from "stellar-sdk";
import { Input, InputProps } from "@stellar/design-system";
import { validatePublicKey } from "@/helpers/validatePublicKey";

interface PubKeyPickerProps extends Omit<InputProps, "fieldSize"> {
id: string;
Expand All @@ -27,41 +27,23 @@ export const PubKeyPicker = ({
onChange,
onBlur,
...props
}: PubKeyPickerProps) => {
const validatePublicKey = (issuer: string) => {
if (!issuer) {
return "Asset issuer is required.";
}

if (issuer.startsWith("M")) {
if (!StrKey.isValidMed25519PublicKey(issuer)) {
return "Muxed account address is invalid.";
}
} else if (!StrKey.isValidEd25519PublicKey(issuer)) {
return "Public key is invalid.";
}

return "";
};

return (
<Input
id={id}
fieldSize={fieldSize}
label={label}
labelSuffix={labelSuffix}
placeholder={placeholder}
value={value}
onChange={(e) => {
const error = validatePublicKey(e.target.value);
onChange(e.target.value, error);
}}
onBlur={(e) => {
const error = validatePublicKey(e.target.value);
onBlur(e.target.value, error);
}}
error={error}
{...props}
/>
);
};
}: PubKeyPickerProps) => (
<Input
id={id}
fieldSize={fieldSize}
label={label}
labelSuffix={labelSuffix}
placeholder={placeholder}
value={value}
onChange={(e) => {
const error = validatePublicKey(e.target.value);
onChange(e.target.value, error);
}}
onBlur={(e) => {
const error = validatePublicKey(e.target.value);
onBlur(e.target.value, error);
}}
error={error}
{...props}
/>
);
7 changes: 4 additions & 3 deletions src/components/NetworkSelector/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import React, {
useState,
} from "react";
import { Button, Icon, Input, Notification } from "@stellar/design-system";
import { Networks } from "stellar-sdk";

import { NetworkIndicator } from "@/components/NetworkIndicator";
import { localStorageSavedNetwork } from "@/helpers/localStorageSavedNetwork";
Expand All @@ -20,21 +21,21 @@ const NetworkOptions: Network[] = [
label: "Futurenet",
horizonUrl: "https://horizon-futurenet.stellar.org",
rpcUrl: "https://rpc-futurenet.stellar.org",
passphrase: "Test SDF Future Network ; October 2022",
passphrase: Networks.FUTURENET,
jeesunikim marked this conversation as resolved.
Show resolved Hide resolved
},
{
id: "testnet",
label: "Testnet",
horizonUrl: "https://horizon-testnet.stellar.org",
rpcUrl: "https://soroban-testnet.stellar.org",
passphrase: "Test SDF Network ; September 2015",
passphrase: Networks.TESTNET,
},
{
id: "mainnet",
label: "Mainnet",
horizonUrl: "https://horizon.stellar.org",
rpcUrl: "",
passphrase: "Public Global Stellar Network ; September 2015",
passphrase: Networks.PUBLIC,
},
{
id: "custom",
Expand Down
7 changes: 7 additions & 0 deletions src/helpers/shortenStellarAddress.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export const shortenStellarAddress = (address: string, size: number = 8) => {
const slice = Math.ceil(size / 2);

return `${address.substring(0, slice)}…${address.substring(
address.length - slice,
)}`;
};
17 changes: 17 additions & 0 deletions src/helpers/validatePublicKey.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { StrKey } from "stellar-sdk";

export const validatePublicKey = (issuer: string) => {
if (!issuer) {
return "Asset issuer is required.";
}

if (issuer.startsWith("M")) {
if (!StrKey.isValidMed25519PublicKey(issuer)) {
return "Muxed account address is invalid.";
}
} else if (!StrKey.isValidEd25519PublicKey(issuer)) {
return "Public key is invalid.";
}

return "";
};
43 changes: 43 additions & 0 deletions src/query/useFriendBot.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { useQuery } from "@tanstack/react-query";

import { shortenStellarAddress } from "@/helpers/shortenStellarAddress";

export const useFriendBot = ({
network,
publicKey,
}: {
network: string;
publicKey: string;
}) => {
const friendbotURL =
network === "futurenet"
? "https://friendbot-futurenet.stellar.org"
: "https://friendbot.stellar.org";

const query = useQuery({
queryKey: ["friendBot"],
queryFn: async () => {
try {
const response = await fetch(`${friendbotURL}/?addr=${publicKey}`);

if (!response.ok) {
const errorBody = await response.json();

throw new Error(errorBody.status);
}
return response;
} catch (e: any) {
if (e.status === 0) {
throw new Error(`Unable to reach Friendbot server at ${network}`);
} else {
throw new Error(
`Unable to fund ${shortenStellarAddress(publicKey)} on the test network`,
);
}
}
},
enabled: false,
});

return query;
};
Loading