import { constants, generatePseudoRandomSalt } from '@0xproject/order-utils';
import { ECSignature } from '@0xproject/types';
import { BigNumber } from '@0xproject/utils';
import * as _ from 'lodash';
import * as moment from 'moment';
import {
Action,
ActionTypes,
BlockchainErrs,
Order,
ProviderType,
ScreenWidths,
Side,
SideToAssetToken,
TokenByAddress,
} from 'ts/types';
import { Translate } from 'ts/utils/translate';
import { utils } from 'ts/utils/utils';
// Instead of defaulting the docs version to an empty string, we pre-populate it with
// a valid version value. This does not need to be updated however, since onLoad, it
// is always replaced with a value retrieved from our S3 bucket.
const DEFAULT_DOCS_VERSION = '0.0.0';
export interface State {
// Portal
blockchainErr: BlockchainErrs;
blockchainIsLoaded: boolean;
networkId: number;
orderExpiryTimestamp: BigNumber;
orderFillAmount: BigNumber;
orderTakerAddress: string;
orderECSignature: ECSignature;
orderSalt: BigNumber;
nodeVersion: string;
screenWidth: ScreenWidths;
shouldBlockchainErrDialogBeOpen: boolean;
sideToAssetToken: SideToAssetToken;
tokenByAddress: TokenByAddress;
lastForceTokenStateRefetch: number;
userAddress: string;
userEtherBalanceInWei?: BigNumber;
portalOnboardingStep: number;
isPortalOnboardingShowing: boolean;
hasPortalOnboardingBeenClosed: boolean;
// Note: cache of supplied orderJSON in fill order step. Do not use for anything else.
userSuppliedOrderCache: Order;
// Docs
docsVersion: string;
availableDocVersions: string[];
// Shared
flashMessage: string | React.ReactNode;
providerType: ProviderType;
injectedProviderName: string;
translate: Translate;
}
export const INITIAL_STATE: State = {
// Portal
blockchainErr: BlockchainErrs.NoError,
blockchainIsLoaded: false,
networkId: undefined,
orderExpiryTimestamp: utils.initialOrderExpiryUnixTimestampSec(),
orderFillAmount: undefined,
orderECSignature: {
r: '',
s: '',
v: 27,
},
orderTakerAddress: constants.NULL_ADDRESS,
orderSalt: generatePseudoRandomSalt(),
nodeVersion: undefined,
screenWidth: utils.getScreenWidth(),
shouldBlockchainErrDialogBeOpen: false,
sideToAssetToken: {
[Side.Deposit]: {},
[Side.Receive]: {},
},
tokenByAddress: {},
lastForceTokenStateRefetch: moment().unix(),
userAddress: '',
userEtherBalanceInWei: undefined,
userSuppliedOrderCache: undefined,
portalOnboardingStep: 0,
isPortalOnboardingShowing: false,
hasPortalOnboardingBeenClosed: false,
// Docs
docsVersion: DEFAULT_DOCS_VERSION,
availableDocVersions: [DEFAULT_DOCS_VERSION],
// Shared
flashMessage: undefined,
providerType: ProviderType.Injected,
injectedProviderName: '',
translate: new Translate(),
};
export function reducer(state: State = INITIAL_STATE, action: Action): State {
switch (action.type) {
// Portal
case ActionTypes.ResetState:
return {
...INITIAL_STATE,
translate: state.translate,
};
case ActionTypes.UpdateOrderSalt: {
return {
...state,
orderSalt: action.data,
};
}
case ActionTypes.UpdateSelectedLanguage: {
return {
...state,
translate: new Translate(action.data),
};
}
case ActionTypes.UpdateNodeVersion: {
return {
...state,
nodeVersion: action.data,
};
}
case ActionTypes.UpdateOrderFillAmount: {
return {
...state,
orderFillAmount: action.data,
};
}
case ActionTypes.UpdateShouldBlockchainErrDialogBeOpen: {
return {
...state,
shouldBlockchainErrDialogBeOpen: action.data,
};
}
case ActionTypes.UpdateUserEtherBalance: {
return {
...state,
userEtherBalanceInWei: action.data,
};
}
case ActionTypes.UpdateUserSuppliedOrderCache: {
return {
...state,
userSuppliedOrderCache: action.data,
};
}
case ActionTypes.AddTokenToTokenByAddress: {
const newTokenByAddress = { ...state.tokenByAddress };
newTokenByAddress[action.data.address] = action.data;
return {
...state,
tokenByAddress: newTokenByAddress,
};
}
case ActionTypes.RemoveTokenFromTokenByAddress: {
const newTokenByAddress = { ...state.tokenByAddress };
delete newTokenByAddress[action.data.address];
return {
...state,
tokenByAddress: newTokenByAddress,
};
}
case ActionTypes.UpdateTokenByAddress: {
const tokenByAddress = { ...state.tokenByAddress };
const tokens = action.data;
_.each(tokens, token => {
const updatedToken = {
...tokenByAddress[token.address],
...token,
};
tokenByAddress[token.address] = updatedToken;
});
return {
...state,
tokenByAddress,
};
}
case ActionTypes.BatchDispatch: {
const userAddress = _.isUndefined(action.data.userAddressIfExists) ? '' : action.data.userAddressIfExists;
return {
...state,
networkId: action.data.networkId,
userAddress,
sideToAssetToken: action.data.sideToAssetToken,
tokenByAddress: action.data.tokenByAddress,
};
}
case ActionTypes.ForceTokenStateRefetch:
return {
...state,
lastForceTokenStateRefetch: moment().unix(),
};
case ActionTypes.UpdateOrderECSignature: {
return {
...state,
orderECSignature: action.data,
};
}
case ActionTypes.UpdateScreenWidth: {
return {
...state,
screenWidth: action.data,
};
}
case ActionTypes.UpdateBlockchainIsLoaded: {
return {
...state,
blockchainIsLoaded: action.data,
};
}
case ActionTypes.BlockchainErrEncountered: {
return {
...state,
blockchainErr: action.data,
};
}
case ActionTypes.UpdateNetworkId: {
return {
...state,
networkId: action.data,
};
}
case ActionTypes.UpdateChosenAssetToken: {
const newSideToAssetToken = {
...state.sideToAssetToken,
[action.data.side]: action.data.token,
};
return {
...state,
sideToAssetToken: newSideToAssetToken,
};
}
case ActionTypes.UpdateChosenAssetTokenAddress: {
const newAssetToken = { ...state.sideToAssetToken[action.data.side] };
newAssetToken.address = action.data.address;
const newSideToAssetToken = {
...state.sideToAssetToken,
[action.data.side]: newAssetToken,
};
return {
...state,
sideToAssetToken: newSideToAssetToken,
};
}
case ActionTypes.SwapAssetTokens: {
const newSideToAssetToken = {
[Side.Deposit]: state.sideToAssetToken[Side.Receive],
[Side.Receive]: state.sideToAssetToken[Side.Deposit],
};
return {
...state,
sideToAssetToken: newSideToAssetToken,
};
}
case ActionTypes.UpdateOrderExpiry: {
return {
...state,
orderExpiryTimestamp: action.data,
};
}
case ActionTypes.UpdateOrderTakerAddress: {
return {
...state,
orderTakerAddress: action.data,
};
}
case ActionTypes.UpdateUserAddress: {
const userAddress = _.isUndefined(action.data) ? '' : action.data;
return {
...state,
userAddress,
};
}
case ActionTypes.UpdatePortalOnboardingStep: {
const portalOnboardingStep = action.data;
return {
...state,
portalOnboardingStep,
};
}
case ActionTypes.UpdatePortalOnboardingShowing: {
const isPortalOnboardingShowing = action.data;
return {
...state,
isPortalOnboardingShowing,
hasPortalOnboardingBeenClosed: !isPortalOnboardingShowing ? true : state.hasPortalOnboardingBeenClosed,
// always start onboarding from the beginning
portalOnboardingStep: 0,
};
}
// Docs
case ActionTypes.UpdateLibraryVersion: {
return {
...state,
docsVersion: action.data,
};
}
case ActionTypes.UpdateAvailableLibraryVersions: {
return {
...state,
availableDocVersions: action.data,
};
}
// Shared
case ActionTypes.ShowFlashMessage: {
return {
...state,
flashMessage: action.data,
};
}
case ActionTypes.HideFlashMessage: {
return {
...state,
flashMessage: undefined,
};
}
case ActionTypes.UpdateProviderType: {
return {
...state,
providerType: action.data,
};
}
case ActionTypes.UpdateInjectedProviderName: {
return {
...state,
injectedProviderName: action.data,
};
}
default:
return state;
}
}