Skip to content

Commit

Permalink
enable saving state on page refresh
Browse files Browse the repository at this point in the history
  • Loading branch information
Jeesun Kim authored and Jeesun Kim committed Jun 24, 2024
1 parent 8821da2 commit 418cb84
Show file tree
Hide file tree
Showing 3 changed files with 177 additions and 65 deletions.
197 changes: 156 additions & 41 deletions src/app/(sidebar)/transaction/fee-bump/page.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
"use client";

import { useState } from "react";
import { useEffect, useState } from "react";
import { Button, Card, Icon, Text } from "@stellar/design-system";
import { get, omit, set } from "lodash";
import { useRouter } from "next/navigation";

import { FeeBumpParams } from "@/store/createStore";
import { useStore } from "@/store/useStore";

import { validate } from "@/validate";

import { sanitizeObject } from "@/helpers/sanitizeObject";
import { txHelper, FeeBumpedTxResponse } from "@/helpers/txHelper";

import { useRouter } from "next/navigation";
import { KeysOfUnion } from "@/types/types";

import { Box } from "@/components/layout/Box";
import { PositiveIntPicker } from "@/components/FormElements/PositiveIntPicker";
Expand All @@ -24,51 +28,132 @@ export default function FeeBumpTransaction() {
const { network, transaction } = useStore();
const {
feeBump,
updateBaseFeeSource,
updateBaseFeeBase,
updateBaseFeeInnerXdr,
updateFeeBumpParams,
updateSignActiveView,
updateSignImportXdr,
resetBaseFee,
} = transaction;
const { source_account, fee, innerTxXdr } = feeBump;
const { source_account, fee, xdr } = feeBump;

// Sign tx status related
type ParamsField = KeysOfUnion<typeof feeBump>;

type ParamsError = {
[K in keyof FeeBumpParams]?: any;
};

// buildFeeBumpTransaction status result
const [feeBumpedTx, setFeeBumpedTx] = useState<FeeBumpedTxResponse>({
errors: [],
xdr: "",
});
const [xdrError, setXdrError] = useState<string>("");
const [sourceErrorMessage, setSourceErrorMessage] = useState<string>("");
const [baseErrorMessage, setBaseErrorMessage] = useState<string>("");
const [paramsError, setParamsError] = useState<ParamsError>({});

const onXdrChange = (value: string) => {
useEffect(() => {
Object.entries(feeBump).forEach(([key, val]) => {
if (val) {
handleParamsError(key, validateParam(key as ParamsField, val));
}
});

// Run this only when page loads
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

useEffect(() => {
if (source_account && fee && xdr) {
handleFieldChange(source_account, fee, xdr);
}

// Not inlcuding handleFieldChange
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [source_account, fee, xdr]);

const resetResult = () => {
// reset messages onChange
setXdrError("");
setFeeBumpedTx({
errors: [],
xdr: "",
});
};

updateBaseFeeInnerXdr(value);
const validateParam = (param: ParamsField, value: any) => {
switch (param) {
case "source_account":
return validate.publicKey(value);
case "fee":
return validate.positiveInt(value);
case "xdr":
if (validate.xdr(value)?.result === "success") {
return false;
}
return validate.xdr(value)?.message;
default:
return false;
}
};

if (value.length > 0) {
const validatedXDR = validate.xdr(value);
const getFieldLabel = (field: ParamsField) => {
switch (field) {
case "fee":
return "Base Fee";
case "source_account":
return "Source Account";
case "xdr":
return "Inner Transaction";
default:
return "";
}
};

if (validatedXDR?.result && validatedXDR.message) {
if (validatedXDR.result === "success") {
const result = txHelper.buildFeeBumpTx({
innerTxXDR: value,
maxFee: fee,
sourceAccount: source_account,
networkPassphrase: network.passphrase,
});
const getMissingFieldErrors = () => {
const allErrorMessages: string[] = [];
const missingParams = Object.fromEntries(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
Object.entries(feeBump).filter(([_, value]) => !value),
);

setFeeBumpedTx(result);
} else {
setXdrError(validatedXDR.message);
}
}
const missingParamsKeys = Object.keys(missingParams);

if (missingParamsKeys.length > 0) {
const errorMsg = missingParamsKeys.reduce((res, cur) => {
return [
...res,
`${getFieldLabel(cur as ParamsField)} is a required field`,
];
}, [] as string[]);

allErrorMessages.push(...errorMsg);
}

return allErrorMessages;
};

const handleParamsChange = <T,>(paramPath: string, value: T) => {
updateFeeBumpParams(set({}, `${paramPath}`, value));
};

const handleParamsError = <T,>(id: string, error: T) => {
if (error) {
setParamsError(set({ ...paramsError }, id, error));
} else if (get(paramsError, id)) {
setParamsError(sanitizeObject(omit({ ...paramsError }, id), true));
}
};

const handleFieldChange = (
source_account: string,
fee: string,
xdr: string,
) => {
const result = txHelper.buildFeeBumpTx({
innerTxXdr: xdr,
maxFee: fee,
sourceAccount: source_account,
networkPassphrase: network.passphrase,
});

if (result) {
setFeeBumpedTx(result);
}
};

Expand All @@ -85,6 +170,7 @@ export default function FeeBumpTransaction() {
icon={<Icon.RefreshCw01 />}
iconPosition="right"
onClick={() => {
resetResult();
resetBaseFee();
}}
>
Expand All @@ -97,11 +183,14 @@ export default function FeeBumpTransaction() {
id="source_account"
label="Source Account"
value={source_account}
error={sourceErrorMessage}
error={paramsError.source_account}
onChange={(e) => {
const error = validate.publicKey(e.target.value);
setSourceErrorMessage(error || "");
updateBaseFeeSource(e.target.value);
const id = "source_account";

resetResult();

handleParamsError(id, validateParam(id, e.target.value));
handleParamsChange(id, e.target.value);
}}
note="The account responsible for paying the transaction fee."
infoLink="https://developers.stellar.org/docs/learn/glossary#source-account"
Expand All @@ -111,11 +200,14 @@ export default function FeeBumpTransaction() {
id="fee"
label="Base Fee"
value={fee}
error={baseErrorMessage}
error={paramsError.fee}
onChange={(e) => {
const error = validate.positiveInt(e.target.value);
setBaseErrorMessage(error || "");
updateBaseFeeBase(e.target.value);
const id = "fee";

resetResult();

handleParamsError(id, validateParam(id, e.target.value));
handleParamsChange(id, e.target.value);
}}
note={
<>
Expand All @@ -133,11 +225,18 @@ export default function FeeBumpTransaction() {
/>

<XdrPicker
id="submit-tx-xdr"
id="xdr"
label="Input a base-64 encoded TransactionEnvelope:"
value={innerTxXdr}
error={xdrError}
onChange={(e) => onXdrChange(e.target.value)}
value={xdr}
error={paramsError.xdr}
onChange={(e) => {
const id = "xdr";

resetResult();

handleParamsError(id, validateParam(id, e.target.value));
handleParamsChange(id, e.target.value);
}}
note="Enter a base-64 encoded XDR blob to decode."
hasCopyButton
/>
Expand Down Expand Up @@ -187,14 +286,30 @@ export default function FeeBumpTransaction() {
) : null}
</>
<>
{feeBumpedTx.errors.length && innerTxXdr ? (
{feeBumpedTx.errors.length ? (
<ValidationResponseCard
variant="error"
title="Transaction Sign Error:"
response={feeBumpedTx.errors}
/>
) : null}
</>

<>
{getMissingFieldErrors().length > 0 ? (
<ValidationResponseCard
variant="primary"
title="Fee bump errors:"
response={
<ul>
{getMissingFieldErrors().map((e, i) => (
<li key={`e-${i}`}>{e}</li>
))}
</ul>
}
/>
) : null}
</>
</Box>
);
}
8 changes: 3 additions & 5 deletions src/helpers/txHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const buildFeeBumpTx = ({
sourceAccount,
networkPassphrase,
}: {
innerTxXDR: string;
innerTxXdr: string;
maxFee: string;
sourceAccount: string;
networkPassphrase: string;
Expand All @@ -41,7 +41,7 @@ const buildFeeBumpTx = ({

try {
innerTx = TransactionBuilder.fromXDR(
innerTxXDR,
innerTxXdr,
networkPassphrase,
) as Transaction;
} catch (e) {
Expand All @@ -54,10 +54,8 @@ const buildFeeBumpTx = ({
return result;
}

let feeBumpTx;

try {
feeBumpTx = TransactionBuilder.buildFeeBumpTransaction(
const feeBumpTx = TransactionBuilder.buildFeeBumpTransaction(
sourceAccount,
maxFee,
innerTx,
Expand Down
37 changes: 18 additions & 19 deletions src/store/createStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,16 @@ import {
TxnOperation,
} from "@/types/types";

export type FeeBumpParams = {
source_account: string;
fee: string;
xdr: string;
};

type FeeBumpParamsObj = {
[K in keyof FeeBumpParams]?: FeeBumpParams[K];
};

export type TransactionBuildParams = {
source_account: string;
fee: string;
Expand Down Expand Up @@ -95,11 +105,7 @@ export interface Store {
bipPath: string;
hardWalletSigs: xdr.DecoratedSignature[] | [];
};
feeBump: {
source_account: string;
fee: string;
innerTxXdr: string;
};
feeBump: FeeBumpParams;
// [Transaction] Build Transaction actions
updateBuildActiveTab: (tabId: string) => void;
updateBuildParams: (params: TransactionBuildParamsObj) => void;
Expand All @@ -126,9 +132,7 @@ export interface Store {
updateHardWalletSigs: (signer: xdr.DecoratedSignature[]) => void;
resetSign: () => void;
resetSignHardWalletSigs: () => void;
updateBaseFeeSource: (source: string) => void;
updateBaseFeeBase: (feeBase: string) => void;
updateBaseFeeInnerXdr: (xdr: string) => void;
updateFeeBumpParams: (params: FeeBumpParamsObj) => void;
resetBaseFee: () => void;
};

Expand Down Expand Up @@ -190,7 +194,7 @@ const initTransactionState = {
feeBump: {
source_account: "",
fee: "",
innerTxXdr: "",
xdr: "",
},
};

Expand Down Expand Up @@ -377,17 +381,12 @@ export const createStore = (options: CreateStoreOptions) =>
state.transaction.sign.hardWalletSigs =
initTransactionState.sign.hardWalletSigs;
}),
updateBaseFeeSource: (source: string) =>
updateFeeBumpParams: (params: FeeBumpParamsObj) =>
set((state) => {
state.transaction.feeBump.source_account = source;
}),
updateBaseFeeBase: (feeBase: string) =>
set((state) => {
state.transaction.feeBump.fee = feeBase;
}),
updateBaseFeeInnerXdr: (xdr: string) =>
set((state) => {
state.transaction.feeBump.innerTxXdr = xdr;
state.transaction.feeBump = {
...state.transaction.feeBump,
...params,
};
}),
resetBaseFee: () =>
set((state) => {
Expand Down

0 comments on commit 418cb84

Please sign in to comment.