Skip to content

Commit

Permalink
Merge branch 'main' into fix/tip_profile_modal
Browse files Browse the repository at this point in the history
  • Loading branch information
ugur-eren authored Jul 23, 2024
2 parents 8769376 + bcf4927 commit ee68be8
Show file tree
Hide file tree
Showing 17 changed files with 169 additions and 15 deletions.
2 changes: 2 additions & 0 deletions JoyboyCommunity/src/app/Router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -117,6 +118,7 @@ const AuthNavigator: React.FC = () => {
{publicKey && <AuthStack.Screen name="Login" component={Login} />}
<AuthStack.Screen name="CreateAccount" component={CreateAccount} />
<AuthStack.Screen name="SaveKeys" component={SaveKeys} />
<AuthStack.Screen name="ImportKeys" component={ImportKeys} />
</AuthStack.Navigator>
);
};
Expand Down
2 changes: 1 addition & 1 deletion JoyboyCommunity/src/components/Menu/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion JoyboyCommunity/src/hooks/nostr/useContacts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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],
Expand Down
5 changes: 5 additions & 0 deletions JoyboyCommunity/src/hooks/nostr/useEditContacts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
},
});
Expand Down
2 changes: 1 addition & 1 deletion JoyboyCommunity/src/hooks/nostr/useNote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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],
Expand Down
2 changes: 1 addition & 1 deletion JoyboyCommunity/src/hooks/nostr/useProfile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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});

Expand Down
2 changes: 1 addition & 1 deletion JoyboyCommunity/src/hooks/nostr/useReactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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],
Expand Down
2 changes: 1 addition & 1 deletion JoyboyCommunity/src/hooks/nostr/useReplyNotes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
2 changes: 1 addition & 1 deletion JoyboyCommunity/src/hooks/nostr/useReposts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
2 changes: 1 addition & 1 deletion JoyboyCommunity/src/hooks/nostr/useRootNotes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
8 changes: 7 additions & 1 deletion JoyboyCommunity/src/screens/Auth/CreateAccount.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -70,6 +70,10 @@ export const CreateAccount: React.FC<AuthCreateAccountScreenProps> = ({navigatio
navigation.navigate('SaveKeys', {privateKey, publicKey});
};

const handleImportKey = () => {
navigation.navigate('ImportKeys');
};

return (
<Auth title="Create Account">
<Input placeholder="@ Username" value={username} onChangeText={setUsername} />
Expand All @@ -90,6 +94,8 @@ export const CreateAccount: React.FC<AuthCreateAccountScreenProps> = ({navigatio
>
Create Account
</Button>

<TextButton onPress={handleImportKey}>Import account</TextButton>
</Auth>
);
};
95 changes: 95 additions & 0 deletions JoyboyCommunity/src/screens/Auth/ImportKeys.tsx
Original file line number Diff line number Diff line change
@@ -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<AuthImportKeysScreenProps> = ({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 (
<Auth title="ImportKeys">
<Input
left={<LockIcon color={theme.colors.primary} />}
value={privateKey}
onChangeText={setPrivateKey}
secureTextEntry
placeholder="Private key"
/>

<Input
left={<LockIcon color={theme.colors.primary} />}
value={password}
onChangeText={setPassword}
secureTextEntry
placeholder="Password"
/>

<Button
block
variant="secondary"
disabled={!password || !privateKey}
onPress={handleImportAccount}
>
Import Account
</Button>
</Auth>
);
};
22 changes: 21 additions & 1 deletion JoyboyCommunity/src/screens/Auth/Login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,25 @@ export const Login: React.FC<AuthLoginScreenProps> = ({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 (
<Auth title="Login">
<Input
Expand All @@ -92,8 +111,9 @@ export const Login: React.FC<AuthLoginScreenProps> = ({navigation}) => {
<Button block variant="secondary" disabled={!password?.length} onPress={handleLogin}>
Login
</Button>

<TextButton onPress={handleCreateAccount}>Create Account</TextButton>

<TextButton onPress={handleImportAccount}>Import Account</TextButton>
</Auth>
);
};
13 changes: 9 additions & 4 deletions JoyboyCommunity/src/screens/Profile/Info/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +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 {useTipModal} from '../../../hooks/modals';
import {useTipModal, useToast} from '../../../hooks/modals';
import {useAuth} from '../../../store/auth';
import {ProfileScreenProps} from '../../../types';
import {ProfileHead} from '../Head';
Expand All @@ -27,6 +27,7 @@ export const ProfileInfo: React.FC<ProfileInfoProps> = ({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]});
Expand All @@ -42,12 +43,16 @@ export const ProfileInfo: React.FC<ProfileInfoProps> = ({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',
});
},
},
);
Expand Down
2 changes: 1 addition & 1 deletion JoyboyCommunity/src/styles/Colors.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export const DarkTheme = {
textSecondary: '#FFFFFF',
textLight: '#FFFFFF',
textStrong: '#FFFFFF',
onPrimary: '#8F979E',
onPrimary: '#FFFFFF',
onSecondary: '#FFFFFF',

divider: '#1b1b18',
Expand Down
6 changes: 6 additions & 0 deletions JoyboyCommunity/src/types/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export type AuthStackParams = {
privateKey: string;
publicKey: string;
};
ImportKeys: undefined;
};

export type MainStackParams = {
Expand Down Expand Up @@ -49,6 +50,11 @@ export type AuthSaveKeysScreenProps = CompositeScreenProps<
NativeStackScreenProps<RootStackParams>
>;

export type AuthImportKeysScreenProps = CompositeScreenProps<
NativeStackScreenProps<AuthStackParams, 'ImportKeys'>,
NativeStackScreenProps<RootStackParams>
>;

// Home Stack
export type HomeNavigationProp = NativeStackNavigationProp<HomeBottomStackParams>;

Expand Down
15 changes: 15 additions & 0 deletions JoyboyCommunity/src/utils/keypair.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};

0 comments on commit ee68be8

Please sign in to comment.