Skip to content

Commit

Permalink
feat: improve stacking flow, closes #343
Browse files Browse the repository at this point in the history
  • Loading branch information
kyranjamie committed Jan 12, 2021
1 parent 28b912a commit 2141120
Show file tree
Hide file tree
Showing 9 changed files with 95 additions and 41 deletions.
5 changes: 2 additions & 3 deletions app/main.dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import contextMenu from 'electron-context-menu';

import MenuBuilder from './menu';
import { deriveKey } from './crypto/key-generation';
import { NETWORK } from './constants/index';
import { validateConfig } from './main/validate-config';
import { getUserDataPath } from './main/get-user-data-path';

Expand Down Expand Up @@ -83,7 +82,7 @@ const createWindow = async () => {
});

const iconPath =
NETWORK === 'mainnet'
process.env.STX_NETWORK === 'mainnet'
? path.join(__dirname, '../resources/icon-512x512.png')
: path.join(__dirname, '../resources/icon-512x512-testnet.png');

Expand All @@ -96,7 +95,7 @@ const createWindow = async () => {
frame: process.platform !== 'darwin',
titleBarStyle: process.platform === 'darwin' ? 'hidden' : 'default',
icon: iconPath,
title: `Stacks Wallet` + (NETWORK === 'testnet' ? ' Testnet' : ''),
title: `Stacks Wallet` + (process.env.STX_NETWORK === 'testnet' ? ' Testnet' : ''),
webPreferences: {
disableBlinkFeatures: 'Auxclick',
spellcheck: false,
Expand Down
2 changes: 1 addition & 1 deletion app/main/get-user-data-path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export function getUserDataPath(app: App) {
const appId = 'so.hiro.StacksWallet';
const appData = app.getPath('appData');
const network = process.env.STX_NETWORK === 'mainnet' ? '' : 'Testnet';
if (CONFIG.NODE_ENV === 'development') {
if (process.env.NODE_ENV === 'development') {
const devName = `${appId}${network}Dev`;
return path.join(appData, devName);
}
Expand Down
9 changes: 6 additions & 3 deletions app/pages/stacking/components/stacking-form-step.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,20 @@ import {
ButtonProps,
} from '@blockstack/ui';

import { StackingStepState } from '../stacking';
interface StackingFormStepProps extends FlexProps {
title: string;
isComplete: boolean;
state: StackingStepState;
value?: string;
step?: number;
onEdit?(step: number): void;
}

export const StackingStep: FC<StackingFormStepProps> = props => {
const { step, title, isComplete, value, children, onEdit, ...rest } = props;
const { step, title, state, isComplete, value, children, onEdit, ...rest } = props;
if (!step) return null;
const showCompeteCheckmark = isComplete && state !== 'open';
return (
<Flex flexDirection="column" mt="extra-loose" {...rest}>
<Text
Expand All @@ -35,9 +38,9 @@ export const StackingStep: FC<StackingFormStepProps> = props => {
<Text textStyle="display.small" mt="extra-tight" mr="tight">
{title}
</Text>
{isComplete && <CheckmarkCircleIcon size="16px" color="blue" />}
{showCompeteCheckmark && <CheckmarkCircleIcon size="16px" color="blue" />}
</Flex>
{isComplete ? (
{state === 'closed' ? (
<Box>
{value && (
<Text display="block" mt="tight" textStyle="body.large">
Expand Down
71 changes: 46 additions & 25 deletions app/pages/stacking/stacking.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,41 +32,55 @@ enum Step {
ConfirmAndLock = 'Confirm and lock',
}

type StepState = 'incomplete' | 'complete' | null;
export type StackingStepState = 'open' | 'closed';

export const Stacking: FC = () => {
useBackButton(routes.HOME);

const [amount, setAmount] = useState<BigNumber | null>(null);
const [cycles, setCycles] = useState(12);
const [cycles, setCycles] = useState<number | null>(null);
const [btcAddress, setBtcAddress] = useState<string | null>(null);
const [modalOpen, setModalOpen] = useState(false);

const [stepConfirmation, setStepConfirmation] = useState<Record<Step, StepState>>({
[Step.ChooseAmount]: 'incomplete',
[Step.ChooseCycles]: 'incomplete',
[Step.ChooseBtcAddress]: 'incomplete',
[Step.ConfirmAndLock]: null,
const [stepState, setStepState] = useState<Record<Step, StackingStepState>>({
[Step.ChooseAmount]: 'open',
[Step.ChooseCycles]: 'closed',
[Step.ChooseBtcAddress]: 'closed',
[Step.ConfirmAndLock]: 'open',
});

const stepComplete = {
[Step.ChooseAmount]: amount !== null && stepState[Step.ChooseAmount] === 'closed',
[Step.ChooseCycles]: cycles !== null && stepState[Step.ChooseCycles] === 'closed',
[Step.ChooseBtcAddress]: btcAddress !== null && stepState[Step.ChooseBtcAddress] === 'closed',
[Step.ConfirmAndLock]: true,
};

const { stackingCycleDuration, availableBalance, nextCycleInfo, poxInfo } = useSelector(
(state: RootState) => ({
walletType: selectWalletType(state),
activeNode: selectActiveNodeApi(state),
stackingCycleDuration: selectEstimatedStackingDuration(cycles)(state),
stackingCycleDuration: selectEstimatedStackingDuration(cycles || 1)(state),
availableBalance: selectAvailableBalance(state),
nextCycleInfo: selectNextCycleInfo(state),
poxInfo: selectPoxInfo(state),
})
);

const updateStep = (step: Step, to: StepState) =>
setStepConfirmation(state => ({ ...state, [step]: to }));

const isComplete = (step: Step) => stepConfirmation[step] === 'complete';
const updateStep = (step: Step, to: StackingStepState) => {
if (step === Step.ChooseAmount && to === 'closed') {
setStepState(state => ({ ...state, [Step.ChooseCycles]: 'open' }));
}
if (step === Step.ChooseCycles && to === 'closed') {
setStepState(state => ({ ...state, [Step.ChooseBtcAddress]: 'open' }));
}
setStepState(state => ({ ...state, [step]: to }));
};

const formComplete =
[Step.ChooseAmount, Step.ChooseCycles, Step.ChooseBtcAddress].every(isComplete) && !!btcAddress;
[Step.ChooseAmount, Step.ChooseCycles, Step.ChooseBtcAddress].every(
value => stepComplete[value]
) && !!btcAddress;

const balance = availableBalance === null ? new BigNumber(0) : new BigNumber(availableBalance);

Expand All @@ -78,7 +92,7 @@ export const Stacking: FC = () => {

const stackingInfoCard = (
<StackingInfoCard
cycles={cycles}
cycles={cycles || 1}
balance={amount}
startDate={nextCycleInfo.nextCycleStartingAt}
blocksPerCycle={poxInfo.reward_cycle_length}
Expand All @@ -90,28 +104,34 @@ export const Stacking: FC = () => {
<StackingFormContainer>
<ChooseAmountStep
id={Step.ChooseAmount}
isComplete={isComplete(Step.ChooseAmount)}
isComplete={stepComplete[Step.ChooseAmount]}
state={stepState[Step.ChooseAmount]}
value={amount}
balance={balance}
minimumAmountToStack={poxInfo.paddedMinimumStackingAmountMicroStx}
onEdit={() => updateStep(Step.ChooseAmount, 'incomplete')}
onComplete={amount => (setAmount(amount), updateStep(Step.ChooseAmount, 'complete'))}
onEdit={() => updateStep(Step.ChooseAmount, 'open')}
onComplete={amount => (setAmount(amount), updateStep(Step.ChooseAmount, 'closed'))}
/>
<ChooseCycleStep
id={Step.ChooseCycles}
cycles={cycles}
isComplete={isComplete(Step.ChooseCycles)}
onEdit={() => updateStep(Step.ChooseCycles, 'incomplete')}
onComplete={() => updateStep(Step.ChooseCycles, 'complete')}
cycles={cycles || 1}
isComplete={stepComplete[Step.ChooseCycles]}
state={stepState[Step.ChooseCycles]}
onEdit={() => updateStep(Step.ChooseCycles, 'open')}
onComplete={cycles => {
setCycles(cycles);
updateStep(Step.ChooseCycles, 'closed');
}}
onUpdate={cycle => setCycles(cycle)}
/>
<ChooseBtcAddressStep
id={Step.ChooseBtcAddress}
value={btcAddress || undefined}
isComplete={isComplete(Step.ChooseBtcAddress)}
onEdit={() => updateStep(Step.ChooseBtcAddress, 'incomplete')}
isComplete={stepComplete[Step.ChooseBtcAddress]}
state={stepState[Step.ChooseBtcAddress]}
onEdit={() => updateStep(Step.ChooseBtcAddress, 'open')}
onComplete={address => (
setBtcAddress(address), updateStep(Step.ChooseBtcAddress, 'complete')
setBtcAddress(address), updateStep(Step.ChooseBtcAddress, 'closed')
)}
/>
<ConfirmAndLockStep
Expand All @@ -126,11 +146,12 @@ export const Stacking: FC = () => {

return (
<>
a
{modalOpen && btcAddress && amount !== null && (
<StackingModal
onClose={() => setModalOpen(false)}
amountToStack={amount}
numCycles={cycles}
numCycles={cycles || 1}
poxAddress={btcAddress}
/>
)}
Expand Down
15 changes: 14 additions & 1 deletion app/pages/stacking/step/choose-amount.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ import { ErrorLabel } from '@components/error-label';
import { ErrorText } from '@components/error-text';
import { validateDecimalPrecision } from '@utils/form/validate-decimals';
import { microStxToStx, stxToMicroStx, toHumanReadableStx } from '@utils/unit-convert';
import { StackingStepState } from '../stacking';

interface ChooseAmountStepProps {
id: string;
balance: BigNumber;
step?: number;
state: StackingStepState;
minimumAmountToStack: number;
isComplete: boolean;
value: BigNumber | null;
Expand All @@ -28,7 +30,17 @@ interface ChooseAmountStepProps {
const BigNumberFloorRound = BigNumber.clone({ ROUNDING_MODE: BigNumber.ROUND_FLOOR });

export const ChooseAmountStep: FC<ChooseAmountStepProps> = props => {
const { isComplete, step, id, value, balance, minimumAmountToStack, onEdit, onComplete } = props;
const {
isComplete,
state,
step,
id,
value,
balance,
minimumAmountToStack,
onEdit,
onComplete,
} = props;

const stxAmountForm = useFormik({
initialValues: { stxAmount: '' },
Expand Down Expand Up @@ -76,6 +88,7 @@ export const ChooseAmountStep: FC<ChooseAmountStepProps> = props => {
step={step}
title={id}
value={currentValue.toString()}
state={state}
isComplete={isComplete}
onEdit={onEdit}
>
Expand Down
13 changes: 11 additions & 2 deletions app/pages/stacking/step/choose-btc-address.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import validate from 'bitcoin-address-validation';
import { ErrorText } from '@components/error-text';
import { ErrorLabel } from '@components/error-label';
import { NETWORK, SUPPORTED_BTC_ADDRESS_FORMATS } from '@constants/index';
import { StackingStepState } from '../stacking';

import {
StackingStep,
Expand All @@ -17,13 +18,14 @@ interface ChooseBtcAddressStepProps {
id: string;
step?: number;
isComplete: boolean;
state: StackingStepState;
value?: string;
onEdit(): void;
onComplete(address: string): void;
}

export const ChooseBtcAddressStep: FC<ChooseBtcAddressStepProps> = props => {
const { isComplete, step, id, value, onEdit, onComplete } = props;
const { isComplete, state, step, id, value, onEdit, onComplete } = props;

const btcAddressForm = useFormik({
initialValues: { btcAddress: '' },
Expand All @@ -48,7 +50,14 @@ export const ChooseBtcAddressStep: FC<ChooseBtcAddressStepProps> = props => {
});

return (
<StackingStep title={id} step={step} value={value} isComplete={isComplete} onEdit={onEdit}>
<StackingStep
title={id}
step={step}
state={state}
value={value}
isComplete={isComplete}
onEdit={onEdit}
>
<StackingStepDescription>
Choose the address where you’d like to receive Bitcoin.
</StackingStepDescription>
Expand Down
17 changes: 13 additions & 4 deletions app/pages/stacking/step/choose-cycles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,31 @@ import {
StackingStepAction,
StackingStepDescription,
} from '../components/stacking-form-step';
import { StackingStepState } from '../stacking';

interface ChooseCycleStepProps {
id: string;
step?: number;
isComplete: boolean;
state: StackingStepState;
cycles: number;
onEdit(): void;
onComplete(): void;
onComplete(cycle: number): void;
onUpdate(cycle: number): void;
}

export const ChooseCycleStep: FC<ChooseCycleStepProps> = props => {
const { isComplete, step, cycles, id, onUpdate, onEdit, onComplete } = props;
const { isComplete, state, step, cycles, id, onUpdate, onEdit, onComplete } = props;
const value = `${cycles} cycle${cycles !== 1 ? 's' : ''} selected`;
return (
<StackingStep step={step} title={id} value={value} isComplete={isComplete} onEdit={onEdit}>
<StackingStep
step={step}
title={id}
value={value}
state={state}
isComplete={isComplete}
onEdit={onEdit}
>
<StackingStepDescription>
Choose the amount of cycles to lock your STX. One cycle typically lasts between 6 and 8
days, depending on the Bitcoin block time. At the end of each cycle, you'll have the chance
Expand All @@ -41,7 +50,7 @@ export const ChooseCycleStep: FC<ChooseCycleStepProps> = props => {
onUpdate(cycle);
}}
/>
<StackingStepAction onClick={onComplete}>Continue</StackingStepAction>
<StackingStepAction onClick={() => onComplete(cycles)}>Continue</StackingStepAction>
</StackingStep>
);
};
2 changes: 1 addition & 1 deletion app/pages/stacking/step/confirm-and-lock.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export const ConfirmAndLockStep: FC<ConfirmAndLockStepProps> = props => {
const { step, id, formComplete, timeUntilNextCycle, estimatedDuration, onConfirmAndLock } = props;

return (
<StackingStep title={id} step={step} isComplete={false} mb="300px">
<StackingStep title={id} step={step} isComplete={false} state="open" mb="300px">
<StackingStepDescription>
When you confirm, your STX will be locked in your wallet.
<br />
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"package-win": "yarn build && electron-builder build --win --x64",
"postinstall": "node -r @babel/register internals/scripts/CheckNativeDep.js && electron-builder install-app-deps && yarn build-dll",
"start": "cross-env NODE_ENV=production electron ./app/main.prod.js",
"start-main-dev": "cross-env START_HOT=1 NODE_ENV=production electron -r ./internals/scripts/BabelRegister ./app/main.dev.ts",
"start-main-dev": "cross-env START_HOT=1 NODE_ENV=development electron -r ./internals/scripts/BabelRegister ./app/main.dev.ts",
"start-renderer-dev": "cross-env NODE_ENV=development webpack-dev-server --config configs/webpack.config.renderer.dev.babel.js",
"start-preload-dev": "cross-env NODE_ENV=development webpack --watch --config configs/webpack.config.preload.babel.js",
"test": "cross-env NODE_ENV=test BABEL_DISABLE_CACHE=1 jest"
Expand Down

0 comments on commit 2141120

Please sign in to comment.