Skip to content

Commit 70b747c

Browse files
committed
fix: refactor contact add
1 parent 100941d commit 70b747c

File tree

7 files changed

+121
-60
lines changed

7 files changed

+121
-60
lines changed

.github/workflows/e2e-ios.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ jobs:
106106
- name: Restart docker before last attempt
107107
if: steps.test1.outcome != 'success' && steps.test2.outcome != 'success' && steps.test3.outcome != 'success'
108108
run: |
109-
cd docker && docker-compose down && docker-compose up -d && cd ..
109+
cd docker && docker-compose down -t 60 && docker-compose up -d && cd ..
110110
while ! nc -z '127.0.0.1' 60001; do sleep 1; done
111111
112112
- name: Test attempt 4

e2e/slashtags.e2e.js

+5-2
Original file line numberDiff line numberDiff line change
@@ -129,11 +129,13 @@ d('Profile and Contacts', () => {
129129

130130
// self
131131
await element(by.id('AddContact')).tap();
132-
await element(by.id('ContactURLInput')).typeText(slashtagsUrl + '\n');
133-
await expect(element(by.id('ContactError'))).toBeVisible();
132+
await element(by.id('ContactURLInput')).typeText(slashtagsUrl);
133+
await element(by.id('AddContactButton')).tap();
134+
await expect(element(by.id('ContactURLInput-error'))).toBeVisible();
134135

135136
// Satoshi
136137
await element(by.id('ContactURLInput')).replaceText(satoshi.url);
138+
await element(by.id('AddContactButton')).tap();
137139
await waitFor(element(by.id('NameInput')))
138140
.toBeVisible()
139141
.withTimeout(30000);
@@ -147,6 +149,7 @@ d('Profile and Contacts', () => {
147149
// Hal
148150
await element(by.id('AddContact')).tap();
149151
await element(by.id('ContactURLInput')).replaceText(hal.url);
152+
await element(by.id('AddContactButton')).tap();
150153
await waitFor(element(by.id('NameInput')))
151154
.toBeVisible()
152155
.withTimeout(30000);

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@
6262
"@synonymdev/slashtags-keychain": "1.0.0",
6363
"@synonymdev/slashtags-profile": "1.0.2",
6464
"@synonymdev/slashtags-sdk": "1.0.0-alpha.38",
65-
"@synonymdev/slashtags-url": "1.0.0-alpha.4",
65+
"@synonymdev/slashtags-url": "1.0.1",
6666
"@synonymdev/slashtags-widget-bitcoin-feed": "1.0.0",
6767
"@synonymdev/slashtags-widget-facts-feed": "1.1.0",
6868
"@synonymdev/slashtags-widget-news-feed": "1.1.0",

src/components/LabeledInput.tsx

+32-6
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import React, { ReactElement, RefObject } from 'react';
22
import { View, StyleSheet, ViewStyle, StyleProp } from 'react-native';
33
import { TextInput, BottomSheetTextInput } from '../styles/components';
4-
import { Caption13Up } from '../styles/text';
4+
import { Caption13Up, Text02S } from '../styles/text';
5+
import { IThemeColors } from '../styles/themes';
56

67
type LabeledInputProps = {
78
label: string;
9+
error?: string;
810
children?: JSX.Element | JSX.Element[];
911
ref?: RefObject<any>;
1012
autoFocus?: boolean;
@@ -17,10 +19,12 @@ type LabeledInputProps = {
1719
onChange?: (value: string) => void;
1820
maxLength?: number;
1921
testID?: string;
22+
color?: keyof IThemeColors;
2023
};
2124

2225
const LabeledInput = ({
2326
label,
27+
error,
2428
children,
2529
ref,
2630
autoFocus,
@@ -33,6 +37,7 @@ const LabeledInput = ({
3337
style,
3438
maxLength,
3539
testID,
40+
color = 'white',
3641
}: LabeledInputProps): ReactElement => {
3742
const numberOfChildren = React.Children.toArray(children).length;
3843

@@ -41,9 +46,19 @@ const LabeledInput = ({
4146

4247
return (
4348
<View style={style}>
44-
<Caption13Up color="gray1" style={styles.label}>
45-
{label}
46-
</Caption13Up>
49+
<View style={styles.header}>
50+
<Caption13Up style={styles.label} color="gray1">
51+
{label}
52+
</Caption13Up>
53+
{error && (
54+
<Text02S
55+
color="brand"
56+
style={styles.error}
57+
testID={testID ? testID + '-error' : undefined}>
58+
{error}
59+
</Text02S>
60+
)}
61+
</View>
4762
<View style={onChange ? styles.inputContainer : styles.readOnlyInput}>
4863
{bottomSheet ? (
4964
<BottomSheetTextInput
@@ -60,12 +75,12 @@ const LabeledInput = ({
6075
editable={!!onChange}
6176
returnKeyType={returnKeyType}
6277
testID={testID}
78+
color={color}
6379
/>
6480
) : (
6581
<TextInput
6682
style={textInputStyle}
6783
defaultValue={value}
68-
color="white"
6984
autoCapitalize="none"
7085
autoCorrect={false}
7186
autoFocus={autoFocus}
@@ -77,6 +92,7 @@ const LabeledInput = ({
7792
returnKeyType={returnKeyType}
7893
maxLength={maxLength}
7994
testID={testID}
95+
color={color}
8096
/>
8197
)}
8298
{children && (
@@ -97,9 +113,19 @@ const LabeledInput = ({
97113
};
98114

99115
const styles = StyleSheet.create({
100-
label: {
116+
header: {
117+
flexDirection: 'row',
118+
alignItems: 'center',
119+
justifyContent: 'space-between',
120+
flexWrap: 'wrap',
101121
marginBottom: 8,
102122
},
123+
label: {
124+
marginRight: 8,
125+
},
126+
error: {
127+
marginVertical: -1,
128+
},
103129
inputContainer: {
104130
position: 'relative',
105131
},

src/screens/Contacts/AddContact.tsx

+69-46
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,8 @@ import React, { ReactElement, useState } from 'react';
22
import { View, StyleSheet, TouchableOpacity } from 'react-native';
33
import { StackNavigationProp } from '@react-navigation/stack';
44
import Clipboard from '@react-native-clipboard/clipboard';
5-
import { useTranslation } from 'react-i18next';
6-
import { SlashURL } from '@synonymdev/slashtags-sdk';
75
import { parse } from '@synonymdev/slashtags-url';
6+
import { useTranslation } from 'react-i18next';
87

98
import { closeBottomSheet } from '../../store/actions/ui';
109
import { handleSlashtagURL } from '../../utils/slashtags';
@@ -15,10 +14,12 @@ import {
1514
useBottomSheetBackPress,
1615
useSnapPoints,
1716
} from '../../hooks/bottomSheet';
18-
import { Text01S, Text02S } from '../../styles/text';
17+
import { Text01S } from '../../styles/text';
1918
import { ClipboardTextIcon, CornersOutIcon } from '../../styles/icons';
2019
import type { RootStackParamList } from '../../navigation/types';
2120
import { useSelectedSlashtag2 } from '../../hooks/slashtags2';
21+
import Button from '../../components/Button';
22+
import SafeAreaInset from '../../components/SafeAreaInset';
2223

2324
const AddContact = ({
2425
navigation,
@@ -27,57 +28,63 @@ const AddContact = ({
2728
}): ReactElement => {
2829
const { t } = useTranslation('slashtags');
2930
const snapPoints = useSnapPoints('small');
30-
const [addContactURL, setAddContactURL] = useState('');
31-
const [error, setError] = useState<boolean | string>(false);
31+
const [url, setUrl] = useState('');
32+
const [error, setError] = useState<undefined | string>();
3233
const { url: myProfileURL } = useSelectedSlashtag2();
3334

3435
useBottomSheetBackPress('addContactModal');
3536

36-
const updateContactID = (url: string): void => {
37-
setAddContactURL(url);
38-
setError(false);
37+
const handleChangeUrl = (contactUrl: string): void => {
38+
setUrl(contactUrl);
39+
setError(undefined);
40+
};
3941

40-
if (url === '') {
42+
const handleAddContact = (contactUrl?: string): void => {
43+
contactUrl = contactUrl ?? url;
44+
setError(undefined);
45+
if (!contactUrl) {
4146
return;
4247
}
4348

4449
try {
45-
if (parse(url).id === parse(myProfileURL).id) {
50+
parse(contactUrl);
51+
} catch (e) {
52+
setError(t('contact_error_key'));
53+
return;
54+
}
55+
56+
try {
57+
if (parse(contactUrl).id === parse(myProfileURL).id) {
4658
setError(t('contact_error_yourself'));
4759
return;
4860
}
4961
} catch (e) {}
5062

51-
if (!url.startsWith('slash:')) {
52-
// Handle z32 key without slash: scheme prefix
53-
try {
54-
SlashURL.decode(url);
55-
handleSlashtagURL('slash:' + url, onError, onContact);
56-
} catch {
57-
onError();
58-
}
59-
} else {
60-
handleSlashtagURL(url, onError, onContact);
61-
}
62-
63-
function onError(): void {
63+
const onError = (): void => {
6464
setError(t('contact_error_key'));
65-
}
65+
};
6666

67-
function onContact(): void {
68-
setAddContactURL('');
67+
const onContact = (): void => {
68+
setUrl('');
6969
closeBottomSheet('addContactModal');
70-
}
70+
};
71+
72+
handleSlashtagURL(contactUrl, onError, onContact);
73+
};
74+
75+
const updateContactID = async (contactUrl: string): Promise<void> => {
76+
setUrl(contactUrl);
77+
handleAddContact(contactUrl);
7178
};
7279

73-
const pasteAddContact = async (): Promise<void> => {
74-
let url = await Clipboard.getString();
75-
url = url.trim();
76-
updateContactID(url);
80+
const handlePaste = async (): Promise<void> => {
81+
let contactUrl = await Clipboard.getString();
82+
contactUrl = contactUrl.trim();
83+
updateContactID(contactUrl);
7784
};
7885

79-
const navigateToScanner = (): void => {
80-
navigation.navigate('Scanner');
86+
const handleScanner = (): void => {
87+
navigation.navigate('Scanner', { onScan: updateContactID });
8188
};
8289

8390
return (
@@ -97,25 +104,33 @@ const AddContact = ({
97104
<LabeledInput
98105
bottomSheet={true}
99106
label={t('contact_add')}
100-
value={addContactURL}
107+
error={error}
108+
value={url}
101109
placeholder={t('contact_key_paste')}
102110
multiline={true}
103-
onChange={updateContactID}
104-
testID="ContactURLInput">
105-
<TouchableOpacity onPress={navigateToScanner}>
111+
onChange={handleChangeUrl}
112+
testID="ContactURLInput"
113+
color={error ? 'brand' : undefined}>
114+
<TouchableOpacity onPress={handleScanner}>
106115
<CornersOutIcon width={24} height={24} color="brand" />
107116
</TouchableOpacity>
108-
<TouchableOpacity onPress={pasteAddContact}>
117+
<TouchableOpacity onPress={handlePaste}>
109118
<ClipboardTextIcon width={24} height={24} color="brand" />
110119
</TouchableOpacity>
111120
</LabeledInput>
121+
</View>
112122

113-
{error && (
114-
<View style={styles.error} testID="ContactError">
115-
<Text02S color="brand">{error}</Text02S>
116-
</View>
117-
)}
123+
<View style={styles.footer}>
124+
<Button
125+
size="large"
126+
disabled={!url}
127+
style={styles.button}
128+
text={t('contact_add_button')}
129+
onPress={(): void => handleAddContact()}
130+
testID="AddContactButton"
131+
/>
118132
</View>
133+
<SafeAreaInset type="bottom" minPadding={16} />
119134
</View>
120135
</BottomSheetWrapper>
121136
);
@@ -130,10 +145,18 @@ const styles = StyleSheet.create({
130145
},
131146
addContactNote: {
132147
marginHorizontal: 16,
133-
marginBottom: 56,
148+
marginBottom: 16,
134149
},
135-
error: {
136-
marginTop: 16,
150+
footer: {
151+
paddingHorizontal: 16,
152+
alignItems: 'flex-end',
153+
justifyContent: 'flex-end',
154+
flex: 1,
155+
flexDirection: 'row',
156+
},
157+
button: {
158+
marginBottom: 16,
159+
flex: 1,
137160
},
138161
});
139162

src/utils/i18n/locales/en/slashtags.json

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"contact_add": "Add contact",
88
"contact_add_capital": "Add Contact",
99
"contact_add_explain": "Add a new contact by scanning their QR or by pasting their key below.",
10+
"contact_add_button": "Add",
1011
"contact_key_paste": "Paste a key",
1112
"contact_error_yourself": "You cannot add yourself as a contact.",
1213
"contact_error_key": "This is not a valid key.",

yarn.lock

+12-4
Original file line numberDiff line numberDiff line change
@@ -3326,10 +3326,10 @@
33263326
turbo-hash-map "^1.0.3"
33273327
ws "^8.8.1"
33283328

3329-
"@synonymdev/[email protected].0-alpha.4", "@synonymdev/slashtags-url@^1.0.0-alpha.3":
3330-
version "1.0.0-alpha.4"
3331-
resolved "https://registry.yarnpkg.com/@synonymdev/slashtags-url/-/slashtags-url-1.0.0-alpha.4.tgz#f9f47a59dc57aa5efbb2e9ad0cb2a33399fdca67"
3332-
integrity sha512-6+ac8h2MqF0H+h4km4dqmyhomvDvyXB7ny1QsvwwwPNPmyVbSyatjsnGLmEkzpNTVO7PHVCkEbDWGrZmdUVksQ==
3329+
"@synonymdev/[email protected].1":
3330+
version "1.0.1"
3331+
resolved "https://registry.yarnpkg.com/@synonymdev/slashtags-url/-/slashtags-url-1.0.1.tgz#b1d5d350e4774c1397fdd35a572be17bd93a3b36"
3332+
integrity sha512-b5cAc73itbWRur/cp9H+eW8oqamnfLBY8ji8bV0ADXTdfN0xpeR3xJDsgMV1HYkaGvCm5ZVUXioQfCUWdNoqaA==
33333333
dependencies:
33343334
b4a "^1.6.0"
33353335
z32 "^1.0.0"
@@ -3350,6 +3350,14 @@
33503350
b4a "^1.6.0"
33513351
z32 "^1.0.0"
33523352

3353+
"@synonymdev/slashtags-url@^1.0.0-alpha.3":
3354+
version "1.0.0-alpha.4"
3355+
resolved "https://registry.yarnpkg.com/@synonymdev/slashtags-url/-/slashtags-url-1.0.0-alpha.4.tgz#f9f47a59dc57aa5efbb2e9ad0cb2a33399fdca67"
3356+
integrity sha512-6+ac8h2MqF0H+h4km4dqmyhomvDvyXB7ny1QsvwwwPNPmyVbSyatjsnGLmEkzpNTVO7PHVCkEbDWGrZmdUVksQ==
3357+
dependencies:
3358+
b4a "^1.6.0"
3359+
z32 "^1.0.0"
3360+
33533361
"@synonymdev/[email protected]":
33543362
version "1.0.0"
33553363
resolved "https://registry.yarnpkg.com/@synonymdev/slashtags-widget-bitcoin-feed/-/slashtags-widget-bitcoin-feed-1.0.0.tgz#5486a6629ef08a9bdb9563a3861dc465f8961906"

0 commit comments

Comments
 (0)