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

Add core saga logic to link a new wallet for email associated ZERO account #2247

Merged
merged 6 commits into from
Sep 5, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Container } from './container';
import { Errors, AccountManagementState } from '../../../../store/account-management';
import { Errors, AccountManagementState, State } from '../../../../store/account-management';
import { RootState } from '../../../../store/reducer';
import { ConnectionStatus } from '../../../../lib/web3';

Expand Down Expand Up @@ -57,6 +57,14 @@ describe('Container', () => {
});
});

it('addWalletState', () => {
const props = subject({
accountManagement: { state: State.NONE } as any,
});

expect(props.addWalletState).toEqual(State.NONE);
});

describe('errors', () => {
test('wallets error: unknown error', () => {
const props = subject({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,14 @@ import { RootState } from '../../../../store/reducer';
import { connectContainer } from '../../../../store/redux-container';

import { AccountManagementPanel } from './index';
import { openAddEmailAccountModal, closeAddEmailAccountModal, Errors } from '../../../../store/account-management';
import {
openAddEmailAccountModal,
closeAddEmailAccountModal,
reset,
Errors,
addNewWallet,
State,
} from '../../../../store/account-management';
import { currentUserSelector } from '../../../../store/authentication/selectors';
import { ConnectionStatus } from '../../../../lib/web3';

Expand All @@ -20,9 +27,12 @@ export interface Properties extends PublicProperties {
canAddEmail: boolean;
isWalletConnected: boolean;
connectedWallet: string;
addWalletState: State;

openAddEmailAccountModal: () => void;
closeAddEmailAccountModal: () => void;
addNewWallet: () => void;
onReset: () => void;
}

export class Container extends React.Component<Properties> {
Expand All @@ -41,6 +51,7 @@ export class Container extends React.Component<Properties> {
isAddEmailModalOpen: accountManagement.isAddEmailAccountModalOpen,
isWalletConnected: status === ConnectionStatus.Connected,
connectedWallet: value?.address,
addWalletState: accountManagement.state,
currentUser: {
userId: currentUser?.id,
firstName: currentUser?.profileSummary.firstName,
Expand All @@ -57,6 +68,8 @@ export class Container extends React.Component<Properties> {
return {
openAddEmailAccountModal,
closeAddEmailAccountModal,
addNewWallet,
onReset: reset,
};
}

Expand All @@ -81,9 +94,12 @@ export class Container extends React.Component<Properties> {
canAddEmail={this.props.canAddEmail}
isWalletConnected={this.props.isWalletConnected}
connectedWallet={this.props.connectedWallet}
addWalletState={this.props.addWalletState}
onOpenAddEmailModal={() => this.props.openAddEmailAccountModal()}
onCloseAddEmailModal={() => this.props.closeAddEmailAccountModal()}
onAddNewWallet={this.props.addNewWallet}
onBack={this.props.onClose}
reset={this.props.onReset}
/>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { PanelHeader } from '../../list/panel-header';
import { bem } from '../../../../lib/bem';
import { Button } from '@zero-tech/zui/components/Button';
import { ConnectButton } from '@rainbow-me/rainbowkit';
import { State } from '../../../../store/account-management';

const featureFlags = { enableAddWallets: true };
jest.mock('../../../../lib/feature-flags', () => ({
Expand All @@ -30,10 +31,13 @@ describe(AccountManagementPanel, () => {
canAddEmail: false,
isWalletConnected: false,
connectedWallet: '',
addWalletState: State.NONE,

onBack: () => {},
onOpenAddEmailModal: () => {},
onCloseAddEmailModal: () => {},
reset: () => {},
onAddNewWallet: () => {},
...props,
};

Expand Down Expand Up @@ -239,5 +243,56 @@ describe(AccountManagementPanel, () => {
(linkWalletModal as any).props().onClose();
expect(wrapper.state('isUserLinkingNewWallet')).toEqual(false);
});

it('does not render Link Wallet modal if error is set', () => {
const wrapper = subject({
currentUser: { primaryEmail: '[email protected]', wallets: [] },
isWalletConnected: true,
error: 'An error occurred',
});
wrapper.setState({ isUserLinkingNewWallet: true });

const linkWalletModal = wrapper.find('Modal').at(1);
expect(linkWalletModal.exists()).toEqual(false);
});

it('does not render Link Wallet modal if wallet is NOT connected', () => {
const wrapper = subject({
currentUser: { primaryEmail: '[email protected]', wallets: [] },
isWalletConnected: false,
});
wrapper.setState({ isUserLinkingNewWallet: true });

const linkWalletModal = wrapper.find('Modal').at(1);
expect(linkWalletModal.exists()).toEqual(false);
});

it('calls addNewWallet when user clicks on Link Wallet', () => {
const addNewWallet = jest.fn();
const wrapper = subject({
currentUser: { primaryEmail: '[email protected]', wallets: [] },
isWalletConnected: true,
addWalletState: State.NONE,
onAddNewWallet: addNewWallet,
});
wrapper.setState({ isUserLinkingNewWallet: true });

const linkWalletModal = wrapper.find('Modal').at(1);
(linkWalletModal as any).props().onPrimary();

expect(addNewWallet).toHaveBeenCalled();
});

it('keeps Link Wallet button in loading state while IN_PROGRESS', () => {
const wrapper = subject({
currentUser: { primaryEmail: '[email protected]', wallets: [] },
isWalletConnected: true,
addWalletState: State.INPROGRESS,
});
wrapper.setState({ isUserLinkingNewWallet: true });

const linkWalletModal = wrapper.find('Modal').at(1);
expect(linkWalletModal.prop('isProcessing')).toEqual(true);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { ScrollbarContainer } from '../../../scrollbar-container';
import { ConnectButton } from '@rainbow-me/rainbowkit';
import { Color, Modal, Variant } from '../../../modal';
import { featureFlags } from '../../../../lib/feature-flags';
import { State as AddWalletState } from '../../../../store/account-management';

const cn = bemClassName('account-management-panel');

Expand All @@ -26,10 +27,13 @@ export interface Properties {
canAddEmail: boolean;
isWalletConnected: boolean;
connectedWallet: string;
addWalletState: AddWalletState;

onBack: () => void;
reset: () => void; // reset saga state
onOpenAddEmailModal: () => void;
onCloseAddEmailModal: () => void;
onAddNewWallet: () => void;
}

interface State {
Expand All @@ -52,6 +56,7 @@ export class AccountManagementPanel extends React.Component<Properties, State> {
renderAddNewWalletButton = () => {
const handleAddWallet = (account, openConnectModal) => {
this.setIsUserLinkingNewWallet(true);
this.props.reset();

if (!account?.address) {
// Prompt user to connect their wallet if none is connected
Expand Down Expand Up @@ -178,10 +183,10 @@ export class AccountManagementPanel extends React.Component<Properties, State> {
secondaryText='Cancel'
secondaryVariant={Variant.Secondary}
secondaryColor={Color.Red}
onPrimary={() => {}}
onPrimary={this.props.onAddNewWallet}
onSecondary={onClose}
onClose={onClose}
isProcessing={false}
isProcessing={this.props.addWalletState === AddWalletState.INPROGRESS}
>
<div {...cn('link-new-wallet-modal')}>
You have a wallet connected by the address{' '}
Expand All @@ -196,6 +201,16 @@ export class AccountManagementPanel extends React.Component<Properties, State> {
);
};

get isLinkNewWalletModalOpen() {
const { isWalletConnected, error, addWalletState } = this.props;
return (
this.state.isUserLinkingNewWallet &&
isWalletConnected &&
!error &&
(addWalletState === AddWalletState.NONE || addWalletState === AddWalletState.INPROGRESS)
);
}

render() {
return (
<div {...cn()}>
Expand Down Expand Up @@ -223,7 +238,7 @@ export class AccountManagementPanel extends React.Component<Properties, State> {
</div>

{this.renderAddEmailAccountModal()}
{this.state.isUserLinkingNewWallet && this.props.isWalletConnected && this.renderLinkNewWalletModal()}
{this.isLinkNewWalletModalOpen && this.renderLinkNewWalletModal()}
</div>
</ScrollbarContainer>
</div>
Expand Down
20 changes: 20 additions & 0 deletions src/store/account-management/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { post } from '../../lib/api/rest';

export async function linkNewWalletToZEROAccount(token) {
try {
const response = await post('/api/v2/accounts/add-wallet').send({ web3Token: token });
return {
success: true,
response: response.body,
};
} catch (error: any) {
if (error?.response?.status === 400) {
return {
success: false,
response: error.response.body.code,
error: error.response.body.message,
};
}
throw error;
}
}
19 changes: 16 additions & 3 deletions src/store/account-management/index.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,29 @@
import { createAction, createSlice, PayloadAction } from '@reduxjs/toolkit';
import { Connectors } from '../../lib/web3';
//import { Connectors } from '../../lib/web3';

export enum SagaActionTypes {
AddNewWallet = 'Wallets/addNewWallet',
AddEmailAccount = 'Wallets/addEmailAccount',
OpenAddEmailAccountModal = 'Wallets/openAddEmailAccountModal',
CloseAddEmailAccountModal = 'Wallets/closeAddEmailAccountModal',
Reset = 'Wallets/reset',
}

export type AccountManagementState = {
state: State;
errors: string[];
isAddEmailAccountModalOpen: boolean;
successMessage: string;
};

export enum State {
NONE,
INPROGRESS,
LOADED,
}

export const initialState: AccountManagementState = {
state: State.NONE,
errors: [],
isAddEmailAccountModalOpen: false,
successMessage: '',
Expand All @@ -24,10 +33,11 @@ export enum Errors {
UNKNOWN_ERROR = 'UNKNOWN_ERROR',
}

export const addNewWallet = createAction<{ connector: Connectors }>(SagaActionTypes.AddNewWallet);
export const addNewWallet = createAction(SagaActionTypes.AddNewWallet);
export const addEmailAccount = createAction<{ email: string; password: string }>(SagaActionTypes.AddEmailAccount);
export const openAddEmailAccountModal = createAction(SagaActionTypes.OpenAddEmailAccountModal);
export const closeAddEmailAccountModal = createAction(SagaActionTypes.CloseAddEmailAccountModal);
export const reset = createAction(SagaActionTypes.Reset);

const slice = createSlice({
name: 'accountManagement',
Expand All @@ -42,11 +52,14 @@ const slice = createSlice({
) => {
state.isAddEmailAccountModalOpen = action.payload;
},
setState: (state, action: PayloadAction<AccountManagementState['state']>) => {
state.state = action.payload;
},
setSuccessMessage: (state, action: PayloadAction<AccountManagementState['successMessage']>) => {
state.successMessage = action.payload;
},
},
});

export const { setErrors, setAddEmailAccountModalStatus, setSuccessMessage } = slice.actions;
export const { setErrors, setAddEmailAccountModalStatus, setSuccessMessage, setState } = slice.actions;
export const { reducer } = slice;
Loading
Loading