diff --git a/JoyboyCommunity/src/app/Router.tsx b/JoyboyCommunity/src/app/Router.tsx
index 18f8ace7..3d5408c0 100644
--- a/JoyboyCommunity/src/app/Router.tsx
+++ b/JoyboyCommunity/src/app/Router.tsx
@@ -7,6 +7,7 @@ import {StyleSheet, View} from 'react-native';
import {Icon} from '../components';
import {useStyles, useTheme} from '../hooks';
import {CreateAccount} from '../screens/Auth/CreateAccount';
+import {ImportKeys} from '../screens/Auth/ImportKeys';
import {Login} from '../screens/Auth/Login';
import {SaveKeys} from '../screens/Auth/SaveKeys';
import {CreatePost} from '../screens/CreatePost';
@@ -117,6 +118,7 @@ const AuthNavigator: React.FC = () => {
{publicKey && }
+
);
};
diff --git a/JoyboyCommunity/src/screens/Auth/CreateAccount.tsx b/JoyboyCommunity/src/screens/Auth/CreateAccount.tsx
index 758fb667..96fc4904 100644
--- a/JoyboyCommunity/src/screens/Auth/CreateAccount.tsx
+++ b/JoyboyCommunity/src/screens/Auth/CreateAccount.tsx
@@ -4,7 +4,7 @@ import {useState} from 'react';
import {Platform} from 'react-native';
import {LockIcon} from '../../assets/icons';
-import {Button, Input} from '../../components';
+import {Button, Input, TextButton} from '../../components';
import {useNostrContext} from '../../context/NostrContext';
import {useTheme} from '../../hooks';
import {useDialog, useToast} from '../../hooks/modals';
@@ -70,6 +70,10 @@ export const CreateAccount: React.FC = ({navigatio
navigation.navigate('SaveKeys', {privateKey, publicKey});
};
+ const handleImportKey = () => {
+ navigation.navigate('ImportKeys');
+ };
+
return (
@@ -90,6 +94,8 @@ export const CreateAccount: React.FC = ({navigatio
>
Create Account
+
+ Import account
);
};
diff --git a/JoyboyCommunity/src/screens/Auth/ImportKeys.tsx b/JoyboyCommunity/src/screens/Auth/ImportKeys.tsx
new file mode 100644
index 00000000..7932f5f7
--- /dev/null
+++ b/JoyboyCommunity/src/screens/Auth/ImportKeys.tsx
@@ -0,0 +1,95 @@
+import {canUseBiometricAuthentication} from 'expo-secure-store';
+import {useState} from 'react';
+import {Platform} from 'react-native';
+
+import {LockIcon} from '../../assets/icons';
+import {Button, Input} from '../../components';
+import {useTheme} from '../../hooks';
+import {useDialog, useToast} from '../../hooks/modals';
+import {Auth} from '../../modules/Auth';
+import {AuthImportKeysScreenProps} from '../../types';
+import {getPublicKeyFromSecret, isValidNostrPrivateKey} from '../../utils/keypair';
+import {storePassword, storePrivateKey, storePublicKey} from '../../utils/storage';
+
+export const ImportKeys: React.FC = ({navigation}) => {
+ const {theme} = useTheme();
+
+ const [password, setPassword] = useState('');
+ const [privateKey, setPrivateKey] = useState('');
+ const {showToast} = useToast();
+ const {showDialog, hideDialog} = useDialog();
+
+ const handleImportAccount = async () => {
+ if (!password) {
+ showToast({type: 'error', title: 'Password is required'});
+ return;
+ }
+
+ if (!privateKey) {
+ showToast({type: 'error', title: 'Private key to import is required'});
+ return;
+ }
+
+ if (!isValidNostrPrivateKey(privateKey)) {
+ showToast({type: 'error', title: 'Private key not valid'});
+ return;
+ }
+ await storePrivateKey(privateKey, password);
+ const publicKey = getPublicKeyFromSecret(privateKey);
+ await storePublicKey(publicKey);
+
+ const biometySupported = Platform.OS !== 'web' && canUseBiometricAuthentication();
+ if (biometySupported) {
+ showDialog({
+ title: 'Easy login',
+ description: 'Would you like to use biometrics to login?',
+ buttons: [
+ {
+ type: 'primary',
+ label: 'Yes',
+ onPress: async () => {
+ await storePassword(password);
+ hideDialog();
+ },
+ },
+ {
+ type: 'default',
+ label: 'No',
+ onPress: hideDialog,
+ },
+ ],
+ });
+ }
+
+ navigation.navigate('SaveKeys', {privateKey, publicKey});
+ };
+
+ return (
+
+ }
+ value={privateKey}
+ onChangeText={setPrivateKey}
+ secureTextEntry
+ placeholder="Private key"
+ />
+
+ }
+ value={password}
+ onChangeText={setPassword}
+ secureTextEntry
+ placeholder="Password"
+ />
+
+
+
+ );
+};
diff --git a/JoyboyCommunity/src/screens/Auth/Login.tsx b/JoyboyCommunity/src/screens/Auth/Login.tsx
index 81b2041e..6bb5bd45 100644
--- a/JoyboyCommunity/src/screens/Auth/Login.tsx
+++ b/JoyboyCommunity/src/screens/Auth/Login.tsx
@@ -79,6 +79,25 @@ export const Login: React.FC = ({navigation}) => {
});
};
+ const handleImportAccount = () => {
+ showDialog({
+ title: 'WARNING',
+ description:
+ 'Creating a new account will delete your current account. Are you sure you want to continue?',
+ buttons: [
+ {
+ type: 'primary',
+ label: 'Continue',
+ onPress: () => {
+ navigation.navigate('ImportKeys');
+ hideDialog();
+ },
+ },
+ {type: 'default', label: 'Cancel', onPress: hideDialog},
+ ],
+ });
+ };
+
return (
= ({navigation}) => {
-
Create Account
+
+ Import Account
);
};
diff --git a/JoyboyCommunity/src/types/routes.ts b/JoyboyCommunity/src/types/routes.ts
index cabd19b8..68225b2c 100644
--- a/JoyboyCommunity/src/types/routes.ts
+++ b/JoyboyCommunity/src/types/routes.ts
@@ -14,6 +14,7 @@ export type AuthStackParams = {
privateKey: string;
publicKey: string;
};
+ ImportKeys: undefined;
};
export type MainStackParams = {
@@ -49,6 +50,11 @@ export type AuthSaveKeysScreenProps = CompositeScreenProps<
NativeStackScreenProps
>;
+export type AuthImportKeysScreenProps = CompositeScreenProps<
+ NativeStackScreenProps,
+ NativeStackScreenProps
+>;
+
// Home Stack
export type HomeNavigationProp = NativeStackNavigationProp;
diff --git a/JoyboyCommunity/src/utils/keypair.ts b/JoyboyCommunity/src/utils/keypair.ts
index 34a39d83..3a6818a0 100644
--- a/JoyboyCommunity/src/utils/keypair.ts
+++ b/JoyboyCommunity/src/utils/keypair.ts
@@ -28,3 +28,18 @@ export const getPublicKeyFromSecret = (privateKey: string) => {
throw new Error('Failed to get public key from secret key');
}
};
+
+export const isValidNostrPrivateKey = (key: string): boolean => {
+ // Check if the string is exactly 64 characters long
+ if (key.length !== 64) {
+ return false;
+ }
+
+ // Check if the string contains only hexadecimal characters
+ const hexRegex = /^[0-9a-fA-F]+$/;
+ if (!hexRegex.test(key)) {
+ return false;
+ }
+
+ return true;
+};