diff --git a/app.json b/app.json index a8f37f9..c8a1bc7 100644 --- a/app.json +++ b/app.json @@ -9,6 +9,7 @@ "userInterfaceStyle": "automatic", "newArchEnabled": true, "ios": { + "googleServicesFile": "./GoogleService-Info.plist", "supportsTablet": true, "bundleIdentifier": "com.builtbybennett.nameflame", "infoPlist": { @@ -16,6 +17,7 @@ } }, "android": { + "googleServicesFile": "./google-services.json", "adaptiveIcon": { "foregroundImage": "./assets/images/adaptive-icon.png", "backgroundColor": "#ffffff" @@ -41,7 +43,9 @@ [ "expo-font", { - "fonts": ["./assets/fonts/BricolageGrotesque.ttf"] + "fonts": [ + "./assets/fonts/BricolageGrotesque.ttf" + ] } ] ], @@ -56,6 +60,12 @@ "projectId": "4a498677-22c5-4a41-a8ca-3fdcc2c436b1" } }, - "owner": "builtbybennett" + "owner": "builtbybennett", + "runtimeVersion": { + "policy": "appVersion" + }, + "updates": { + "url": "https://u.expo.dev/4a498677-22c5-4a41-a8ca-3fdcc2c436b1" + } } } diff --git a/app/(app)/_layout.tsx b/app/(app)/_layout.tsx index 49b76b1..da0f352 100644 --- a/app/(app)/_layout.tsx +++ b/app/(app)/_layout.tsx @@ -7,13 +7,13 @@ import { Colors } from '../../constants/Colors'; import { MaterialIcons } from '@expo/vector-icons'; import { useRouter, useLocalSearchParams } from 'expo-router'; -import { useToken } from '../../contexts/authCtx'; +import { useAuth } from '../../contexts/authCtx'; import useApi from '@/hooks/useApi'; import { useActiveNameContext } from '../../contexts/activeNameContext'; import { ThemedView } from '@/components/ThemedView'; export default function AppLayout() { - const { token, isLoading } = useToken(); + const { user, isLoading } = useAuth(); const router = useRouter(); const api = useApi(); @@ -29,7 +29,7 @@ export default function AppLayout() { // Only require authentication within the (app) group's layout as users // need to be able to access the (auth) group and sign in again. - if (!token) { + if (!user) { // On web, static rendering will stop here as the user is not authenticated // in the headless Node process that the pages are rendered in. return ; diff --git a/app/(app)/nameContext/[id].tsx b/app/(app)/nameContext/[id].tsx index 8d0e965..5f990d6 100644 --- a/app/(app)/nameContext/[id].tsx +++ b/app/(app)/nameContext/[id].tsx @@ -6,15 +6,19 @@ import { Dropdown } from 'react-native-element-dropdown'; import { Colors } from '@/constants/Colors'; import useApi from '@/hooks/useApi'; import { useActiveNameContext } from '@/contexts/activeNameContext'; +import { useConfirmationContext } from '@/contexts/confirmationCtx'; +import { useErrorContext } from '@/contexts/errorCtx'; import { MaterialIcons } from '@expo/vector-icons'; export default function NameContextDetailsView() { const { id } = useLocalSearchParams<{ id: string }>(); const router = useRouter(); const activeNameContext = useActiveNameContext(); + const { requireConfirmation } = useConfirmationContext(); + const { addApiError } = useErrorContext(); const api = useApi(); const [loading, setLoading] = useState(true); - const [error, setError] = useState(''); + const [error, setError] = useState({}); // form values const [nameValue, setNameValue] = useState(''); const [descriptionValue, setDescriptionValue] = useState(''); @@ -66,12 +70,14 @@ export default function NameContextDetailsView() { }) }).catch((err) => { - setError(err); + addApiError(err); + }).finally(() => { setLoading(false); }); } function saveNameContext() { + setLoading(true); api.post('/nameContext', { name: nameValue, description: descriptionValue, @@ -83,24 +89,28 @@ export default function NameContextDetailsView() { }, participants }).then(() => { - alert('Name context created'); router.push('/nameContext'); }).catch((err) => { - console.log(err); - alert('Failed to create name context'); + if (err.response?.status === 500) { + addApiError(err); + } + else { + setError(err.response?.data?.error || {}); + } + }).finally(() => { + setLoading(false); }); } function handleDelete() { - if (confirm('Are you sure you want to delete this name context?')) { + setLoading(true); api.delete(`/nameContext/${id}`).then(() => { - alert('Name context deleted'); router.push('/nameContext'); }).catch((err) => { - console.log(err); - alert('Failed to delete name context'); + addApiError(err); + }).finally(() => { + setLoading(false); }); - } } if (loading) { @@ -115,15 +125,23 @@ export default function NameContextDetailsView() { Basic Information {isExistingNameContxt && - + { + requireConfirmation({ + primaryActionTitle: 'Delete Name Context', + primaryAction: handleDelete, + message: 'Are you sure you want to delete this name context?' + }); + }} style={{ padding: 5, backgroundColor: Colors.core.purple, borderRadius: 5 }}> } + {isExistingNameContxt && router.push(`/nameContext/${id}/match`)} style={{ padding: 5, backgroundColor: Colors.core.orange, borderRadius: 5 }}> + } Name + {error.name?.message ? {error.name?.message} : null} Description + {error.description?.message ? {error.description?.message} : null} Noun + {error.noun?.message ? {error.noun?.message} : null} Max Characters + {error.maxCharacters?.message ? {error.maxCharacters?.message} : null} Gender + {error.gender?.message ? {error.gender.message} : null} Starts with the letter setStartsWithValue(String(value).toUpperCase())} maxLength={1} /> + {error.startsWithLetter?.message ? {error.startsWithLetter?.message} : null} ); @@ -255,4 +279,8 @@ const styles = StyleSheet.create({ color: Colors.core.white, fontSize: 16, }, + errorText: { + color: Colors.core.orange, + fontSize: 12, + }, }); \ No newline at end of file diff --git a/app/(app)/nameContext/[id]/match.tsx b/app/(app)/nameContext/[id]/match.tsx index 03a928a..675a93d 100644 --- a/app/(app)/nameContext/[id]/match.tsx +++ b/app/(app)/nameContext/[id]/match.tsx @@ -30,8 +30,6 @@ export default function NameContextDetailsMatchs() { function fetchNewName() { api.get('/name/random').then((resp) => { setCurrentName(resp.data); - }).catch((err) => { - alert(err); }); } @@ -45,9 +43,6 @@ export default function NameContextDetailsMatchs() { }).then(() => { setSearchValue(''); fetchNewName(); - } - ).catch((err) => { - alert(err); }); } diff --git a/app/(app)/nameContext/index.tsx b/app/(app)/nameContext/index.tsx index 4886a75..3ae24d5 100644 --- a/app/(app)/nameContext/index.tsx +++ b/app/(app)/nameContext/index.tsx @@ -21,11 +21,9 @@ export default function NameContextListView() { setRefreshing(true); api.get('/nameContexts').then((resp) => { setNameContexts(resp.data); - console.log(resp); setRefreshing(false); }).catch((err) => { setRefreshing(false); - alert(err); }) }; diff --git a/app/(app)/settings/index.js b/app/(app)/settings/index.js index eaba31d..f2c1fc8 100644 --- a/app/(app)/settings/index.js +++ b/app/(app)/settings/index.js @@ -1,15 +1,50 @@ -import { useState, useEffect } from "react"; -import { View, Button, Switch } from 'react-native'; +import { useState } from "react"; +import { View, Button, Switch, Pressable, ActivityIndicator } from 'react-native'; import { Colors } from '../../../constants/Colors'; import { ThemedView } from '../../../components/ThemedView'; import { ThemedText } from '../../../components/ThemedText'; -import { useToken } from '../../../contexts/authCtx'; +import { useAuth } from '../../../contexts/authCtx'; +import { useConfirmationContext } from '../../../contexts/confirmationCtx'; +import useApi from '../../../hooks/useApi'; export default function SettingsView() { - const { signOut } = useToken(); + const { signOutUser } = useAuth(); + const api = useApi(); + const [loading, setLoading] = useState(false); const [sendNotifications, setSendNotifications] = useState(true); + const { requireConfirmation, resolveModal } = useConfirmationContext(); + + function handleAccoundDeletion() { + requireConfirmation({ + message: 'Are you sure you want to delete your account? Deleting your account will remove all of your data including shared name contexts and cannot be undone.', + primaryActionTitle: 'Delete Account', + primaryAction: async () => { + setLoading(true); + try { + await api.delete('/user'); + resolveModal(); + signOutUser(); + } catch (error) { + console.error(error); + } + setLoading(false); + } + }); + } + + if (loading) { + return ( + + + + ); + } return ( -