Skip to content

Commit

Permalink
Delete account from inside of wallet.
Browse files Browse the repository at this point in the history
Fixes #56

Change wording of confirmation dialog. Fixes #64
  • Loading branch information
jasny committed May 26, 2023
1 parent 25fa932 commit e7cedff
Show file tree
Hide file tree
Showing 6 changed files with 91 additions and 39 deletions.
22 changes: 13 additions & 9 deletions src/components/ConfirmationDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,19 @@ import { ButtonContainer } from './styles/ConfirmationDialog.styles'

export default function ConfirmationDialog(props: {
visible: boolean,
message: string,
message?: string,
onCancel: () => void,
onPress: () => void,
titleLabel?: string,
cancelButtonLabel?: string,
continueButtonLabel?: string
continueButtonLabel?: string,
danger: boolean,
children?: React.ReactNode
}): JSX.Element {

const titleLabel = props.titleLabel || 'Confirm:'
const cancelButtonLabel = props.cancelButtonLabel || 'Cancel'
const continueButtonLabel = props.continueButtonLabel || 'Continue'
const cancelButtonLabel = props.cancelButtonLabel || 'Abort'
const continueButtonLabel = props.continueButtonLabel || 'Confirm'

return (
<Portal>
Expand All @@ -25,15 +27,17 @@ export default function ConfirmationDialog(props: {
onDismiss={props.onCancel}
>
<Dialog.Title testID='dialog'>{titleLabel}</Dialog.Title>
<Dialog.Content>
<Paragraph style={{ textAlign: 'justify' }}>{props.message}</Paragraph>
</Dialog.Content>
<Dialog.Content>{
props.message
? <Paragraph style={{ textAlign: 'justify' }}>{props.message}</Paragraph>
: props.children
}</Dialog.Content>
<ButtonContainer>
<Dialog.Actions>
<Button uppercase={false} testID='cancel' onPress={props.onCancel}>{cancelButtonLabel}</Button>
<Button color="#666" uppercase={false} testID='cancel' onPress={props.onCancel}>{cancelButtonLabel}</Button>
</Dialog.Actions>
<Dialog.Actions>
<Button uppercase={false} testID='continue' onPress={props.onPress}>{continueButtonLabel}</Button>
<Button color={props.danger ? 'red' : ''} uppercase={false} testID='continue' onPress={props.onPress}>{continueButtonLabel}</Button>
</Dialog.Actions>
</ButtonContainer>

Expand Down
8 changes: 4 additions & 4 deletions src/components/styles/StyledButton.styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ export const StyledButton = styled(Button)`
height: 40px;
width: 290px;
border-radius: 20px;
justify-content: flex-end;
justify-content: center;
align-items: center;
font-size: 16px;
font-weight: 800;
${props => props.mode === `contained` && props.disabled === false
? `background-color: #A017B7; border-color: #A017B7; border-width: 1px; color: #ffffff;`
: `border-width: 1px; color: #A017B7; border-color: #A017B7;`};
? `background-color: ${props.color || '#A017B7'}; border-color: ${props.color || '#A017B7'}; border-width: 1px; color: #ffffff;`
: `border-width: 1px; color: ${props.color || '#A017B7'}; border-color: ${props.color || '#A017B7'};`};
${props => props.disabled === true && `border-color: transparent;`};
`
`
4 changes: 4 additions & 0 deletions src/constants/Text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,10 @@ export const PROFILE = {
PHRASE: 'Backup Phrase (Seed)',
DISCOVER_PRIVATEKEY: 'Show private key',
DISCOVER_PHRASE: 'Show backup phrase',
DELETE_ACCOUNT: 'Delete account',
DELETE_ACCOUNT_LABEL: 'Are you sure you want to delete your account?',
DELETE_ACCOUNT_MESSAGE: 'You\'re about to delete your account. This action is irreversible and may result in a loss of funds.',
DELETE_ACCOUNT_MESSAGE_2: 'Make sure you have a backup of your seed phrase before proceeding.',
}

export const LOCKED_SCREEN = {
Expand Down
4 changes: 2 additions & 2 deletions src/navigation/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -130,11 +130,11 @@ function RootNavigator(): any {
<Stack.Screen
name='Profile'
component={ProfileScreen}
options={{
options={({ navigation }: RootStackScreenProps<'Profile'>) => ({
headerBackTitleVisible: false,
headerTitle: 'MY ACCOUNT',
headerTitleStyle: { fontSize: 20, color: '#000000' },
}}
})}
/>
</Stack.Group>
<Stack.Screen
Expand Down
41 changes: 38 additions & 3 deletions src/screens/ProfileScreen/ProfileScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
import React, { useEffect, useState } from 'react'
import { TouchableOpacity } from 'react-native'
import { Card } from 'react-native-paper'
import {TouchableOpacity, View} from 'react-native'
import {Card, Paragraph, Text} from 'react-native-paper'
import Spinner from '../../components/Spinner'
import { PROFILE } from '../../constants/Text'
import LocalStorageService from '../../services/LocalStorage.service'
import { CardsContainer, Content, Field, HiddenTitle, MainCard } from './ProfileScreen.styles'
import PressToCopy from "../../components/PressToCopy";
import LTOService from "../../services/LTO.service";
import {StyledButton} from "../../components/styles/StyledButton.styles";
import ConfirmationDialog from "../../components/ConfirmationDialog";
import {RootStackScreenProps} from "../../../types";

export default function ProfileScreen() {
export default function ProfileScreen({ navigation }: RootStackScreenProps<'Profile'>) {

const [isLoading, setIsLoading] = useState<boolean>(true)
const [accountInformation, setAccountInformation] = useState(Object.create(null))
const [isKeyBlur, setIsKeyBlur] = useState<boolean>(true)
const [isSeedBlur, setIsSeedBlur] = useState<boolean>(true)
const [accountNickname, setAccountNickname] = useState<string>("")
const [showConfirmDelete, setShowConfirmDelete] = useState<boolean>(false)

const { address, publicKey, privateKey, seed } = accountInformation

Expand All @@ -32,6 +36,16 @@ export default function ProfileScreen() {
}
}, [])

const deleteAccount = () => {
setShowConfirmDelete(false)
LTOService.deleteAccount().then(() => {
navigation.reset({
index: 0,
routes: [{ name: 'SignUp' }],
})
})
}

const readStorage = () => {
LTOService.getAccount()
.then(account => {
Expand Down Expand Up @@ -111,8 +125,29 @@ export default function ProfileScreen() {
</MainCard>
</PressToCopy>
}

<StyledButton
mode='text'
color='red'
uppercase={false}
labelStyle={{ fontWeight: '400', fontSize: 16, width: '100%' }}
onPress={() => setShowConfirmDelete(true)}
>
{PROFILE.DELETE_ACCOUNT}
</StyledButton>
</CardsContainer>
}
<ConfirmationDialog
danger={true}
titleLabel={PROFILE.DELETE_ACCOUNT_LABEL}
continueButtonLabel={PROFILE.DELETE_ACCOUNT}
visible={showConfirmDelete}
onCancel={() => setShowConfirmDelete(false)}
onPress={() => deleteAccount()}
>
<Paragraph style={{ textAlign: 'justify', marginBottom: 10 }}>{PROFILE.DELETE_ACCOUNT_MESSAGE}</Paragraph>
<Paragraph style={{ textAlign: 'justify', fontWeight: 'bold' }}>{PROFILE.DELETE_ACCOUNT_MESSAGE_2}</Paragraph>
</ConfirmationDialog>
</>
)
}
51 changes: 30 additions & 21 deletions src/services/LTO.service.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { Account, CancelLease, Lease, LTO, Transaction } from "@ltonetwork/lto"
import LocalStorageService from "./LocalStorage.service"
import { TypedTransaction } from "../interfaces/TypedTransaction"

export const lto = new LTO(process.env.LTO_NETWORK_ID)
if (process.env.LTO_API_URL) lto.nodeAddress = process.env.LTO_API_URL

export default class LTOService {
static account?: Account

public static isUnlocked = (): boolean => {
return !!LTOService.account
return !!this.account
}

public static unlock = async (password: string | undefined, signature?: string) => {
Expand All @@ -19,64 +20,72 @@ export default class LTOService {
const seed = encryptedAccount.seed[seedIndex]

if (signature) {
LTOService.account = lto.account({ seedPassword: encodedSignature, seed })
this.account = lto.account({ seedPassword: encodedSignature, seed })
} else {
LTOService.account = lto.account({ seedPassword: password, seed })
this.account = lto.account({ seedPassword: password, seed })
}
}

public static lock = () => {
delete LTOService.account
delete this.account
}

public static getAccount = async (): Promise<Account> => {
if (!LTOService.account) {
if (!this.account) {
throw new Error("Not logged in")
}

return LTOService.account
return this.account
}

public static storeAccount = async (nickname: string, password: string, signature?: string) => {
if (!LTOService.account) {
if (!this.account) {
throw new Error("Account not created")
}
const encodedSignature = signature && encodeURIComponent(signature)

const encryptedWithSignature = encodedSignature ? LTOService.account.encryptSeed(encodedSignature) : undefined
const encryptedWithPassword = LTOService.account.encryptSeed(password)
const encryptedWithSignature = encodedSignature ? this.account.encryptSeed(encodedSignature) : undefined
const encryptedWithPassword = this.account.encryptSeed(password)


await LocalStorageService.storeData('@accountData', [{
nickname: nickname,
address: LTOService.account.address,
address: this.account.address,
seed: [encryptedWithSignature, encryptedWithPassword],
}])
}

public static createAccount = async () => {
try {
LTOService.account = lto.account()
this.account = lto.account()
} catch (error) {
throw new Error('Error creating account')
}
}

public static importAccount = async (seed: string) => {
try {
LTOService.account = lto.account({ seed: seed })
this.account = lto.account({ seed: seed })
} catch (error) {
throw new Error('Error importing account from seeds')
}
}

public static deleteAccount = async () => {
await Promise.all([
LocalStorageService.removeData('@accountData'),
LocalStorageService.removeData('@userAlias')
])
this.lock()
}

private static apiUrl = (path: string): string => {
return lto.nodeAddress.replace(/\/$/g, '') + path
}

public static getBalance = async (address: string) => {
try {
const url = LTOService.apiUrl(`/addresses/balance/details/${address}`)
const url = this.apiUrl(`/addresses/balance/details/${address}`)
const response = await fetch(url)
return response.json()
} catch (error) {
Expand All @@ -85,7 +94,7 @@ export default class LTOService {
}

public static getTransactions = async (address: string, limit?: number, page = 1) => {
const pending = await LTOService.getPendingTransactions(address)
const pending = await this.getPendingTransactions(address)

let offset
if (!limit) {
Expand All @@ -101,12 +110,12 @@ export default class LTOService {

return ([] as TypedTransaction[]).concat(
pending.slice(limit * (page - 1), limit),
limit > 0 ? await LTOService.getProcessedTransactions(address, limit, offset) : []
limit > 0 ? await this.getProcessedTransactions(address, limit, offset) : []
)
}

private static getPendingTransactions = async (address: string) => {
const url = LTOService.apiUrl(`/transactions/unconfirmed`)
const url = this.apiUrl(`/transactions/unconfirmed`)
const response = await fetch(url)
const utx: TypedTransaction[] = await response.json()

Expand All @@ -117,7 +126,7 @@ export default class LTOService {
}

private static getProcessedTransactions = async (address: string, limit = 100, offset = 0) => {
const url = LTOService.apiUrl(`/transactions/address/${address}?limit=${limit}&offset=${offset}`)
const url = this.apiUrl(`/transactions/address/${address}?limit=${limit}&offset=${offset}`)
const response = await fetch(url)
const [txs] = await response.json()

Expand All @@ -126,8 +135,8 @@ export default class LTOService {

public static getLeases = async (address: string) => {
const [pending, active] = await Promise.all([
LTOService.getPendingTransactions(address),
LTOService.getActiveLeases(address)
this.getPendingTransactions(address),
this.getActiveLeases(address)
])
const leases = [...pending.filter(tx => tx.type === Lease.TYPE), ...active]

Expand All @@ -140,14 +149,14 @@ export default class LTOService {
}

private static getActiveLeases = async (address: string) => {
const url = LTOService.apiUrl(`/leasing/active/${address}`)
const url = this.apiUrl(`/leasing/active/${address}`)

const response = await fetch(url)
return response.json()
}

public static broadcast = async (transaction: Transaction) => {
const url = LTOService.apiUrl('/transactions/broadcast')
const url = this.apiUrl('/transactions/broadcast')
const response = await fetch(url, {
method: 'POST',
headers: {
Expand Down

0 comments on commit e7cedff

Please sign in to comment.