Skip to content

Commit

Permalink
Merge pull request #16 from TaskRatchet/pre-initial-release-changes
Browse files Browse the repository at this point in the history
delete account functionality
  • Loading branch information
narthur authored Feb 8, 2024
2 parents c0f93c3 + 296ce51 commit 566ef92
Show file tree
Hide file tree
Showing 12 changed files with 1,594 additions and 9 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Identity/
devReferances
*.log
yarn.lock
.env

# OSX
#
Expand Down
1,283 changes: 1,283 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@
"@testing-library/react-native": "^12.4.2",
"babel-preset-react-native": "2.1.0",
"eslint-plugin-react-native": "^4.1.0",
"expo": "^49.0.0",
"expo-dev-client": "~2.4.12",
"firebase": "^10.8.0",
"moment": "^2.29.4",
"node-fetch": "^3.3.2",
"punycode": "^2.3.1",
Expand All @@ -33,8 +35,7 @@
"react-native-gesture-handler": "^2.14.1",
"react-native-safe-area-context": "^4.7.4",
"react-native-screens": "^3.27.0",
"react-native-secure-key-store": "^2.0.10",
"expo": "^49.0.0"
"react-native-secure-key-store": "^2.0.10"
},
"devDependencies": {
"@babel/core": "^7.20.0",
Expand Down
99 changes: 99 additions & 0 deletions src/components/deleteAccountPopup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import {useMutation} from '@tanstack/react-query';
import React from 'react';
import {Modal, Pressable, Text, View} from 'react-native';

import deleteMe from '../services/taskratchet/deleteMe';
import {styles} from '../styles/deleteAccountPopupStyle';
import PressableLoading from './pressableLoading';
import type {DeleteAccountPopupProps} from './types';

export default function DeleteAccountPopup({
navigation,
modalVisible,
setModalVisible,
}: DeleteAccountPopupProps) {
const mutation = useMutation({
mutationFn: deleteMe,
onError: error => {
console.log('delete account error ' + String(error));
},
});

return (
<View>
<Modal visible={modalVisible} transparent={true} animationType="none">
<View style={styles.centeredView}>
<View style={styles.modalView}>
<Text style={styles.title}>Delete Account</Text>
{mutation.isSuccess ? (
<>
<Text style={styles.description}>
Your account has been successfully deleted.
</Text>
<Pressable
style={({pressed}) => [
{
backgroundColor: pressed
? 'rgba(33, 150, 243, 0.5)'
: '#2196F3',
},
styles.cancelButton,
]}>
<Text
style={styles.textStyle}
onPress={() => {
setModalVisible(false);
navigation?.navigate('LoginScreen');
}}>
Logout
</Text>
</Pressable>
</>
) : (
<>
<Text style={styles.description}>
Are you sure you want to delete your account? This will
permanently delete all data associated with your account, and
you will no longer be able to access your account in the app
or online. This action cannot be undone.
</Text>
<PressableLoading
style={({pressed}) => [
{
backgroundColor: pressed ? 'rgba(255, 0, 0, 0.5)' : 'red',
},
styles.confirmButton,
]}
onPress={() => mutation.mutate()}
loading={mutation.isPending}
loadingTextStyle={styles.loadingText}>
<Text style={styles.textStyle}>Confirm</Text>
</PressableLoading>
<Pressable
onPress={() => setModalVisible(false)}
style={({pressed}) => [
{
backgroundColor: pressed
? 'rgba(33, 150, 243, 0.5)'
: '#2196F3',
},
styles.cancelButton,
]}>
<Text style={styles.textStyle}>Cancel</Text>
</Pressable>
{mutation.error ? (
<>
<Text style={styles.error}>Failed to Delete Account</Text>
<Text style={styles.error}>
Please email [email protected]
</Text>
</>
) : null}
</>
)}
</View>
</View>
</Modal>
</View>
);
}
6 changes: 6 additions & 0 deletions src/components/types.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,9 @@ export type TaskType = {
};

export type taskType = TaskType;

export type DeleteAccountPopupProps = {
navigation?: ProfileScreenNavigationProp;
modalVisible: boolean;
setModalVisible: Dispatch<SetStateAction<boolean>>;
};
48 changes: 41 additions & 7 deletions src/screens/ProfileScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import {useQuery} from '@tanstack/react-query';
import React from 'react';
import {Button, Image, ImageSourcePropType, Text, View} from 'react-native';
import React, {useState} from 'react';
import {Image, ImageSourcePropType, Pressable, Text, View} from 'react-native';
import {SafeAreaView} from 'react-native-safe-area-context';

import logo from '../../assets/images/[email protected]';
import DeleteAccountPopup from '../components/deleteAccountPopup';
import {Props} from '../components/types';
import themeProvider from '../providers/themeProvider';
import {getMe} from '../services/taskratchet/getMe';
import {styles} from '../styles/profileScreenStyle';
import useIsDarkMode from '../utils/checkDarkMode';

export default function ProfileScreen({navigation}: Props) {
const [modalVisible, setModalVisible] = useState(false);

const isDarkMode = useIsDarkMode();
const backgroundStyle = {
backgroundColor: isDarkMode
Expand Down Expand Up @@ -38,6 +41,12 @@ export default function ProfileScreen({navigation}: Props) {
source={logo as ImageSourcePropType}
/>

<DeleteAccountPopup
navigation={navigation}
modalVisible={modalVisible}
setModalVisible={setModalVisible}
/>

<View style={styles.profileTitle}>
<Text style={[textColorStyle, styles.name]}>Profile</Text>
</View>
Expand Down Expand Up @@ -79,11 +88,36 @@ export default function ProfileScreen({navigation}: Props) {
<Text>Loading...</Text>
)}
<View style={styles.buttons}>
<Button
title="Go to Home"
onPress={() => navigation.navigate('HomeScreen')}
/>
<Button title="Logout" onPress={goToLoginScreen} />
<Pressable onPress={() => navigation?.navigate('HomeScreen')}>
{({pressed}) => (
<Text
// eslint-disable-next-line react-native/no-inline-styles
style={[styles.button, {color: pressed ? 'blue' : '#0178FA'}]}>
Go to Home
</Text>
)}
</Pressable>
<Pressable onPress={goToLoginScreen}>
{({pressed}) => (
<Text
// eslint-disable-next-line react-native/no-inline-styles
style={[styles.button, {color: pressed ? 'blue' : '#0178FA'}]}>
Logout
</Text>
)}
</Pressable>
<Pressable
onPress={() => setModalVisible(true)}
style={({pressed}) => [
{
backgroundColor: pressed ? 'rgba(255, 0, 0, 0.5)' : 'red',
},
styles.deleteAccountButton,
]}>
<Text style={[textColorStyle, styles.deleteAccount]}>
Delete Account
</Text>
</Pressable>
</View>
</View>
</SafeAreaView>
Expand Down
1 change: 1 addition & 0 deletions src/services/taskratchet/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export const IS_PRODUCTION = HOSTNAME === 'app.taskratchet.com';
export const IS_STAGING = HOSTNAME && HOSTNAME.includes('deploy-preview');
export const IS_LOCAL = !IS_PRODUCTION && !IS_STAGING;
export const API1_BASE = 'https://api.taskratchet.com/api1/';
export const API2_BASE = 'https://api.taskratchet.com/api2/';
// export const API1_BASE = IS_PRODUCTION
// ? 'https://api.taskratchet.com/api1/'
// : IS_STAGING
Expand Down
5 changes: 5 additions & 0 deletions src/services/taskratchet/deleteMe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import fetch2 from './fetch2';

export default async function deleteMe(): Promise<void> {
await fetch2('me', true, 'DELETE');
}
43 changes: 43 additions & 0 deletions src/services/taskratchet/fetch2.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import secureKeystore from 'react-native-secure-key-store';

import {API2_BASE} from './constants';
import {logout} from './sessions';

const _trim = (s: string, c: string) => {
if (c === ']') {
c = '\\]';
}
if (c === '\\') {
c = '\\\\';
}
return s.replace(new RegExp('^[' + c + ']+|[' + c + ']+$', 'g'), '');
};

export default async function fetch2(
route: string,
protected_ = false,
method = 'GET',
data: unknown = null,
): Promise<Response> {
const token = ((await secureKeystore.get('firebase_token')) as string) || '';
const route_ = _trim(route, '/');

if (protected_ && !token) {
throw new Error('User not logged in');
}

// noinspection SpellCheckingInspection
const response = await fetch(API2_BASE + route_, {
method: method,
body: data ? JSON.stringify(data) : undefined,
headers: {
Authorization: `Bearer ${token}`,
},
});

if (response.status === 403) {
await logout();
}

return response;
}
33 changes: 33 additions & 0 deletions src/services/taskratchet/login.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,32 @@
import AsyncStorage from '@react-native-async-storage/async-storage';
import {initializeApp} from 'firebase/app';
import {Auth, getAuth, signInWithEmailAndPassword} from 'firebase/auth';
import RNSecureKeyStore, {ACCESSIBLE} from 'react-native-secure-key-store';

import fetch1 from './fetch1';
import {publishSession} from './sessions';

const firebaseConfig = {
apiKey: process.env.EXPO_PUBLIC_FIREBASE_API_KEY,
authDomain: process.env.EXPO_PUBLIC_FIREBASE_AUTH_DOMAIN,
databaseURL: process.env.EXPO_PUBLIC_FIREBASE_DATABASE_URL,
projectId: process.env.EXPO_PUBLIC_FIREBASE_PROJECT_ID,
storageBucket: process.env.EXPO_PUBLIC_FIREBASE_STORAGE_BUCKET,
messagingSenderId: process.env.EXPO_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
appId: process.env.EXPO_PUBLIC_FIREBASE_APP_ID,
};

let _auth: Auth;

function _getAuth() {
if (!_auth) {
const app = initializeApp(firebaseConfig);
_auth = getAuth(app);
}

return _auth;
}

export async function login(email: string, password: string): Promise<boolean> {
const res = await fetch1('account/login', false, 'POST', {
email,
Expand All @@ -26,6 +49,16 @@ export async function login(email: string, password: string): Promise<boolean> {

await AsyncStorage.setItem('email', email);

const cred = await signInWithEmailAndPassword(_getAuth(), email, password);

try {
await RNSecureKeyStore.set('firebase_token', await cred.user.getIdToken(), {
accessible: ACCESSIBLE.ALWAYS_THIS_DEVICE_ONLY,
});
} catch (error) {
console.error(`Error setting firebase token: ${String(error)}`);
}

await publishSession();

return true;
Expand Down
63 changes: 63 additions & 0 deletions src/styles/deleteAccountPopupStyle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import {StyleSheet} from 'react-native';

export const styles = StyleSheet.create({
centeredView: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
marginTop: 22,
},
modalView: {
width: '90%',
backgroundColor: 'white',
borderRadius: 20,
padding: 35,
shadowColor: '#000',
shadowOffset: {
width: 0,
height: 2,
},
shadowOpacity: 0.25,
shadowRadius: 4,
elevation: 5,
},
title: {
fontSize: 24,
fontWeight: 'bold',
textAlign: 'center',
},
confirmButton: {
borderRadius: 20,
margin: 5,
padding: 10,
elevation: 2,
},
cancelButton: {
borderRadius: 20,
margin: 5,
padding: 10,
elevation: 2,
},
textStyle: {
fontSize: 18,
color: 'white',
fontWeight: 'bold',
textAlign: 'center',
},
error: {
color: 'red',
textAlign: 'center',
},
loadingText: {
fontSize: 18,
color: 'white',
fontWeight: 'bold',
textAlign: 'center',
},
description: {
marginBottom: 20,
marginTop: 20,
textAlign: 'center',
fontSize: 16,
},
});
Loading

0 comments on commit 566ef92

Please sign in to comment.