Skip to content

Commit a7bcd61

Browse files
committed
feat: Delete Slashtags Profile
1 parent 136a3d4 commit a7bcd61

File tree

6 files changed

+152
-11
lines changed

6 files changed

+152
-11
lines changed

e2e/slashtags.e2e.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ d('Profile and Contacts', () => {
8080
// - receive money and attach contact to the transaction
8181
// - backup and restore wallet from the seed
8282
// - check that everything is in place
83+
// - delete profile
8384

8485
it('Can manage Slashtags Profile', async () => {
8586
if (checkComplete('slash-1')) {
@@ -281,6 +282,19 @@ d('Profile and Contacts', () => {
281282
element(by.text(satoshi.name).withAncestor(by.id('ContactSmall'))),
282283
).toBeVisible();
283284

285+
// DELETE PROFILE
286+
await element(by.id('NavigationClose')).tap();
287+
await element(by.id('Header')).tap();
288+
await element(by.id('EditButton')).tap();
289+
await element(by.id('ProfileDeleteButton')).tap();
290+
await waitFor(element(by.id('DeleteDialog')))
291+
.toBeVisible()
292+
.withTimeout(10000);
293+
await element(by.id('DialogConfirm')).tap();
294+
await expect(element(by.id('EmptyProfileHeader'))).toBeVisible();
295+
await element(by.id('Header')).tap();
296+
await expect(element(by.id('OnboardingContinue'))).toBeVisible();
297+
284298
markComplete('slash-1');
285299
});
286300
});

src/screens/Profile/ProfileEdit.tsx

Lines changed: 57 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { useTranslation } from 'react-i18next';
1313
import { ScrollView, View as ThemedView } from '../../styles/components';
1414
import { BodyS } from '../../styles/text';
1515
import { PlusIcon } from '../../styles/icons';
16+
import Dialog from '../../components/Dialog';
1617
import NavigationHeader from '../../components/NavigationHeader';
1718
import KeyboardAvoidingView from '../../components/KeyboardAvoidingView';
1819
import Button from '../../components/buttons/Button';
@@ -31,9 +32,10 @@ import { BasicProfile } from '../../store/types/slashtags';
3132
import { slashtagsLinksSelector } from '../../store/reselect/slashtags';
3233
import { onboardingProfileStepSelector } from '../../store/reselect/slashtags';
3334
import { arraysMatch } from '../../utils/helpers';
35+
import { deteleProfile, saveProfile } from '../../utils/slashtags';
36+
import { showToast } from '../../utils/notifications';
3437
import ProfileLinkNavigation from '../../navigation/bottom-sheet/ProfileLinkNavigation';
3538
import type { RootStackScreenProps } from '../../navigation/types';
36-
import { saveProfile } from '../../utils/slashtags';
3739

3840
const ProfileEdit = ({
3941
navigation,
@@ -42,7 +44,8 @@ const ProfileEdit = ({
4244
const { url, profile: slashtagsProfile } = useSlashtags();
4345
const { profile: savedProfile } = useProfile(url);
4446
const [hasEdited, setHasEdited] = useState(false);
45-
const [isSaving, setIsSaving] = useState(false);
47+
const [isLoading, setIsLoading] = useState(false);
48+
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
4649
const [fields, setFields] = useState<Omit<BasicProfile, 'links'>>({});
4750
const dispatch = useAppDispatch();
4851
const links = useAppSelector(slashtagsLinksSelector);
@@ -95,9 +98,9 @@ const ProfileEdit = ({
9598
};
9699

97100
const onSave = async (): Promise<void> => {
98-
setIsSaving(true);
101+
setIsLoading(true);
99102
const res = await saveProfile(url, profile, slashtagsProfile);
100-
setIsSaving(false);
103+
setIsLoading(false);
101104
if (res.isErr()) {
102105
return;
103106
}
@@ -110,6 +113,23 @@ const ProfileEdit = ({
110113
}
111114
};
112115

116+
const onDelete = async (): Promise<void> => {
117+
setIsLoading(true);
118+
const res = await deteleProfile(url, slashtagsProfile);
119+
setIsLoading(false);
120+
setShowDeleteDialog(false);
121+
if (res.isErr()) {
122+
return;
123+
}
124+
await Keyboard.dismiss();
125+
navigation.popToTop();
126+
showToast({
127+
type: 'success',
128+
title: t('profile_delete_success_title'),
129+
description: t('profile_delete_success_msg'),
130+
});
131+
};
132+
113133
const isValid = useCallback(() => {
114134
const isAnyLinkEmpty = links.some((link) => link.url === '');
115135

@@ -160,12 +180,27 @@ const ProfileEdit = ({
160180
<BodyS color="secondary">{t('profile_public_warn')}</BodyS>
161181

162182
{/* leave button visible over keyboard for onboarding */}
163-
<View style={onboardedProfile && styles.bottom}>
183+
<View
184+
style={[
185+
styles.buttonsContainer,
186+
onboardedProfile && styles.bottom,
187+
]}>
188+
{onboardedProfile && (
189+
<Button
190+
style={styles.button}
191+
text={t('profile_delete')}
192+
size="large"
193+
loading={isLoading}
194+
onPress={() => setShowDeleteDialog(true)}
195+
testID="ProfileDeleteButton"
196+
/>
197+
)}
198+
164199
<Button
165200
style={styles.button}
166201
text={t(onboardedProfile ? 'profile_save' : 'continue')}
167202
size="large"
168-
loading={isSaving}
203+
loading={isLoading}
169204
disabled={!hasEdited || !isValid()}
170205
onPress={onSave}
171206
testID="ProfileSaveButton"
@@ -176,6 +211,17 @@ const ProfileEdit = ({
176211
</KeyboardAvoidingView>
177212

178213
<ProfileLinkNavigation />
214+
215+
<Dialog
216+
visible={showDeleteDialog}
217+
title={t('profile_delete_dialogue_title')}
218+
description={t('profile_delete_dialogue_msg')}
219+
confirmText={t('profile_delete_dialogue_yes')}
220+
visibleTestID="DeleteDialog"
221+
onHide={(): void => setShowDeleteDialog(false)}
222+
onConfirm={onDelete}
223+
onCancel={(): void => setShowDeleteDialog(false)}
224+
/>
179225
</ThemedView>
180226
);
181227
};
@@ -198,9 +244,13 @@ const styles = StyleSheet.create({
198244
bottom: {
199245
marginTop: 'auto',
200246
},
247+
buttonsContainer: {
248+
flexDirection: 'row',
249+
marginTop: 16,
250+
gap: 16,
251+
},
201252
button: {
202253
flex: 1,
203-
marginTop: 16,
204254
},
205255
});
206256

src/screens/Wallets/Header.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ const Header = (): ReactElement => {
5050
{profile.name ? (
5151
<Title>{truncate(profile?.name, 20)}</Title>
5252
) : (
53-
<Title>{t('your_name_capital')}</Title>
53+
<Title testID="EmptyProfileHeader">{t('your_name_capital')}</Title>
5454
)}
5555
</Pressable>
5656
<View style={styles.middleColumn} />

src/store/slices/slashtags.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ export const slashtagsSlice = createSlice({
4646
deleteLink: (state, action: PayloadAction<string>) => {
4747
state.links = state.links.filter((link) => link.id !== action.payload);
4848
},
49+
deleteAllLinks: (state) => {
50+
state.links = [];
51+
},
4952
addContact: (
5053
state,
5154
action: PayloadAction<{ url: string; name: string }>,
@@ -79,6 +82,10 @@ export const slashtagsSlice = createSlice({
7982
const { id } = parse(action.payload.url);
8083
state.profilesCache[id] = action.payload.profile;
8184
},
85+
deteleProfileCache: (state, action: PayloadAction<{ url: string }>) => {
86+
const { id } = parse(action.payload.url);
87+
delete state.profilesCache[id];
88+
},
8289
resetSlashtagsState: () => initialSlashtagsState,
8390
},
8491
});
@@ -92,11 +99,13 @@ export const {
9299
addLink,
93100
editLink,
94101
deleteLink,
102+
deleteAllLinks,
95103
addContact,
96104
addContacts,
97105
deleteContact,
98106
updateLastPaidContacts,
99107
cacheProfile,
108+
deteleProfileCache,
100109
resetSlashtagsState,
101110
} = actions;
102111

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

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@
117117
"string": "Profile"
118118
},
119119
"profile_save": {
120-
"string": "Save Profile"
120+
"string": "Save"
121121
},
122122
"profile_pay_contacts": {
123123
"string": "Pay Your Contacts"
@@ -158,6 +158,24 @@
158158
"profile_edit": {
159159
"string": "Edit Profile"
160160
},
161+
"profile_delete": {
162+
"string": "Delete"
163+
},
164+
"profile_delete_dialogue_title": {
165+
"string": "Delete Profile Information?"
166+
},
167+
"profile_delete_dialogue_msg": {
168+
"string": "Are you sure you want to delete all of your Bitkit profile information?"
169+
},
170+
"profile_delete_dialogue_yes": {
171+
"string": "Yes, Delete"
172+
},
173+
"profile_delete_success_title": {
174+
"string": "Profile Deleted"
175+
},
176+
"profile_delete_success_msg": {
177+
"string": "Your Bitkit profile information has been deleted."
178+
},
161179
"offline_enable": {
162180
"string": "Enable payments with contacts*"
163181
},
@@ -236,6 +254,12 @@
236254
"error_saving_profile": {
237255
"string": "Unable To Save Profile"
238256
},
257+
"error_saving_profile": {
258+
"string": "Unable To Save Profile"
259+
},
260+
"error_deleting_profile": {
261+
"string": "Unable To Delete Profile"
262+
},
239263
"error_pay_title": {
240264
"string": "Unable To Pay Contact"
241265
},

src/utils/slashtags/index.ts

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,12 @@ import { format, parse } from '@synonymdev/slashtags-url';
66
import i18n from '../i18n';
77
import { showToast } from '../notifications';
88
import { BasicProfile, SlashPayConfig } from '../../store/types/slashtags';
9-
import { cacheProfile } from '../../store/slices/slashtags';
9+
import {
10+
cacheProfile,
11+
deleteAllLinks,
12+
deteleProfileCache,
13+
setOnboardingProfileStep,
14+
} from '../../store/slices/slashtags';
1015
import { TWalletName } from '../../store/types/wallet';
1116
import { dispatch, getSettingsStore } from '../../store/helpers';
1217
import { createLightningInvoice } from '../../store/utils/lightning';
@@ -26,6 +31,7 @@ import {
2631
getSelectedWallet,
2732
} from '../wallet';
2833
import SlashpayConfig from './slashpay';
34+
import { updateSettings } from '../../store/slices/settings';
2935

3036
/**
3137
* Handles pasting or scanning a slash:// url
@@ -61,7 +67,7 @@ export const saveProfile = async (
6167
console.log('profile saving error', e);
6268
showToast({
6369
type: 'warning',
64-
title: i18n.t('slashtags:error_saving_contact'),
70+
title: i18n.t('slashtags:error_saving_profile'),
6571
description: i18n.t('other:try_again'),
6672
});
6773
return err(e);
@@ -72,6 +78,31 @@ export const saveProfile = async (
7278
return ok('Profile saved');
7379
};
7480

81+
export const deteleProfile = async (
82+
url: string,
83+
slashtagsProfile: SlashtagsProfile,
84+
): Promise<Result<string>> => {
85+
try {
86+
await slashtagsProfile.del({ awaitRelaySync: true });
87+
await deleteSlashPayConfig();
88+
} catch (e) {
89+
console.log('profile delete error', e);
90+
showToast({
91+
type: 'warning',
92+
title: i18n.t('slashtags:error_deleting_profile'),
93+
description: i18n.t('other:try_again'),
94+
});
95+
return err(e);
96+
}
97+
98+
dispatch(deteleProfileCache({ url }));
99+
dispatch(deleteAllLinks());
100+
dispatch(setOnboardingProfileStep('Intro'));
101+
dispatch(updateSettings({ enableOfflinePayments: false }));
102+
103+
return ok('Profile deleted');
104+
};
105+
75106
const INVOICE_EXPIRY_DELTA = 60 * 60 * 24 * 7; // one week
76107

77108
export const getNewProfileUrl = (url: string, webRelayUrl: string): string => {
@@ -82,6 +113,19 @@ export const getNewProfileUrl = (url: string, webRelayUrl: string): string => {
82113
return res;
83114
};
84115

116+
const deleteSlashPayConfig = async (): Promise<Result<string>> => {
117+
try {
118+
if (!webRelayClient) {
119+
throw new Error('webRelayClient not ready yet');
120+
}
121+
const slashpay = new SlashpayConfig(webRelayClient);
122+
await slashpay.del();
123+
return ok('Deleted slashpay.json');
124+
} catch (e) {
125+
return err(e);
126+
}
127+
};
128+
85129
export const updateSlashPayConfig = debounce(
86130
async ({
87131
forceUpdate = false,

0 commit comments

Comments
 (0)