From 1875d32fba97fc9c0ad4467a409f1988f80dcb37 Mon Sep 17 00:00:00 2001
From: MSG <59928086+MSghais@users.noreply.github.com>
Date: Mon, 22 Jul 2024 18:35:32 +0200
Subject: [PATCH 1/3] Import account with a Nostr Private key (#234)
* import account screen in login
* add nav in create account + yarn format
* yarn format
* yarn lint
* change request
---
JoyboyCommunity/src/app/Router.tsx | 2 +
.../src/screens/Auth/CreateAccount.tsx | 8 +-
.../src/screens/Auth/ImportKeys.tsx | 95 +++++++++++++++++++
JoyboyCommunity/src/screens/Auth/Login.tsx | 22 ++++-
JoyboyCommunity/src/types/routes.ts | 6 ++
JoyboyCommunity/src/utils/keypair.ts | 15 +++
6 files changed, 146 insertions(+), 2 deletions(-)
create mode 100644 JoyboyCommunity/src/screens/Auth/ImportKeys.tsx
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;
+};
From e31f2679ab21479e1372331c18848b4bbb8677cc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?U=C4=9Fur=20Eren?=
Date: Tue, 23 Jul 2024 06:27:07 +0300
Subject: [PATCH 2/3] Fix: follow & like & comment issues (#246)
* fix: follow & like & comment
* fix: follow created_at
---
JoyboyCommunity/src/hooks/nostr/useContacts.ts | 2 +-
JoyboyCommunity/src/hooks/nostr/useEditContacts.ts | 5 +++++
JoyboyCommunity/src/hooks/nostr/useNote.ts | 2 +-
JoyboyCommunity/src/hooks/nostr/useProfile.ts | 2 +-
JoyboyCommunity/src/hooks/nostr/useReactions.ts | 2 +-
JoyboyCommunity/src/hooks/nostr/useReplyNotes.ts | 2 +-
JoyboyCommunity/src/hooks/nostr/useReposts.ts | 2 +-
JoyboyCommunity/src/hooks/nostr/useRootNotes.ts | 2 +-
JoyboyCommunity/src/screens/Profile/Info/index.tsx | 12 +++++++++---
9 files changed, 21 insertions(+), 10 deletions(-)
diff --git a/JoyboyCommunity/src/hooks/nostr/useContacts.ts b/JoyboyCommunity/src/hooks/nostr/useContacts.ts
index edd8aa29..86d99b22 100644
--- a/JoyboyCommunity/src/hooks/nostr/useContacts.ts
+++ b/JoyboyCommunity/src/hooks/nostr/useContacts.ts
@@ -12,7 +12,7 @@ export const useContacts = (options?: UseContactsOptions) => {
const {ndk} = useNostrContext();
return useQuery({
- queryKey: ['contacts', ndk, options?.authors, options?.search],
+ queryKey: ['contacts', options?.authors, options?.search, ndk],
queryFn: async () => {
const contacts = await ndk.fetchEvent({
kinds: [NDKKind.Contacts],
diff --git a/JoyboyCommunity/src/hooks/nostr/useEditContacts.ts b/JoyboyCommunity/src/hooks/nostr/useEditContacts.ts
index 03418572..8b38d15b 100644
--- a/JoyboyCommunity/src/hooks/nostr/useEditContacts.ts
+++ b/JoyboyCommunity/src/hooks/nostr/useEditContacts.ts
@@ -23,12 +23,17 @@ export const useEditContacts = () => {
contacts.tags = [];
}
+ // Resetting the id and created_at to avoid conflicts
+ contacts.id = undefined as any;
+ contacts.created_at = undefined;
+
if (data.type === 'add') {
contacts.tags.push(['p', data.pubkey, '', '']);
} else {
contacts.tags = contacts.tags.filter((tag) => tag[1] !== data.pubkey);
}
+ await contacts.sign();
return contacts.publish();
},
});
diff --git a/JoyboyCommunity/src/hooks/nostr/useNote.ts b/JoyboyCommunity/src/hooks/nostr/useNote.ts
index b5272761..bdacc1f7 100644
--- a/JoyboyCommunity/src/hooks/nostr/useNote.ts
+++ b/JoyboyCommunity/src/hooks/nostr/useNote.ts
@@ -11,7 +11,7 @@ export const useNote = (options: UseNoteOptions) => {
const {ndk} = useNostrContext();
return useQuery({
- queryKey: ['note', ndk, options.noteId],
+ queryKey: ['note', options.noteId, ndk],
queryFn: async () => {
const note = await ndk.fetchEvent({
kinds: [NDKKind.Text],
diff --git a/JoyboyCommunity/src/hooks/nostr/useProfile.ts b/JoyboyCommunity/src/hooks/nostr/useProfile.ts
index 3df26734..3b2110d0 100644
--- a/JoyboyCommunity/src/hooks/nostr/useProfile.ts
+++ b/JoyboyCommunity/src/hooks/nostr/useProfile.ts
@@ -10,7 +10,7 @@ export const useProfile = (options: UseProfileOptions) => {
const {ndk} = useNostrContext();
return useQuery({
- queryKey: ['profile', ndk, options.publicKey],
+ queryKey: ['profile', options.publicKey, ndk],
queryFn: async () => {
const user = ndk.getUser({pubkey: options.publicKey});
diff --git a/JoyboyCommunity/src/hooks/nostr/useReactions.ts b/JoyboyCommunity/src/hooks/nostr/useReactions.ts
index c5d1c5ba..d58dee34 100644
--- a/JoyboyCommunity/src/hooks/nostr/useReactions.ts
+++ b/JoyboyCommunity/src/hooks/nostr/useReactions.ts
@@ -13,7 +13,7 @@ export const useReactions = (options?: UseReactionsOptions) => {
const {ndk} = useNostrContext();
return useQuery({
- queryKey: ['reactions', ndk, options?.noteId, options?.authors, options?.search],
+ queryKey: ['reactions', options?.noteId, options?.authors, options?.search, ndk],
queryFn: async () => {
const notes = await ndk.fetchEvents({
kinds: [NDKKind.Reaction],
diff --git a/JoyboyCommunity/src/hooks/nostr/useReplyNotes.ts b/JoyboyCommunity/src/hooks/nostr/useReplyNotes.ts
index a3195fa3..6fed31f2 100644
--- a/JoyboyCommunity/src/hooks/nostr/useReplyNotes.ts
+++ b/JoyboyCommunity/src/hooks/nostr/useReplyNotes.ts
@@ -14,7 +14,7 @@ export const useReplyNotes = (options?: UseReplyNotesOptions) => {
return useInfiniteQuery({
initialPageParam: 0,
- queryKey: ['replyNotes', ndk, options?.noteId, options?.authors, options?.search],
+ queryKey: ['replyNotes', options?.noteId, options?.authors, options?.search, ndk],
getNextPageParam: (lastPage: any, allPages, lastPageParam) => {
if (!lastPage?.length) return undefined;
diff --git a/JoyboyCommunity/src/hooks/nostr/useReposts.ts b/JoyboyCommunity/src/hooks/nostr/useReposts.ts
index b11b2c0e..1320a52d 100644
--- a/JoyboyCommunity/src/hooks/nostr/useReposts.ts
+++ b/JoyboyCommunity/src/hooks/nostr/useReposts.ts
@@ -13,7 +13,7 @@ export const useReposts = (options?: UseRepostsOptions) => {
return useInfiniteQuery({
initialPageParam: 0,
- queryKey: ['reposts', ndk, options?.authors, options?.search],
+ queryKey: ['reposts', options?.authors, options?.search, ndk],
getNextPageParam: (lastPage: any, allPages, lastPageParam) => {
if (!lastPage?.length) return undefined;
diff --git a/JoyboyCommunity/src/hooks/nostr/useRootNotes.ts b/JoyboyCommunity/src/hooks/nostr/useRootNotes.ts
index 1e687b53..c04b2343 100644
--- a/JoyboyCommunity/src/hooks/nostr/useRootNotes.ts
+++ b/JoyboyCommunity/src/hooks/nostr/useRootNotes.ts
@@ -13,7 +13,7 @@ export const useRootNotes = (options?: UseRootNotesOptions) => {
return useInfiniteQuery({
initialPageParam: 0,
- queryKey: ['rootNotes', ndk, options?.authors, options?.search],
+ queryKey: ['rootNotes', options?.authors, options?.search, ndk],
getNextPageParam: (lastPage: any, allPages, lastPageParam) => {
if (!lastPage?.length) return undefined;
diff --git a/JoyboyCommunity/src/screens/Profile/Info/index.tsx b/JoyboyCommunity/src/screens/Profile/Info/index.tsx
index d750cb9e..d5a9ae12 100644
--- a/JoyboyCommunity/src/screens/Profile/Info/index.tsx
+++ b/JoyboyCommunity/src/screens/Profile/Info/index.tsx
@@ -6,6 +6,7 @@ import {Pressable, View} from 'react-native';
import {UserPlusIcon} from '../../../assets/icons';
import {Button, IconButton, Menu, Text} from '../../../components';
import {useContacts, useEditContacts, useProfile, useStyles, useTheme} from '../../../hooks';
+import {useToast} from '../../../hooks/modals';
import {useAuth} from '../../../store/auth';
import {ProfileScreenProps} from '../../../types';
import {ProfileHead} from '../Head';
@@ -26,6 +27,7 @@ export const ProfileInfo: React.FC = ({publicKey: userPublicKe
const [menuOpen, setMenuOpen] = useState(false);
const publicKey = useAuth((state) => state.publicKey);
+ const {showToast} = useToast();
const queryClient = useQueryClient();
const userContacts = useContacts({authors: [userPublicKey]});
const contacts = useContacts({authors: [publicKey]});
@@ -40,12 +42,16 @@ export const ProfileInfo: React.FC = ({publicKey: userPublicKe
const onConnectionPress = () => {
editContacts.mutateAsync(
- {pubkey: publicKey, type: isConnected ? 'remove' : 'add'},
+ {pubkey: userPublicKey, type: isConnected ? 'remove' : 'add'},
{
onSuccess: () => {
queryClient.invalidateQueries({queryKey: ['contacts']});
- userContacts.refetch();
- contacts.refetch();
+ },
+ onError: () => {
+ showToast({
+ type: 'error',
+ title: isConnected ? 'Failed to unfollow user' : 'Failed to follow user',
+ });
},
},
);
From bcf4927afdd7f04bae24fabbdb7d566d4f7cd3c8 Mon Sep 17 00:00:00 2001
From: Lau Chaves
Date: Mon, 22 Jul 2024 21:58:27 -0600
Subject: [PATCH 3/3] Fix: claim button and menu diviver theme colors (#230)
Co-authored-by: lauchaves
---
JoyboyCommunity/src/components/Menu/styles.ts | 2 +-
JoyboyCommunity/src/styles/Colors.tsx | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/JoyboyCommunity/src/components/Menu/styles.ts b/JoyboyCommunity/src/components/Menu/styles.ts
index 2fd7b51a..3f2ae86f 100644
--- a/JoyboyCommunity/src/components/Menu/styles.ts
+++ b/JoyboyCommunity/src/components/Menu/styles.ts
@@ -28,7 +28,7 @@ export default ThemedStyleSheet((theme) => ({
position: 'absolute',
height: 'auto',
gap: StyleSheet.hairlineWidth,
- backgroundColor: theme.colors.background,
+ backgroundColor: theme.colors.divider,
borderRadius: 16,
overflow: 'hidden',
shadowColor: theme.colors.shadow,
diff --git a/JoyboyCommunity/src/styles/Colors.tsx b/JoyboyCommunity/src/styles/Colors.tsx
index 8e75336f..86793313 100644
--- a/JoyboyCommunity/src/styles/Colors.tsx
+++ b/JoyboyCommunity/src/styles/Colors.tsx
@@ -81,7 +81,7 @@ export const DarkTheme = {
textSecondary: '#FFFFFF',
textLight: '#FFFFFF',
textStrong: '#FFFFFF',
- onPrimary: '#8F979E',
+ onPrimary: '#FFFFFF',
onSecondary: '#FFFFFF',
divider: '#1b1b18',