diff --git a/.gitignore b/.gitignore index f0fb5338..7588c24f 100644 --- a/.gitignore +++ b/.gitignore @@ -73,6 +73,10 @@ rust/.cargo/config.toml android/*.hprof android/app/src/main/jniLibs +# Sentry +android/sentry.properties +ios/sentry.properties + # react-native-config codegen ios/envfile diff --git a/android/sentry.properties b/android/sentry.properties deleted file mode 100644 index e7f3c5ce..00000000 --- a/android/sentry.properties +++ /dev/null @@ -1,4 +0,0 @@ -defaults.url=https://sentry.io/ -defaults.org=psychedelic -defaults.project=plug-mobile -auth.token=4e8aa37713c74bd39ccea5ebb54f5c30953411da65014645b8b5f53208b64f4c diff --git a/ios/Podfile.lock b/ios/Podfile.lock index d83df11d..d9a80e15 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -312,6 +312,8 @@ PODS: - react-native-config/App (= 1.4.6) - react-native-config/App (1.4.6): - React-Core + - react-native-document-picker (8.1.1): + - React-Core - react-native-file-access (2.5.0): - React-Core - ZIPFoundation (= 0.9.11) @@ -526,6 +528,7 @@ DEPENDENCIES: - React-logger (from `../node_modules/react-native/ReactCommon/logger`) - "react-native-blur (from `../node_modules/@react-native-community/blur`)" - react-native-config (from `../node_modules/react-native-config`) + - react-native-document-picker (from `../node_modules/react-native-document-picker`) - react-native-file-access (from `../node_modules/react-native-file-access`) - react-native-minimizer (from `../node_modules/react-native-minimizer`) - react-native-pager-view (from `../node_modules/react-native-pager-view`) @@ -638,6 +641,8 @@ EXTERNAL SOURCES: :path: "../node_modules/@react-native-community/blur" react-native-config: :path: "../node_modules/react-native-config" + react-native-document-picker: + :path: "../node_modules/react-native-document-picker" react-native-file-access: :path: "../node_modules/react-native-file-access" react-native-minimizer: @@ -761,6 +766,7 @@ SPEC CHECKSUMS: React-logger: ebb4d31bbbe4f1a8a1a9b658d7429210b8f68160 react-native-blur: 3e9c8e8e9f7d17fa1b94e1a0ae9fd816675f5382 react-native-config: 7cd105e71d903104e8919261480858940a6b9c0e + react-native-document-picker: f68191637788994baed5f57d12994aa32cf8bf88 react-native-file-access: 204693d308701ba232dcf91facef05498fcde9a2 react-native-minimizer: b94809a769ac3825b46fd081d4f0ae2560791536 react-native-pager-view: 7f00d63688f7df9fad86dfb0154814419cc5eb8d diff --git a/ios/sentry.properties b/ios/sentry.properties deleted file mode 100644 index e7f3c5ce..00000000 --- a/ios/sentry.properties +++ /dev/null @@ -1,4 +0,0 @@ -defaults.url=https://sentry.io/ -defaults.org=psychedelic -defaults.project=plug-mobile -auth.token=4e8aa37713c74bd39ccea5ebb54f5c30953411da65014645b8b5f53208b64f4c diff --git a/package.json b/package.json index 65728972..8ceb7ec2 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "@dfinity/principal": "^0.9.3", "@hookform/error-message": "^2.0.0", "@psychedelic/dab-js": "1.4.12", - "@psychedelic/plug-controller": "0.23.2", + "@psychedelic/plug-controller": "0.23.4", "@react-native-async-storage/async-storage": "^1.17.10", "@react-native-community/blur": "^4.2.0", "@react-native-community/clipboard": "^1.5.1", @@ -77,6 +77,7 @@ "react-native-crypto": "^2.2.0", "react-native-crypto-js": "^1.0.0", "react-native-device-info": "^10.2.0", + "react-native-document-picker": "^8.1.1", "react-native-fast-image": "^8.6.1", "react-native-fetch-api": "^2.0.0", "react-native-file-access": "^2.5.0", diff --git a/src/components/common/GradientText/index.js b/src/components/common/GradientText/index.tsx similarity index 54% rename from src/components/common/GradientText/index.js rename to src/components/common/GradientText/index.tsx index 1fd8a31b..4fdf4c43 100644 --- a/src/components/common/GradientText/index.js +++ b/src/components/common/GradientText/index.tsx @@ -2,19 +2,30 @@ import MaskedView from '@react-native-masked-view/masked-view'; import React from 'react'; import LinearGradient from 'react-native-linear-gradient'; -import Text from '../Text'; +import Text, { TextProps } from '../Text'; -const GradientText = ({ colors, style, ...props }) => { +interface Coordinate { + x: number; + y: number; +} + +interface Props extends TextProps { + colors: string[]; + start?: Coordinate; + end?: Coordinate; +} + +function GradientText({ colors, style, start, end, ...props }: Props) { return ( }> + start={start || { x: 0, y: 0 }} + end={end || { x: 1, y: 1 }}> ); -}; +} export default GradientText; diff --git a/src/components/common/Text/index.tsx b/src/components/common/Text/index.tsx index e4ae2357..2e677f4d 100644 --- a/src/components/common/Text/index.tsx +++ b/src/components/common/Text/index.tsx @@ -1,5 +1,5 @@ import React, { useMemo } from 'react'; -import { Text as RNText, TextProps } from 'react-native'; +import { Text as RNText, TextProps as RNTextProps } from 'react-native'; export type TextTypes = | 'headline1' @@ -14,13 +14,13 @@ export type TextTypes = | 'overline' | 'normal'; -interface Props extends TextProps { +export interface TextProps extends RNTextProps { type?: TextTypes; } import styles from './styles'; -function Text({ style, type, ...props }: Props) { +function Text({ style, type, ...props }: TextProps) { const baseStyle = useMemo( () => (type ? [styles.base, styles[type], style] : [styles.base, style]), [type, style] diff --git a/src/components/common/Text/styles.ts b/src/components/common/Text/styles.ts index a82dbcf8..a02722e5 100644 --- a/src/components/common/Text/styles.ts +++ b/src/components/common/Text/styles.ts @@ -15,4 +15,5 @@ export default StyleSheet.create({ caption: FontStyles.Caption, overline: FontStyles.Overline, normal: FontStyles.Normal, + error: FontStyles.Error, }); diff --git a/src/redux/slices/keyring.ts b/src/redux/slices/keyring.ts index b900dc65..2ab4bf71 100644 --- a/src/redux/slices/keyring.ts +++ b/src/redux/slices/keyring.ts @@ -1,5 +1,5 @@ import { CreatePrincipalOptions } from '@psychedelic/plug-controller/dist/PlugKeyRing/interfaces'; -import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; +import { createAsyncThunk, createSlice, isAnyOf } from '@reduxjs/toolkit'; import { KeyringState, State, Wallet } from '@/interfaces/redux'; import KeyRing from '@/modules/keyring'; @@ -200,6 +200,58 @@ export const login = createAsyncThunk( } ); +export const importAccountFromPem = createAsyncThunk( + 'keyring/importAccountFromPem', + async ( + { pem, icon, name }: { name: string; icon: string; pem: string }, + { rejectWithValue } + ) => { + try { + const instance = KeyRing.getInstance(); + const response = await instance?.importAccountFromPem({ + pem, + icon, + name, + }); + return response; + } catch (e: any) { + // TODO: Add toast to handle error. + return rejectWithValue(e.message); + } + } +); + +export const validatePem = createAsyncThunk( + 'keyring/validatePem', + async ( + { + pem, + onSuccess, + onFailure, + onFinish, + }: { + pem: string; + onSuccess: () => void; + onFailure: () => void; + onFinish: () => void; + }, + { rejectWithValue } + ) => { + try { + const instance = KeyRing.getInstance(); + const response = await instance?.validatePem({ pem }); + if (response) { + onSuccess(); + } else { + onFailure(); + } + onFinish(); + } catch (e: any) { + return rejectWithValue({ error: e.message }); + } + } +); + export const createSubaccount = createAsyncThunk( 'keyring/createSubaccount', async (params: CreatePrincipalOptions, { rejectWithValue }) => { @@ -356,11 +408,6 @@ export const keyringSlice = createSlice({ state.isInitialized = action.payload.isInitialized; state.isUnlocked = action.payload.isUnlocked; }) - .addCase(createSubaccount.fulfilled, (state, action) => { - if (action.payload) { - state.wallets = [...state.wallets, action.payload]; - } - }) .addCase(editSubaccount.fulfilled, (state, action) => { const { isCurrentWallet, wallet } = action.payload; if (isCurrentWallet) { @@ -397,7 +444,15 @@ export const keyringSlice = createSlice({ state.isInitialized = true; state.isUnlocked = unlocked; } - }); + }) + .addMatcher( + isAnyOf(createSubaccount.fulfilled, importAccountFromPem.fulfilled), + (state, action) => { + if (action.payload) { + state.wallets = [...state.wallets, action.payload]; + } + } + ); }, }); diff --git a/src/screens/auth/ImportSeedPhrase/index.js b/src/screens/auth/ImportSeedPhrase/index.js index 19da3f66..4450f928 100644 --- a/src/screens/auth/ImportSeedPhrase/index.js +++ b/src/screens/auth/ImportSeedPhrase/index.js @@ -88,7 +88,7 @@ const ImportSeedPhrase = ({ navigation, route }) => { /> - {t('importSeedPhrase.title')} + {t('common.importWallet')} {t('importSeedPhrase.enterPhrase')} diff --git a/src/screens/auth/Welcome/index.js b/src/screens/auth/Welcome/index.js index f9740bb8..748ecad5 100644 --- a/src/screens/auth/Welcome/index.js +++ b/src/screens/auth/Welcome/index.js @@ -31,7 +31,7 @@ function Welcome() { const title = isInitialized ? t('welcome.initTitle') : t('welcome.title'); const importTitle = isInitialized ? t('welcome.importNew') - : t('welcome.import'); + : t('common.importWallet'); const createTitle = isInitialized ? t('welcome.createNew') : t('welcome.create'); diff --git a/src/screens/flows/WalletConnect/screens/Flows/components/BatchTransactions/index.tsx b/src/screens/flows/WalletConnect/screens/Flows/components/BatchTransactions/index.tsx index 9e47b257..5f2a3a31 100644 --- a/src/screens/flows/WalletConnect/screens/Flows/components/BatchTransactions/index.tsx +++ b/src/screens/flows/WalletConnect/screens/Flows/components/BatchTransactions/index.tsx @@ -47,7 +47,6 @@ function BatchTransactions({ request, metadata }: WallectConnectFlowsData) { {transactions.map(renderTransaction)} diff --git a/src/screens/flows/WalletConnect/screens/Flows/components/BatchTransactions/styles.ts b/src/screens/flows/WalletConnect/screens/Flows/components/BatchTransactions/styles.ts index d4588c03..2f84aacc 100644 --- a/src/screens/flows/WalletConnect/screens/Flows/components/BatchTransactions/styles.ts +++ b/src/screens/flows/WalletConnect/screens/Flows/components/BatchTransactions/styles.ts @@ -5,10 +5,10 @@ import { Colors } from '@/constants/theme'; export default StyleSheet.create({ listContentContainer: { paddingVertical: 10, - flexGrow: 1, }, list: { width: '100%', + flex: 1, }, appIcon: { height: 28, diff --git a/src/screens/tabs/Profile/screens/Accounts/index.js b/src/screens/tabs/Profile/screens/Accounts/index.js index e27aa760..b5669906 100644 --- a/src/screens/tabs/Profile/screens/Accounts/index.js +++ b/src/screens/tabs/Profile/screens/Accounts/index.js @@ -22,6 +22,7 @@ import { setCurrentPrincipal } from '@/redux/slices/keyring'; import shortAddress from '@/utils/shortAddress'; import CreateEditAccount from '../CreateEditAccount'; +import CreateImportAccount from '../CreateImportAccount'; import AddICNS from './AddICNS'; import styles from './styles'; @@ -39,15 +40,16 @@ const Accounts = ({ modalRef, onClose, ...props }) => { const [selectedAccount, setSelectedAccount] = useState(null); const createEditAccountRef = useRef(null); + const createImportAccountRef = useRef(null); const addICNSRef = useRef(null); useEffect(() => { dispatch(getICPPrice()); }, []); - const onCreateAccount = () => { + const onCreateImportAccount = () => { setSelectedAccount(null); - createEditAccountRef.current?.open(); + createImportAccountRef.current?.open(); }; const onEditAccount = account => { @@ -155,11 +157,11 @@ const Accounts = ({ modalRef, onClose, ...props }) => { )} {wallets?.map(renderAccountItem)} - + - {t('accounts.createAccount')} + {t('accounts.createImportAccount')} @@ -168,6 +170,10 @@ const Accounts = ({ modalRef, onClose, ...props }) => { accountsModalRef={modalRef} account={selectedAccount} /> + { +interface Props { + modalRef: RefObject; + accountsModalRef?: RefObject; + account?: Wallet; + pem?: string; + createImportModalRef?: RefObject; +} + +const CreateEditAccount = ({ + modalRef, + account, + accountsModalRef, + pem, + createImportModalRef, +}: Props) => { const { t } = useTranslation(); - const editEmojiRef = useRef(null); + const editEmojiRef = useRef(null); const [accountName, setAccountName] = useState(''); const [editTouched, setEditTouched] = useState(false); const [emoji, setEmoji] = useState(''); + const dispatch = useAppDispatch(); - const { onCreate, onEdit } = useAccounts(); + const title = account ? t('accounts.edit') : t('accounts.create'); + const nameAndIcon = { + name: accountName, + icon: emoji, + }; const onPress = () => { account - ? onEdit({ - walletId: account.walletId, - name: accountName, - icon: emoji, - }) - : onCreate({ - name: accountName, - icon: emoji, - }); + ? dispatch( + editSubaccount({ + walletId: account.walletId, + ...nameAndIcon, + }) + ) + : pem + ? dispatch( + importAccountFromPem({ + ...nameAndIcon, + pem, + }) + ) + : dispatch(createSubaccount(nameAndIcon)); resetState(); + if (createImportModalRef) { + createImportModalRef.current?.close(); + } modalRef.current?.close(); }; @@ -46,35 +81,25 @@ const CreateEditAccount = ({ modalRef, account, accountsModalRef }) => { const onEditEmoji = () => { Keyboard.dismiss(); - editEmojiRef?.current.open(); + editEmojiRef?.current?.open(); setEditTouched(true); }; - const getName = useCallback( - isSave => - isSave - ? t('accounts.save') - : account - ? t('accounts.edit') - : t('accounts.create'), - [account] - ); - useEffect(() => { if (account) { setAccountName(account.name); - setEmoji(account.icon); + setEmoji(account.icon!); } else { resetState(); } }, [account]); const closeModal = () => { - accountsModalRef?.current.close(); + accountsModalRef?.current?.close(); }; const handleBack = () => { - modalRef?.current.close(); + modalRef?.current?.close(); }; const handleClose = () => { @@ -86,17 +111,9 @@ const CreateEditAccount = ({ modalRef, account, accountsModalRef }) => { return (
- {t('common.close')} - - } - left={ - - {t('common.back')} - - } - center={{getName()}} + right={} + left={} + center={{title}} /> @@ -122,11 +139,7 @@ const CreateEditAccount = ({ modalRef, account, accountsModalRef }) => { style={styles.input} onChangeText={setAccountName} /> - + diff --git a/src/screens/tabs/Profile/screens/CreateEditAccount/styles.js b/src/screens/tabs/Profile/screens/CreateEditAccount/styles.ts similarity index 92% rename from src/screens/tabs/Profile/screens/CreateEditAccount/styles.js rename to src/screens/tabs/Profile/screens/CreateEditAccount/styles.ts index de83dee6..9aebfbe7 100644 --- a/src/screens/tabs/Profile/screens/CreateEditAccount/styles.js +++ b/src/screens/tabs/Profile/screens/CreateEditAccount/styles.ts @@ -29,7 +29,4 @@ export default StyleSheet.create({ toolTipText: { color: Colors.Black.Pure, }, - valid: { - color: Colors.ActionBlue, - }, }); diff --git a/src/screens/tabs/Profile/screens/CreateImportAccount/assets/createIcon.png b/src/screens/tabs/Profile/screens/CreateImportAccount/assets/createIcon.png new file mode 100644 index 00000000..bff24ff0 Binary files /dev/null and b/src/screens/tabs/Profile/screens/CreateImportAccount/assets/createIcon.png differ diff --git a/src/screens/tabs/Profile/screens/CreateImportAccount/assets/createIcon@2x.png b/src/screens/tabs/Profile/screens/CreateImportAccount/assets/createIcon@2x.png new file mode 100644 index 00000000..98078aaa Binary files /dev/null and b/src/screens/tabs/Profile/screens/CreateImportAccount/assets/createIcon@2x.png differ diff --git a/src/screens/tabs/Profile/screens/CreateImportAccount/assets/createIcon@3x.png b/src/screens/tabs/Profile/screens/CreateImportAccount/assets/createIcon@3x.png new file mode 100644 index 00000000..3186590a Binary files /dev/null and b/src/screens/tabs/Profile/screens/CreateImportAccount/assets/createIcon@3x.png differ diff --git a/src/screens/tabs/Profile/screens/CreateImportAccount/assets/pemFileIcon.png b/src/screens/tabs/Profile/screens/CreateImportAccount/assets/pemFileIcon.png new file mode 100644 index 00000000..a8005529 Binary files /dev/null and b/src/screens/tabs/Profile/screens/CreateImportAccount/assets/pemFileIcon.png differ diff --git a/src/screens/tabs/Profile/screens/CreateImportAccount/assets/pemFileIcon@2x.png b/src/screens/tabs/Profile/screens/CreateImportAccount/assets/pemFileIcon@2x.png new file mode 100644 index 00000000..e0b97ea7 Binary files /dev/null and b/src/screens/tabs/Profile/screens/CreateImportAccount/assets/pemFileIcon@2x.png differ diff --git a/src/screens/tabs/Profile/screens/CreateImportAccount/assets/pemFileIcon@3x.png b/src/screens/tabs/Profile/screens/CreateImportAccount/assets/pemFileIcon@3x.png new file mode 100644 index 00000000..546a2d98 Binary files /dev/null and b/src/screens/tabs/Profile/screens/CreateImportAccount/assets/pemFileIcon@3x.png differ diff --git a/src/screens/tabs/Profile/screens/CreateImportAccount/assets/privateKeyIcon.png b/src/screens/tabs/Profile/screens/CreateImportAccount/assets/privateKeyIcon.png new file mode 100644 index 00000000..02ec1354 Binary files /dev/null and b/src/screens/tabs/Profile/screens/CreateImportAccount/assets/privateKeyIcon.png differ diff --git a/src/screens/tabs/Profile/screens/CreateImportAccount/assets/privateKeyIcon@2x.png b/src/screens/tabs/Profile/screens/CreateImportAccount/assets/privateKeyIcon@2x.png new file mode 100644 index 00000000..6926a467 Binary files /dev/null and b/src/screens/tabs/Profile/screens/CreateImportAccount/assets/privateKeyIcon@2x.png differ diff --git a/src/screens/tabs/Profile/screens/CreateImportAccount/assets/privateKeyIcon@3x.png b/src/screens/tabs/Profile/screens/CreateImportAccount/assets/privateKeyIcon@3x.png new file mode 100644 index 00000000..d0cec186 Binary files /dev/null and b/src/screens/tabs/Profile/screens/CreateImportAccount/assets/privateKeyIcon@3x.png differ diff --git a/src/screens/tabs/Profile/screens/CreateImportAccount/index.tsx b/src/screens/tabs/Profile/screens/CreateImportAccount/index.tsx new file mode 100644 index 00000000..f7d64227 --- /dev/null +++ b/src/screens/tabs/Profile/screens/CreateImportAccount/index.tsx @@ -0,0 +1,104 @@ +import { t } from 'i18next'; +import React, { RefObject, useRef } from 'react'; +import { Image, TouchableOpacity, View } from 'react-native'; +import DocumentPicker from 'react-native-document-picker'; +import { FileSystem } from 'react-native-file-access'; +import { Modalize } from 'react-native-modalize'; + +import { + ActionButton, + GradientText, + Header, + Modal, + Text, +} from '@/components/common'; +import { isIos } from '@/constants/platform'; +import { useStateWithCallback } from '@/hooks/useStateWithCallback'; + +import CreateAccount from '../CreateEditAccount'; +import ImportKey from '../ImportKey'; +import styles from './styles'; +import { Button, getCreateImportButtons } from './utils'; + +interface Props { + modalRef: RefObject; + accountsModalRef: RefObject; +} + +function CreateImportAccount({ accountsModalRef, modalRef }: Props) { + const [pemFile, setPemFile] = useStateWithCallback(''); + const createAccountRef = useRef(null); + const importKeyRef = useRef(null); + + const closeModal = () => { + accountsModalRef?.current?.close(); + }; + + const handleBack = () => { + modalRef?.current?.close(); + }; + + const openCreateAccountModal = () => { + createAccountRef?.current?.open(); + }; + + const openImportKeyModal = () => { + importKeyRef?.current?.open(); + }; + + const openFile = async () => { + try { + const type = isIos + ? 'public.x509-certificate' + : ['.pem', 'application/x-pem-file']; + const res = await DocumentPicker.pickSingle({ type }); + + const stringifyPEM = await FileSystem.readFile(res.uri); + setPemFile(stringifyPEM, openCreateAccountModal); + } catch (e) { + // TODO: Add toast to handle this error. + console.log('Error opening .pem'); + } + }; + + const renderButton = ({ id, title, onPress, icon, colors }: Button) => ( + + + + {title} + + + ); + + const buttons = getCreateImportButtons({ + openCreateAccountModal, + openFile, + openImportKeyModal, + }); + + return ( + +
} + left={} + center={ + {t('accounts.createImportAccount')} + } + /> + {buttons.map(renderButton)} + + + + ); +} + +export default CreateImportAccount; diff --git a/src/screens/tabs/Profile/screens/CreateImportAccount/styles.ts b/src/screens/tabs/Profile/screens/CreateImportAccount/styles.ts new file mode 100644 index 00000000..7eb307a9 --- /dev/null +++ b/src/screens/tabs/Profile/screens/CreateImportAccount/styles.ts @@ -0,0 +1,14 @@ +import { StyleSheet } from 'react-native'; + +export default StyleSheet.create({ + container: { + flex: 1, + paddingTop: 10, + paddingBottom: 30, + flexDirection: 'row', + justifyContent: 'space-evenly', + }, + button: { + alignItems: 'center', + }, +}); diff --git a/src/screens/tabs/Profile/screens/CreateImportAccount/utils.ts b/src/screens/tabs/Profile/screens/CreateImportAccount/utils.ts new file mode 100644 index 00000000..491bca99 --- /dev/null +++ b/src/screens/tabs/Profile/screens/CreateImportAccount/utils.ts @@ -0,0 +1,47 @@ +import { t } from 'i18next'; +import { ImageSourcePropType } from 'react-native'; + +import createAccountIcon from './assets/createIcon.png'; +import pemFileIcon from './assets/pemFileIcon.png'; +import privateKeyIcon from './assets/privateKeyIcon.png'; + +export interface Button { + id: string; + title: string; + onPress: () => void; + icon: ImageSourcePropType; + colors: string[]; +} + +export const getCreateImportButtons = ({ + openCreateAccountModal, + openFile, + openImportKeyModal, +}: { + openCreateAccountModal: () => void; + openFile: () => void; + openImportKeyModal: () => void; +}) => + [ + { + id: 'create', + title: t('createImportAccount.create'), + onPress: openCreateAccountModal, + icon: createAccountIcon, + colors: ['#00E8FF', '#44DC45'], + }, + { + id: 'importKey', + title: t('createImportAccount.importKey'), + onPress: openImportKeyModal, + icon: privateKeyIcon, + colors: ['#FB5DC3', '#FDB943'], + }, + { + id: 'importPem', + title: t('createImportAccount.importPem'), + onPress: openFile, + icon: pemFileIcon, + colors: ['#36C3E9', '#CF6ED3'], + }, + ] as Button[]; diff --git a/src/screens/tabs/Profile/screens/ImportKey/index.tsx b/src/screens/tabs/Profile/screens/ImportKey/index.tsx new file mode 100644 index 00000000..ed3255e5 --- /dev/null +++ b/src/screens/tabs/Profile/screens/ImportKey/index.tsx @@ -0,0 +1,100 @@ +import { t } from 'i18next'; +import React, { RefObject, useRef, useState } from 'react'; +import { View } from 'react-native'; +import { Modalize } from 'react-native-modalize'; + +import RainbowButton from '@/components/buttons/RainbowButton'; +import { + ActionButton, + Header, + Modal, + Text, + TextInput, +} from '@/components/common'; +import { useAppDispatch } from '@/redux/hooks'; +import { validatePem } from '@/redux/slices/keyring'; + +import CreateEditAccount from '../CreateEditAccount'; +import styles from './styles'; + +interface Props { + modalRef: RefObject; + createImportRef: RefObject; + accountsModalRef: RefObject; +} + +function ImportKey({ createImportRef, modalRef, accountsModalRef }: Props) { + const createEditAccount = useRef(null); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(false); + const [key, setKey] = useState(''); + const dispatch = useAppDispatch(); + const disabled = key === '' || loading || error; + + const handleOnChangeKey = (value: string) => { + if (error) { + setError(false); + } + setKey(value); + }; + + const closeModal = () => { + createImportRef?.current?.close(); + accountsModalRef?.current?.close(); + }; + + const handleBack = () => { + modalRef?.current?.close(); + }; + + const handleContinue = () => { + setLoading(true); + dispatch( + validatePem({ + pem: key, + onSuccess: () => createEditAccount.current?.open(), + onFailure: () => setError(true), + onFinish: () => setLoading(false), + }) + ); + }; + + return ( + +
} + left={} + center={{t('common.importWallet')}} + /> + + + {error && ( + + {t('createImportAccount.invalidKey')} + + )} + + + + + ); +} + +export default ImportKey; diff --git a/src/screens/tabs/Profile/screens/ImportKey/styles.ts b/src/screens/tabs/Profile/screens/ImportKey/styles.ts new file mode 100644 index 00000000..9972aef0 --- /dev/null +++ b/src/screens/tabs/Profile/screens/ImportKey/styles.ts @@ -0,0 +1,21 @@ +import { StyleSheet } from 'react-native'; + +import { Colors } from '@/constants/theme'; + +export default StyleSheet.create({ + container: { + flex: 1, + paddingHorizontal: 24, + paddingVertical: 32, + }, + inputStyle: { + width: '100%', + marginBottom: 8, + }, + button: { + marginTop: 16, + }, + error: { + color: Colors.Red, + }, +}); diff --git a/src/translations/en/index.js b/src/translations/en/index.js index 12e1b31e..5b195096 100644 --- a/src/translations/en/index.js +++ b/src/translations/en/index.js @@ -30,13 +30,13 @@ const translations = { questionMark: '?', the: 'The', enterPassword: 'Enter Password', + importWallet: 'Import Wallet', }, welcome: { title: 'Welcome to Plug', initTitle: 'Choose an option', create: 'Create Wallet', createNew: 'Create New Wallet', - import: 'Import Wallet', importNew: 'Import New Wallet', }, login: { @@ -46,7 +46,6 @@ const translations = { moreOptions: 'More options', }, importSeedPhrase: { - title: 'Import Wallet', enterPhrase: 'Please enter your 12 word Secret Recovery Phrase.', secretPhrase: 'Secret Recovery Phrase', notFound: 'Recovery phrase not found', @@ -167,7 +166,7 @@ const translations = { }, accounts: { title: 'Accounts', - createAccount: 'Create account', + createImportAccount: 'Create/Import Account', moreOptions: { edit: 'Edit Account', addIcns: 'Add ICNS Domain', @@ -188,7 +187,6 @@ const translations = { editEmoji: 'Edit emoji', editButton: '👈 Edit', accountNamePlaceholder: 'Account name', - save: 'Save Account', edit: 'Edit Account', create: 'Create Account', }, @@ -306,6 +304,12 @@ const translations = { 'You can always recover your accounts through your Secret recovery Phrase given that your accounts exists on the blockchain.', action: 'Delete Wallet', }, + createImportAccount: { + importKey: 'Private Key', + importPem: 'PEM File', + create: 'Create', + invalidKey: 'Invalid key. Please, try again.', + }, }; export default translations; diff --git a/yarn.lock b/yarn.lock index 98337785..b8783d1e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1777,10 +1777,10 @@ cross-fetch "^3.1.4" crypto-js "^4.1.1" -"@psychedelic/plug-controller@0.23.2": - version "0.23.2" - resolved "https://npm.pkg.github.com/download/@Psychedelic/plug-controller/0.23.2/1a7c8dccd0edc69d448c37317caf7e2c04f5a3bb#1a7c8dccd0edc69d448c37317caf7e2c04f5a3bb" - integrity sha512-sfShtvoIdotShcP9tOfGuB9a7XEs7qmRlQ8EbAUKdIs7wFQyLNqXOkqxCyUjbD4EsC5dtFiMo5dpZ0xGfv71Sg== +"@psychedelic/plug-controller@0.23.4": + version "0.23.4" + resolved "https://npm.pkg.github.com/download/@Psychedelic/plug-controller/0.23.4/92ba5923065d37ea322cfc758861da0694959cfa#92ba5923065d37ea322cfc758861da0694959cfa" + integrity sha512-fwdlVPZidcHFt6x+o5OXKSyJi/VA7htkpZJ6UcU6WromEsBenaHKuRPdfJgJzRpY5AMWckgLKf2HVPL1fHxxJg== dependencies: "@dfinity/agent" "0.9.3" "@dfinity/candid" "0.9.3" @@ -8867,6 +8867,13 @@ react-native-device-info@^10.2.0: resolved "https://registry.yarnpkg.com/react-native-device-info/-/react-native-device-info-10.2.0.tgz#be17a1b86cabbe53f4adde96b662204784d3cefd" integrity sha512-7VycjFGaLKIZLDwCxSK5cWZdDOGqz3QxdGF57B/a4bW3gkrUFa5Gf0v7fUR9YL/VBJM0UwDiDH7pR9hkMQt+vw== +react-native-document-picker@^8.1.1: + version "8.1.1" + resolved "https://registry.yarnpkg.com/react-native-document-picker/-/react-native-document-picker-8.1.1.tgz#642bbe25752cc428b96416318f8dc07cef29ee10" + integrity sha512-mH0oghd7ndgU9/1meVJdqts1sAkOfUQW1qbrqTTsvR5f2K9r0BAj/X02dve5IBMOMZvlGd7qWrNVuIFg5AUXWg== + dependencies: + invariant "^2.2.4" + react-native-fast-image@^8.6.1: version "8.6.1" resolved "https://registry.yarnpkg.com/react-native-fast-image/-/react-native-fast-image-8.6.1.tgz#6a3a11b8ebc7629919265d33a420a04d13c897e0"