-
Notifications
You must be signed in to change notification settings - Fork 23
Wallet Detail - Action Summary & Action Logs for stewards #221 #297
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
Changes from 7 commits
a776292
879b624
d3c4473
62e72d2
d75c6a2
58c1359
4b19498
2ff01cf
766b25a
18b2e43
6c2d612
b4d53f6
a5d2dfe
02841a1
c4c7a60
1463b67
0824247
6c4c731
6a1370a
980e240
f257ee0
8599d33
75edd8c
2ba4d15
c600a55
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,78 +1,281 @@ | ||
| import { Image, Text, View, StyleSheet } from 'react-native'; | ||
| import { InterRegular, InterSemiBold } from '../utils/webFonts'; | ||
| import { useState } from 'react'; | ||
| import { StyleSheet, Text, TouchableOpacity, View, Linking } from 'react-native'; | ||
| import { Colors } from '../utils/colors'; | ||
|
|
||
| const ReceiveIconUri = `data:image/svg+xml;utf8,<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg"> <rect width="32" height="32" rx="16" fill="#95EED8"/> <path d="M19.048 14.6186C18.7453 14.3159 18.2546 14.3159 17.952 14.6186L16.775 15.7956V9.33325C16.775 8.90523 16.428 8.55825 16 8.55825C15.572 8.55825 15.225 8.90523 15.225 9.33325V15.7956L14.048 14.6186C13.7453 14.3159 13.2546 14.3159 12.952 14.6186C12.6493 14.9212 12.6493 15.4119 12.952 15.7146L15.452 18.2146C15.7546 18.5172 16.2453 18.5172 16.548 18.2146L19.048 15.7146C19.3507 15.4119 19.3507 14.9212 19.048 14.6186ZM23.4417 15.9999C23.4417 15.5719 23.0947 15.2249 22.6667 15.2249C22.2386 15.2249 21.8917 15.5719 21.8917 15.9999C21.8917 19.2538 19.2539 21.8916 16 21.8916C12.7461 21.8916 10.1083 19.2538 10.1083 15.9999C10.1083 15.5719 9.76135 15.2249 9.33333 15.2249C8.90531 15.2249 8.55833 15.5719 8.55833 15.9999C8.55833 20.1098 11.8901 23.4416 16 23.4416C20.1099 23.4416 23.4417 20.1098 23.4417 15.9999Z" fill="#27564B" stroke="#5BBAA3" stroke-width="0.3"/> </svg> `; | ||
| import { InterMedium, InterSemiBold, InterSmall } from '../utils/webFonts'; | ||
| import { ChevronDownIcon } from 'native-base'; | ||
|
|
||
| interface ActivityLogProps { | ||
| name: string; | ||
| id: string; | ||
| creationDate: string; | ||
| nftId?: string; | ||
| transactionHash?: string; | ||
| ipfsHash?: string; | ||
| collective?: string; | ||
| owner?: string; | ||
| hash?: string; | ||
| } | ||
|
|
||
| function ActivityLog({}: ActivityLogProps) { | ||
| function ActivityLog({ | ||
| name, | ||
| creationDate, | ||
| nftId, | ||
| transactionHash, | ||
| collective, | ||
| ipfsHash, | ||
| owner, | ||
| hash, | ||
| }: ActivityLogProps) { | ||
| const [isExpanded, setIsExpanded] = useState(false); | ||
|
|
||
| const toggleExpanded = () => { | ||
| setIsExpanded(!isExpanded); | ||
| }; | ||
|
|
||
| const openExternalLink = async (url: string) => { | ||
| try { | ||
| const supported = await Linking.canOpenURL(url); | ||
| if (supported) { | ||
| await Linking.openURL(url); | ||
| } else { | ||
| console.warn(`Don't know how to open URL: ${url}`); | ||
| } | ||
| } catch (error) { | ||
| console.error('Error opening URL:', error); | ||
| } | ||
| }; | ||
|
|
||
| const handlePaymentTransactionPress = () => { | ||
| const txHash = transactionHash || hash; | ||
| if (txHash) { | ||
| openExternalLink(`https://celoscan.io/tx/${txHash}`); | ||
| } else if (owner) { | ||
| openExternalLink(`https://celoscan.io/address/${owner}`); | ||
| } | ||
| }; | ||
|
|
||
| const handleNftDetailsPress = () => { | ||
| if (nftId && collective) { | ||
| openExternalLink(`https://celoscan.io/token/${collective}?a=${nftId}`); | ||
| } else if (nftId) { | ||
| openExternalLink(`https://celoscan.io/search?q=${nftId}`); | ||
| } | ||
| }; | ||
|
|
||
| const truncatedName = name.length > 15 ? `${name.substring(0, 5)}...${name.substring(name.length - 5)}` : name; | ||
|
|
||
| const handleIpfsPress = async () => { | ||
| if (ipfsHash) { | ||
| const cleanHash = ipfsHash.replace(/^(ipfs:\/\/|https?:\/\/[^/]+\/ipfs\/)/, ''); | ||
| const gateways = [ | ||
| `https://ipfs.io/ipfs/${cleanHash}`, | ||
| `https://gateway.pinata.cloud/ipfs/${cleanHash}`, | ||
| `https://cloudflare-ipfs.com/ipfs/${cleanHash}`, | ||
| ]; | ||
|
|
||
| try { | ||
|
Comment on lines
+67
to
+87
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. issue (bug_risk): IPFS gateway fallback logic may not work as intended due to openExternalLink always resolving. Since openExternalLink only verifies if a URL can be opened, not if the resource exists, the fallback gateways may never be used even if the first is down. Consider checking resource availability with a fetch or HEAD request before attempting to open the link, or document that this is a best-effort fallback. |
||
| await openExternalLink(gateways[0]); | ||
| } catch (error) { | ||
| try { | ||
| await openExternalLink(gateways[1]); | ||
| } catch (secondError) { | ||
| console.warn('IPFS gateways failed'); | ||
| } | ||
| } | ||
| } | ||
| }; | ||
|
|
||
| const hasPaymentData = Boolean(transactionHash || hash || owner); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. question (bug_risk): hasPaymentData logic may enable Payment Transaction button even if only owner is present. Currently, the button is enabled when only owner is present, which may not correspond to a transaction. Should the button be disabled unless a transaction hash exists? |
||
|
|
||
| return ( | ||
| <View style={styles.container}> | ||
| <View style={styles.row}> | ||
| <View style={styles.bar} /> | ||
| <Image style={styles.icon} source={{ uri: ReceiveIconUri }} /> | ||
| <Text style={styles.logProfileDetails}> | ||
| <Text style={styles.name}>Silvi Tree Claim {'\n'}</Text> | ||
| <Text style={styles.id}>0x723a86c93838c1facse.....</Text> | ||
| </Text> | ||
| <View style={styles.logDate}> | ||
| <Text style={styles.date}>July 3, 2023</Text> | ||
| </View> | ||
| <View style={styles.leftBorder} /> | ||
|
|
||
| <View style={styles.contentContainer}> | ||
| <TouchableOpacity onPress={toggleExpanded} style={styles.actionRow}> | ||
| <View style={styles.leftSection}> | ||
| <View style={styles.iconContainer}> | ||
| <Text style={styles.downloadIcon}>↓</Text> | ||
| </View> | ||
|
|
||
| <View style={styles.actionInfo}> | ||
| <View style={styles.titleRow}> | ||
| <Text style={styles.actionName}>NFT ID: {truncatedName}</Text> | ||
|
|
||
| <Text style={styles.actionDate}>{creationDate}</Text> | ||
| </View> | ||
| <View style={styles.chevronContainer}> | ||
| <ChevronDownIcon | ||
| size={4} | ||
| color={Colors.gray[200]} | ||
| style={{ | ||
| transform: [{ rotate: isExpanded ? '180deg' : '0deg' }], | ||
| }} | ||
| /> | ||
| </View> | ||
| </View> | ||
| </View> | ||
| </TouchableOpacity> | ||
|
|
||
| {isExpanded && ( | ||
| <View style={styles.expandedContent}> | ||
| <TouchableOpacity | ||
| style={styles.linkButton} | ||
| onPress={handlePaymentTransactionPress} | ||
| disabled={!hasPaymentData}> | ||
| <Text style={hasPaymentData ? styles.linkText : styles.linkTextDisabled}>Payment Transaction</Text> | ||
| </TouchableOpacity> | ||
|
|
||
| <TouchableOpacity style={styles.linkButton} onPress={handleNftDetailsPress} disabled={!nftId}> | ||
| <Text style={nftId ? styles.linkText : styles.linkTextDisabled}>NFT Details</Text> | ||
| </TouchableOpacity> | ||
|
|
||
| <TouchableOpacity style={styles.linkButton} onPress={handleIpfsPress} disabled={!ipfsHash}> | ||
| <Text style={ipfsHash ? styles.linkText : styles.linkTextDisabled}> | ||
| IPFS Claim and Proof of Verification | ||
| </Text> | ||
| </TouchableOpacity> | ||
|
|
||
| <TouchableOpacity onPress={toggleExpanded} style={styles.collapseButton}> | ||
| <Text style={styles.collapseIcon}>⌃</Text> | ||
| </TouchableOpacity> | ||
| </View> | ||
| )} | ||
| </View> | ||
| </View> | ||
| ); | ||
| } | ||
|
|
||
| const styles = StyleSheet.create({ | ||
| container: { | ||
| width: '100%', | ||
| backgroundColor: Colors.white, | ||
| paddingLeft: 16, | ||
| paddingRight: 16, | ||
| borderRadius: 12, | ||
| marginBottom: 12, | ||
| shadowColor: Colors.black, | ||
| shadowOffset: { | ||
| width: 0, | ||
| height: 2, | ||
| }, | ||
| shadowOpacity: 0.1, | ||
| shadowRadius: 4, | ||
| elevation: 3, | ||
| overflow: 'hidden', | ||
| }, | ||
| row: { | ||
| width: '100%', | ||
| flex: 1, | ||
| flexDirection: 'row', | ||
| gap: 8, | ||
| backgroundColor: Colors.white, | ||
|
|
||
| contentContainer: { | ||
| paddingLeft: 8, | ||
| paddingRight: 8, | ||
| paddingBottom: 8, | ||
| paddingTop: 8, | ||
| }, | ||
| bar: { | ||
|
|
||
| leftBorder: { | ||
| position: 'absolute', | ||
| left: 0, | ||
| top: 0, | ||
| bottom: 0, | ||
| width: 4, | ||
| backgroundColor: Colors.green[100], | ||
| width: 6, | ||
| height: 40, | ||
| alignSelf: 'flex-start', | ||
| borderTopLeftRadius: 12, | ||
| borderBottomLeftRadius: 12, | ||
| zIndex: 1, | ||
| }, | ||
|
|
||
| actionRow: { | ||
| flexDirection: 'row', | ||
| alignItems: 'center', | ||
| justifyContent: 'space-between', | ||
| padding: 16, | ||
| }, | ||
|
|
||
| leftSection: { | ||
| flexDirection: 'row', | ||
| alignItems: 'center', | ||
| flex: 1, | ||
| }, | ||
| icon: { | ||
|
|
||
| iconContainer: { | ||
| width: 32, | ||
| height: 32, | ||
| borderRadius: 16, | ||
| backgroundColor: Colors.green[100], | ||
| justifyContent: 'center', | ||
| alignItems: 'center', | ||
| marginRight: 12, | ||
| }, | ||
| name: { | ||
| color: Colors.black, | ||
|
|
||
| downloadIcon: { | ||
| color: Colors.green[200], | ||
| fontSize: 18, | ||
| fontWeight: 'bold', | ||
| }, | ||
|
|
||
| actionInfo: { | ||
| flex: 1, | ||
| }, | ||
|
|
||
| titleRow: { | ||
| flexDirection: 'row', | ||
| justifyContent: 'space-between', | ||
| alignItems: 'center', | ||
| width: '100%', | ||
| }, | ||
|
|
||
| actionName: { | ||
| fontSize: 16, | ||
| lineHeight: 24, | ||
| color: Colors.black, | ||
| ...InterSemiBold, | ||
| width: '100%', | ||
| }, | ||
| date: { | ||
| color: Colors.gray[100], | ||
|
|
||
| actionDate: { | ||
| fontSize: 12, | ||
| color: Colors.gray[500], | ||
| ...InterSmall, | ||
| }, | ||
|
|
||
| chevronContainer: { | ||
| padding: 0, | ||
| display: 'flex', | ||
| alignItems: 'center', | ||
| justifyContent: 'center', | ||
| marginLeft: 0, | ||
| }, | ||
|
|
||
| expandedContent: { | ||
| borderTopWidth: 1, | ||
| borderTopColor: Colors.gray[200], | ||
| paddingVertical: 8, | ||
| }, | ||
|
|
||
| linkButton: { | ||
| backgroundColor: 'transparent', | ||
| paddingVertical: 8, | ||
| paddingHorizontal: 16, | ||
| }, | ||
|
|
||
| linkText: { | ||
| fontSize: 14, | ||
| lineHeight: 21, | ||
| textAlign: 'left', | ||
| width: '100%', | ||
| ...InterSemiBold, | ||
| color: Colors.blue[200], | ||
| textDecorationLine: 'underline', | ||
| ...InterMedium, | ||
| }, | ||
| id: { | ||
| fontSize: 10, | ||
| lineHeight: 15, | ||
| ...InterRegular, | ||
|
|
||
| linkTextDisabled: { | ||
| fontSize: 14, | ||
| color: Colors.gray[400], | ||
| ...InterMedium, | ||
| }, | ||
|
|
||
| collapseButton: { | ||
| alignSelf: 'center', | ||
| padding: 8, | ||
| marginTop: 8, | ||
| }, | ||
|
|
||
| collapseIcon: { | ||
| fontSize: 16, | ||
| color: Colors.gray[400], | ||
| }, | ||
| logProfileDetails: { flex: 1, flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }, | ||
| logDate: { alignItems: 'flex-end' }, | ||
| }); | ||
|
|
||
| export default ActivityLog; | ||
Uh oh!
There was an error while loading. Please reload this page.