Skip to content

Commit

Permalink
feat: add nano-contract transaction actions (#530)
Browse files Browse the repository at this point in the history
* feat: add tx actions
  • Loading branch information
alexruzenhack authored Aug 19, 2024
1 parent 87b5884 commit 6d7b713
Show file tree
Hide file tree
Showing 7 changed files with 310 additions and 59 deletions.
61 changes: 61 additions & 0 deletions src/components/NanoContract/NanoContractTransactionActionList.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/**
* Copyright (c) Hathor Labs and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

import { StyleSheet, View } from 'react-native';
import { t } from 'ttag';
import { COLORS } from '../../styles/themes';
import { NanoContractTransactionActionListItem } from './NanoContractTransactionActionListItem';
import { HathorFlatList } from '../HathorFlatList';
import { FeedbackContent } from '../FeedbackContent';

/**
* It presents a list of actions of a transaction.
*
* @param {Object} props
* @param {Object} props.tx Transaction data
*/
export const NanoContractTransactionActionList = ({ tx }) => {
const isEmpty = () => tx.actions.length === 0;
const notEmpty = () => !isEmpty();

return (
<Wrapper>
{isEmpty()
&& (<NoActions />)}
{notEmpty()
&& (
<HathorFlatList
data={tx.actions}
renderItem={({ item }) => (
<NanoContractTransactionActionListItem item={item} />
)}
/>
)}
</Wrapper>
);
};

const NoActions = () => (
<FeedbackContent
title={t`No Actions`}
message={t`See full transaction details on Public Explorer.`}
/>
);

const Wrapper = ({ children }) => (
<View style={styles.wrapper}>
{children}
</View>
);

const styles = StyleSheet.create({
wrapper: {
flex: 1,
alignSelf: 'stretch',
backgroundColor: COLORS.lowContrastDetail, // Defines an outer area on the main list content
},
});
184 changes: 184 additions & 0 deletions src/components/NanoContract/NanoContractTransactionActionListItem.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
/**
* Copyright (c) Hathor Labs and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

import React from 'react';
import {
TouchableHighlight,
StyleSheet,
View,
Text,
} from 'react-native';
import { useSelector } from 'react-redux';
import { t } from 'ttag';
import { NANO_CONTRACT_ACTION } from '../../constants';
import { COLORS } from '../../styles/themes';
import { getShortHash, isTokenNFT, renderValue } from '../../utils';
import { ReceivedIcon } from '../Icons/Received.icon';
import { SentIcon } from '../Icons/Sent.icon';

/**
* Retrieves token symbol, otherwise returns a shortened token hash.
*
* @param {string} tokenUid Token hash
* @returns {(state) => boolean} Callback that takes state as input
*
* Remarks:
* This function should be used combined with `useSelector`.
*/
function getTokenSymbol(tokenUid) {
return (state) => {
const tokens = state.tokens || {};
if (tokenUid in tokens) {
return tokens[tokenUid].symbol;
}
return getShortHash(tokenUid, 7);
};
}

/**
* Checks if the referred token is an NFT.
*
* @param {string} tokenUid Token hash
* @returns {(state) => boolean} Callback that takes state as input
*
* Remarks:
* This function should be used combined with `useSelector`.
*/
function checkIsTokenNft(tokenUid) {
return (state) => isTokenNFT(tokenUid, state.tokenMetadata || {});
}

/**
* It renders the item of actions list of a Nano Contract transaction.
*
* @param {Object} props
* @param {{
* type: string;
* uid: string;
* amount: number;
* }} props.item A transaction action
*/
export const NanoContractTransactionActionListItem = ({ item }) => {
const tokenSymbol = useSelector(getTokenSymbol(item.uid));
const isNft = useSelector(checkIsTokenNft(item.uid));

return (
<Wrapper>
<Icon type={item.type} />
<ContentWrapper tokenSymbol={tokenSymbol} type={item.type} />
<TokenAmount amount={item.amount} isNft={isNft} type={item.type} />
</Wrapper>
);
};

const Wrapper = ({ children }) => (
<TouchableHighlight underlayColor={COLORS.primaryOpacity30}>
<View style={styles.wrapper}>{children}</View>
</TouchableHighlight>
);

/**
* It renders the balance icon, either sent or received.
*
* @param {Object} props
* @param {'deposit'|'withdrawal'} props.type An action type
*/
const Icon = ({ type }) => {
const iconMap = {
deposit: SentIcon({ type: 'default' }),
withdrawal: ReceivedIcon({ type: 'default' }),
};

return (iconMap[type]);
};

/**
* Renders item core content.
*
* @param {Object} props
* @property {string} props.tokenSymbol The symbol that represents a token
* @property {'deposit'|'withdrawal'} props.type An action type
*/
const ContentWrapper = ({ tokenSymbol, type }) => {
const contentMap = {
deposit: t`Deposit ${tokenSymbol}`,
withdrawal: t`Withdrawal ${tokenSymbol}`,
};

return (
<View style={styles.contentWrapper}>
<Text style={[styles.text, styles.property]}>{contentMap[type]}</Text>
</View>
);
};

/**
* It presents the token's amount using the right style.
*
* @param {Object} props
* @param {number} props.amount Action amount as integer
* @param {boolean} props.isNft True when it is an NFT, false otherwise
* @param {'deposit'|'withdrawal'} props.type An action type
*/
const TokenAmount = ({ amount, isNft, type }) => {
const isReceivingToken = type === NANO_CONTRACT_ACTION.withdrawal;
const amountToRender = renderValue(amount, isNft);

return (
<View style={styles.amountWrapper}>
<Text style={[
styles.amount,
isReceivingToken && styles.amountReceived,
]}
>
{amountToRender}
</Text>
</View>
)
};

const styles = StyleSheet.create({
wrapper: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
flexWrap: 'wrap',
width: '100%',
paddingVertical: 24,
paddingHorizontal: 16,
},
contentWrapper: {
maxWidth: '80%',
flexDirection: 'column',
alignItems: 'flex-start',
justifyContent: 'space-between',
marginRight: 'auto',
paddingHorizontal: 16,
},
text: {
fontSize: 14,
lineHeight: 20,
paddingBottom: 6,
color: 'hsla(0, 0%, 38%, 1)',
},
property: {
paddingBottom: 4,
fontWeight: 'bold',
color: 'black',
},
amountWrapper: {
marginLeft: 'auto',
},
amount: {
fontSize: 16,
lineHeight: 20,
},
amountReceived: {
color: 'hsla(180, 85%, 34%, 1)',
fontWeight: 'bold',
},
});
83 changes: 36 additions & 47 deletions src/components/NanoContract/NanoContractTransactionHeader.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { ArrowDownIcon } from '../Icons/ArrowDown.icon';
import { TextValue } from '../TextValue';
import { TextLabel } from '../TextLabel';
import { TransactionStatusLabel } from '../TransactionStatusLabel';
import { ArrowUpIcon } from '../Icons/ArrowUp.icon';

/**
* It presents the header of Nano Contract Transaction screen.
Expand All @@ -31,16 +32,12 @@ import { TransactionStatusLabel } from '../TransactionStatusLabel';
* @param {Obejct} props.tx Transaction data
*/
export const NanoContractTransactionHeader = ({ tx }) => {
// XXX: the set function for the state is beeing ignored because we can't
// use the shrank format just yet. We need the actions component first.
// For his, we also need hathor-core support for actions in each nano contract
// transaction.
const [isShrank] = useState(false);
const [isShrank, toggleShrank] = useState(false); // shows expanded header by default

return (
<HathorHeader>
<HathorHeader.Central style={styles.headerCentral}>
<TouchableWithoutFeedback onPress={() => {}}>
<TouchableWithoutFeedback onPress={() => toggleShrank(!isShrank)}>
<View style={styles.headerWrapper}>
<InfoContainer>
<TextValue title pb4>{getShortHash(tx.txId, 7)}</TextValue>
Expand Down Expand Up @@ -77,38 +74,40 @@ const HeaderExpanded = ({ tx }) => {
Linking.openURL(explorerLink);
};

/* XXX: add <ArrowUpIcon /> when shrank component can be used. */
return (
<View style={styles.wrapper}>
<InfoContainer>
<TransactionStatusLabel hasFirstBlock={hasFirstBlock} isVoided={tx.isVoided} />
</InfoContainer>
<InfoContainer>
<TextValue bold pb4>{ncId}</TextValue>
<TextLabel>{t`Nano Contract ID`}</TextLabel>
</InfoContainer>
<InfoContainer>
<TextValue bold pb4>{tx.ncMethod}</TextValue>
<TextLabel>{t`Blueprint Method`}</TextLabel>
</InfoContainer>
<InfoContainer>
<TextValue bold pb4>{getTimestampFormat(tx.timestamp)}</TextValue>
<TextLabel>{t`Date and Time`}</TextLabel>
</InfoContainer>
<InfoContainer lastElement>
<TextValue bold>{callerAddr}</TextValue>
{tx.isMine
&& (
<View style={styles.headlineLabel}>
<Text style={styles.isMineLabel}>{t`From this wallet`}</Text>
</View>
)}
<TextLabel>{t`Caller`}</TextLabel>
</InfoContainer>
<ActionsWrapper>
<PrimaryTextButton title={t`See transaction details`} onPress={navigatesToExplorer} />
</ActionsWrapper>
</View>
<>
<View style={styles.wrapper}>
<InfoContainer>
<TransactionStatusLabel hasFirstBlock={hasFirstBlock} isVoided={tx.isVoided} />
</InfoContainer>
<InfoContainer>
<TextValue bold pb4>{ncId}</TextValue>
<TextLabel>{t`Nano Contract ID`}</TextLabel>
</InfoContainer>
<InfoContainer>
<TextValue bold pb4>{tx.ncMethod}</TextValue>
<TextLabel>{t`Blueprint Method`}</TextLabel>
</InfoContainer>
<InfoContainer>
<TextValue bold pb4>{getTimestampFormat(tx.timestamp)}</TextValue>
<TextLabel>{t`Date and Time`}</TextLabel>
</InfoContainer>
<InfoContainer lastElement>
<TextValue bold>{callerAddr}</TextValue>
{tx.isMine
&& (
<View style={styles.headlineLabel}>
<Text style={styles.isMineLabel}>{t`From this wallet`}</Text>
</View>
)}
<TextLabel>{t`Caller`}</TextLabel>
</InfoContainer>
<ActionsWrapper>
<PrimaryTextButton title={t`See transaction details`} onPress={navigatesToExplorer} />
</ActionsWrapper>
</View>
<ArrowUpIcon />
</>
)
};

Expand Down Expand Up @@ -161,12 +160,6 @@ const styles = StyleSheet.create({
headerWrapper: {
alignItems: 'center',
},
headerTitle: {
fontSize: 18,
lineHeight: 20,
fontWeight: 'bold',
paddingVertical: 16,
},
wrapper: {
paddingHorizontal: 16,
paddingBottom: 16,
Expand All @@ -189,10 +182,6 @@ const styles = StyleSheet.create({
buttonText: {
fontWeight: 'bold',
},
buttonUnregister: {
marginStart: 24,
color: 'hsla(0, 100%, 41%, 1)',
},
buttonDetails: {
display: 'inline-block',
/* We are using negative margin here to correct the text position
Expand Down
8 changes: 8 additions & 0 deletions src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -290,3 +290,11 @@ export const NANO_CONTRACT_TX_HISTORY_SIZE = 20;
* Nano Contract documentation URL.
*/
export const NANO_CONTRACT_INFO_URL = 'https://docs.hathor.network/explanations/features/nano-contracts/';

/**
* Nano Contract Action Enum
*/
export const NANO_CONTRACT_ACTION = {
withdrawal: 'withdrawal',
deposit: 'deposit',
};
Loading

0 comments on commit 6d7b713

Please sign in to comment.