From 62d186873594c52b045dc0af8b05f124fc13a0e7 Mon Sep 17 00:00:00 2001 From: Edwin Noriega Date: Wed, 28 Aug 2024 16:54:43 -0600 Subject: [PATCH 01/29] chore: Update user hooks and queries in React Native client --- .../react-native/src/services/user/hooks.ts | 19 ++++++------------- .../react-native/src/services/user/index.ts | 3 ++- .../react-native/src/services/user/queries.ts | 19 +++++++++++++++++++ 3 files changed, 27 insertions(+), 14 deletions(-) create mode 100644 {{cookiecutter.project_slug}}/clients/mobile/react-native/src/services/user/queries.ts diff --git a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/services/user/hooks.ts b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/services/user/hooks.ts index e215452c5..7b395360c 100644 --- a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/services/user/hooks.ts +++ b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/services/user/hooks.ts @@ -6,28 +6,21 @@ import { useEffect } from 'react' import { useQuery, useMutation } from '@tanstack/react-query' import { useAuth } from '@stores/auth' -import { userApi } from './api' +import { userApi, userQueries } from '.' import { queryClient } from '@utils/query-client' export const useUser = () => { const userId = useAuth.use.userId() const { writeUserInStorage } = useAuth.use.actions() - const data = useQuery({ - queryKey: ['user', userId], - queryFn: async () => { - const user = await userApi.retrieve(userId) - return user - }, - enabled: Boolean(userId), - }) + const query = useQuery(userQueries.retrieve(userId)) useEffect(() => { - if (data.isSuccess && data.data) { - writeUserInStorage(data.data) + if (query.isSuccess && query.data) { + writeUserInStorage(query.data) } - }, [data.data, data.isSuccess, writeUserInStorage]) + }, [query.data, query.isSuccess, writeUserInStorage]) - return data + return query } diff --git a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/services/user/index.ts b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/services/user/index.ts index 2e69e848c..43a250bac 100644 --- a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/services/user/index.ts +++ b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/services/user/index.ts @@ -1,4 +1,5 @@ export * from './forms' export * from './api' export * from './models' -export * from './hooks' \ No newline at end of file +export * from './hooks' +export * from './queries' \ No newline at end of file diff --git a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/services/user/queries.ts b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/services/user/queries.ts new file mode 100644 index 000000000..9adeb32c8 --- /dev/null +++ b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/services/user/queries.ts @@ -0,0 +1,19 @@ + +import { queryOptions } from '@tanstack/react-query' +import { userApi } from './api' + +/** + * @link https://tkdodo.eu/blog/the-query-options-api?ck_subscriber_id=1819338276 + * Create query factories for a more type safe solution of the queries across the app. This way whichever invalidation that has to happen in the resource can be done with `queryClient.invalidateQueries(userQueries.all())` or any other of the query factory's functions. + * In this case we do have a very simple example for the user query but depending on the resource we may want to add more query factories + */ + +export const userQueries = { + all: () => ['users'], + retrieve: (id: string) => + queryOptions({ + queryKey: [...userQueries.all(), id], + queryFn: () => userApi.retrieve(id), + enabled: Boolean(id), + }), +} From 4220c07e2618e0cf85d11b97ef9b5ab4178df320 Mon Sep 17 00:00:00 2001 From: Edwin Noriega Date: Wed, 28 Aug 2024 16:54:58 -0600 Subject: [PATCH 02/29] feat: Add Container component for React Native client --- .../react-native/src/components/container.tsx | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 {{cookiecutter.project_slug}}/clients/mobile/react-native/src/components/container.tsx diff --git a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/components/container.tsx b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/components/container.tsx new file mode 100644 index 000000000..0a6dccb00 --- /dev/null +++ b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/components/container.tsx @@ -0,0 +1,22 @@ +import { FC, ReactNode } from 'react' +import { View } from 'react-native' +import { MultiPlatformSafeAreaView } from './multi-platform-safe-area-view' + +export const Container: FC<{ + children: ReactNode + containerClassName?: string + innerContainerClassName?: string + hasHorizontalPadding?: boolean +}> = ({ children, containerClassName, innerContainerClassName, hasHorizontalPadding = true }) => { + return ( + + + {children} + + + ) +} From 2b732d1493a48f5d16661c8741dee3ee7a175190 Mon Sep 17 00:00:00 2001 From: Edwin Noriega Date: Wed, 28 Aug 2024 16:55:19 -0600 Subject: [PATCH 03/29] feat: Add ContactEmailButton component for React Native client --- .../src/components/contact-email-button.tsx | 23 +++++++++++++++++++ .../react-native/src/utils/constants.ts | 1 + 2 files changed, 24 insertions(+) create mode 100644 {{cookiecutter.project_slug}}/clients/mobile/react-native/src/components/contact-email-button.tsx diff --git a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/components/contact-email-button.tsx b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/components/contact-email-button.tsx new file mode 100644 index 000000000..56d86e952 --- /dev/null +++ b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/components/contact-email-button.tsx @@ -0,0 +1,23 @@ +import { Linking, Text } from 'react-native' +import { Bounceable } from 'rn-bounceable' +import { useConstants } from '@utils/constants' + +export const ContactEmailButton = () => { + const { supportEmail } = useConstants() + const handleSendMail = async () => { + const mailtoUrl = `mailto:${supportEmail}` + const canOpen = await Linking.canOpenURL(mailtoUrl) + if (canOpen) { + Linking.openURL(mailtoUrl) + } else { + console.error('could not open this url: ', mailtoUrl) + } + } + return ( + + + {supportEmail} + + + ) +} diff --git a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/utils/constants.ts b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/utils/constants.ts index f4034be5b..15c002fa8 100644 --- a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/utils/constants.ts +++ b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/utils/constants.ts @@ -9,5 +9,6 @@ export const useConstants = () => { github: 'https://github.com/kanzitelli/expo-starter', website: 'https://github.com/kanzitelli/expo-starter', }, + supportEmail: 'hello@thinknimble.com', }; }; From 41404f0b561a0031a72b31d1ded33a3e77a7f516 Mon Sep 17 00:00:00 2001 From: Edwin Noriega Date: Wed, 28 Aug 2024 16:55:51 -0600 Subject: [PATCH 04/29] feat: Add settings screens to React Native client --- .../src/screens/settings/contact-us.tsx | 50 +++++ .../src/screens/settings/edit-profile.tsx | 195 ++++++++++++++++ .../src/screens/settings/index.tsx | 212 ++++++++++++++++++ 3 files changed, 457 insertions(+) create mode 100644 {{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/contact-us.tsx create mode 100644 {{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/edit-profile.tsx create mode 100644 {{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/index.tsx diff --git a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/contact-us.tsx b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/contact-us.tsx new file mode 100644 index 000000000..ec61b52b2 --- /dev/null +++ b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/contact-us.tsx @@ -0,0 +1,50 @@ +import { Text, View } from 'react-native' +import { BButton } from '@components/button' +import { Container } from '@components/container' +import { getNavio } from '..' +import { ContactEmailButton } from '@components/contact-email-button' +import { Ionicons } from '@expo/vector-icons' +import colors from '@utils/colors' +import { BounceableWind } from '@components/styled' + +export const ContactUs = () => { + const navio = getNavio() + + return ( + + + { + navio.goBack() + }} + contentContainerClassName="absolute left-0 top-0" + > + + + + Contact Us + + + + + + Needing help? + + + + Reach out to us at + + + + + { + navio.goBack() + }} + /> + + + ) +} diff --git a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/edit-profile.tsx b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/edit-profile.tsx new file mode 100644 index 000000000..7c146c0e3 --- /dev/null +++ b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/edit-profile.tsx @@ -0,0 +1,195 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query' +import { useState } from 'react' +import { ActivityIndicator, Alert, ScrollView, TextInput, Text, View } from 'react-native' +import { Bounceable } from 'rn-bounceable' +import { Container } from '@components/container' +import { UserShape, fullNameZod, useLogout, useUser, userApi } from '@services/user' +import { userQueries } from '@services/user/queries' +import { getNavio } from '..' +import { ErrorMessage } from '@components/errors' +import { Ionicons } from '@expo/vector-icons' +import colors from '@utils/colors' +import { BButton } from '@components/button' +import { isAxiosError } from 'axios' + +const Separator = () => { + return ( + + + + ) +} + +export const EditProfile = () => { + const navio = getNavio() + const onCancel = () => { + navio.goBack() + } + const { data: user } = useUser() + const [fullName, setFullName] = useState(user?.fullName ?? '') + const [errors, setErrors] = useState() + + const handleFullNameChange = (name: string) => { + setFullName(name) + } + const unsavedChanges = user && user.fullName !== fullName + + const parsedName = fullNameZod.safeParse(fullName) + const isValid = parsedName.success + const qClient = useQueryClient() + + const { mutate: save, isPending: isSaving } = useMutation({ + mutationFn: userApi.csc.patchUser, + onMutate: async () => { + const userSnapshot = qClient.getQueryData(userQueries.all())?.fullName + await qClient.cancelQueries({ queryKey: userQueries.all() }) + await qClient.setQueryData(userQueries.all(), (input?: UserShape) => { + return input ? { ...input } : undefined + }) + return { userSnapshot } + }, + onSuccess: () => { + qClient.invalidateQueries({ queryKey: userQueries.all() }) + }, + onError: (e, _, context) => { + //rollback update + qClient.setQueryData(userQueries.all(), (input?: UserShape) => { + return input ? { ...input, fullName: context?.userSnapshot ?? '' } : undefined + }) + + if (isAxiosError(e)) { + const { data } = e?.response ?? {} + if (data) { + const isArrayOfStrings = Array.isArray(data) && data.length && typeof data[0] === 'string' + const isObjectOfErrors = Object.keys(data).every((key) => Array.isArray(data[key])) + setErrors( + (isArrayOfStrings + ? data + : isObjectOfErrors + ? Object.keys(data).map((key) => data[key]) + : ['Something went wrong']) as string[], + ) + } + } + }, + }) + + const { mutate: logout, isPending: isLoggingOut } = useLogout() + + const { mutate: deleteUser, isPending: isDeleting } = useMutation({ + mutationFn: userApi.remove, + onSuccess: () => { + logout() + }, + onError: () => { + Alert.alert('Error', "Couldn't delete your account. Please try again later.", [ + { + text: 'Ok', + onPress: () => console.log('Ok Pressed'), + }, + ]) + }, + }) + + const handleSave = () => { + if (!user) return + const [firstName, lastName] = fullName.split(' ') + save({ id: user.id, firstName, lastName }) + } + + const showWarningAlert = () => { + Alert.alert('WARNING', 'Deleting your account is permanent and cannot be undone. If you would like to use this app again, you will need to create a new account.', [ + { + text: 'Cancel', + onPress: () => console.log('Cancel Pressed'), + style: 'cancel', + }, + {text: 'Delete', onPress: () => { + if (!user) return + deleteUser(user?.id) + }}, + ]) + } + + if (!user) return <> //never + + return ( + + + + + + + Edit Profile + + + {isSaving ? ( + + ) : ( + + Save + + )} + + + + + + + + Full Name + + + + + + + + + + {fullName && !isValid ? ( + + + {parsedName.error.issues.map((i) => i.message).join(', ')} + + + ) : null} + + + + + Email + + + + + {user.email} + + + {errors?.map((error, idx) => ( + + {error} + + ))} + + + + + + + ) +} + diff --git a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/index.tsx b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/index.tsx new file mode 100644 index 000000000..add2c4709 --- /dev/null +++ b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/index.tsx @@ -0,0 +1,212 @@ +import { BButton } from '@components/button' +import { MaterialIcons, Ionicons, AntDesign } from '@expo/vector-icons' +import { useLogout, useUser } from '@services/user' +import * as Application from 'expo-application' +import * as Updates from 'expo-updates' +import { openBrowserAsync } from 'expo-web-browser' +import React from 'react' +import { Alert, ScrollView, Text, View } from 'react-native' +import { useSafeAreaInsets } from 'react-native-safe-area-context' +import { Bounceable } from 'rn-bounceable' +import { AppScreens, getNavio } from '..' +import colors from '@utils/colors' +import { Container } from '@components/container' + +import { BounceableWind } from '@components/styled' + +type SectionChild = { + title: string + icon: JSX.Element + args: + | { + screenName: AppScreens + screenProps?: Record + } + | { link: string } +} + +type Section = { + name: string + children: SectionChild[] +} + +const sections: Section[] = [ + { + name: 'About', + children: [ + { + title: 'Terms of Service', + icon: , + args: { + link: 'https://bebe-foodie.com/terms-of-use/', + }, + }, + { + title: 'Privacy Policy', + icon: , + args: { + link: 'https://bebe-foodie.com/privacy-policy/', + }, + }, + { + title: 'Contact us', + icon: , + args: { + screenName: 'ContactUs', + }, + }, + ], + }, +] + +const UserCard = () => { + const { data: user } = useUser() + const navio = getNavio() + + const handlePress = () => { + navio.push('EditProfile') + } + if (!user) return <> + + return ( + + + + + + + + + {user.fullName} + + {user.email} + + + + + + + + + + ) +} + +const SectionList = () => { + const navio = getNavio() + return ( + + {sections.map((s, sIdx) => { + return ( + + + + {s.name} + + + {s.children.map((sc, scIdx) => { + return ( + { + if ('screenName' in sc.args) { + navio.push( + sc.args.screenName, + 'screenProps' in sc.args ? sc.args.screenProps : undefined, + ) + return + } else if ('link' in sc.args) { + openBrowserAsync(sc.args.link) + } + }} + key={scIdx} + > + + + + {sc.icon} + {sc.title} + + + + + + + + ) + })} + + ) + })} + + ) +} + +export const Settings = () => { + const navio = getNavio() + const { bottom } = useSafeAreaInsets() + const { mutate: logout, isPending: isLoggingOut } = useLogout() + + const handleLogout = () => { + logout(undefined, { + onSettled: () => { + navio?.setRoot('stacks', 'AuthStack') + }, + }) + } + + const showWarningAlert = () => { + Alert.alert('Log out', 'Are you sure you want to log out?', [ + { + text: 'Cancel', + onPress: () => console.log('Cancel Pressed'), + style: 'cancel', + }, + {text: 'Log out', onPress: handleLogout}, + ]) + } + + return ( + + + { + navio.goBack() + }} + contentContainerClassName="absolute left-0 top-0" + > + + + + Settings + + + + + + + + + Version released {Application.nativeApplicationVersion} ( + {Application.nativeBuildVersion}) - {Updates.channel ? Updates.channel : 'Dev'} + + + + + ) +} + +export { ContactUs } from './contact-us' +export { EditProfile } from './edit-profile' From 166e0edac6b17e258ba3960150cbbebe6523504e Mon Sep 17 00:00:00 2001 From: Edwin Noriega Date: Wed, 28 Aug 2024 16:56:01 -0600 Subject: [PATCH 05/29] feat: Add settings screens and related components for React Native client --- .../clients/mobile/react-native/src/screens/routes.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/routes.ts b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/routes.ts index 8ca71e2af..bd6a8eedc 100644 --- a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/routes.ts +++ b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/routes.ts @@ -7,6 +7,7 @@ import { Main } from '@screens/main' import { Auth } from '@screens/auth/auth' import { DashboardScreen } from '@screens/dashboard' import { ComponentsPreview } from '@screens/ComponentsPreview' +import { ContactUs, EditProfile, Settings } from '@screens/settings' // Default options - forcing a mobile trigger export const screenDefaultOptions = (): NativeStackNavigationOptions => ({ @@ -29,10 +30,11 @@ export const tabDefaultOptions = (): BottomTabNavigationOptions => ({ }) // NAVIO export const navio = Navio.build({ - screens: { Auth, Login, SignUp, Main, DashboardScreen, ComponentsPreview }, + screens: { Auth, Login, SignUp, Main, DashboardScreen, ComponentsPreview, Settings, ContactUs, EditProfile }, stacks: { AuthStack: ['Auth'], MainStack: ['DashboardScreen'], + SettingsStack: ['Settings', 'ContactUs', 'EditProfile'], /** * Set me as the root to see the components preview */ From c75605bd8a1d5dc359e7f7200cdeabf4b83a4267 Mon Sep 17 00:00:00 2001 From: Edwin Noriega Date: Wed, 28 Aug 2024 16:56:06 -0600 Subject: [PATCH 06/29] feat: Add Ionicons and BounceableWind to DashboardScreen --- .../clients/mobile/react-native/src/screens/dashboard.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/dashboard.tsx b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/dashboard.tsx index 40daa676f..b33c6c93a 100644 --- a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/dashboard.tsx +++ b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/dashboard.tsx @@ -7,6 +7,8 @@ import { useAtomValue } from 'jotai' import React from 'react' import { Text, View } from 'react-native' import { SheetManager } from 'react-native-actions-sheet' +import Ionicons from '@expo/vector-icons/Ionicons'; +import { BounceableWind } from '@components/styled' export const DashboardScreen = () => { const navio = useAtomValue(navioAtom) @@ -23,6 +25,12 @@ export const DashboardScreen = () => { return ( + + + navio.stacks.push('SettingsStack')}> + + + Welcome to the Dashboard From 7c1e91ef554f9eed5ce0bdebc5dfe8b16d55f567 Mon Sep 17 00:00:00 2001 From: Edwin Noriega Date: Wed, 28 Aug 2024 17:05:22 -0600 Subject: [PATCH 07/29] feat: Refactor settings screen in React Native client --- .../src/screens/settings/index.tsx | 211 +----------------- .../src/screens/settings/main-settings.tsx | 210 +++++++++++++++++ 2 files changed, 211 insertions(+), 210 deletions(-) create mode 100644 {{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/main-settings.tsx diff --git a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/index.tsx b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/index.tsx index add2c4709..f1d6c0718 100644 --- a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/index.tsx +++ b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/index.tsx @@ -1,212 +1,3 @@ -import { BButton } from '@components/button' -import { MaterialIcons, Ionicons, AntDesign } from '@expo/vector-icons' -import { useLogout, useUser } from '@services/user' -import * as Application from 'expo-application' -import * as Updates from 'expo-updates' -import { openBrowserAsync } from 'expo-web-browser' -import React from 'react' -import { Alert, ScrollView, Text, View } from 'react-native' -import { useSafeAreaInsets } from 'react-native-safe-area-context' -import { Bounceable } from 'rn-bounceable' -import { AppScreens, getNavio } from '..' -import colors from '@utils/colors' -import { Container } from '@components/container' - -import { BounceableWind } from '@components/styled' - -type SectionChild = { - title: string - icon: JSX.Element - args: - | { - screenName: AppScreens - screenProps?: Record - } - | { link: string } -} - -type Section = { - name: string - children: SectionChild[] -} - -const sections: Section[] = [ - { - name: 'About', - children: [ - { - title: 'Terms of Service', - icon: , - args: { - link: 'https://bebe-foodie.com/terms-of-use/', - }, - }, - { - title: 'Privacy Policy', - icon: , - args: { - link: 'https://bebe-foodie.com/privacy-policy/', - }, - }, - { - title: 'Contact us', - icon: , - args: { - screenName: 'ContactUs', - }, - }, - ], - }, -] - -const UserCard = () => { - const { data: user } = useUser() - const navio = getNavio() - - const handlePress = () => { - navio.push('EditProfile') - } - if (!user) return <> - - return ( - - - - - - - - - {user.fullName} - - {user.email} - - - - - - - - - - ) -} - -const SectionList = () => { - const navio = getNavio() - return ( - - {sections.map((s, sIdx) => { - return ( - - - - {s.name} - - - {s.children.map((sc, scIdx) => { - return ( - { - if ('screenName' in sc.args) { - navio.push( - sc.args.screenName, - 'screenProps' in sc.args ? sc.args.screenProps : undefined, - ) - return - } else if ('link' in sc.args) { - openBrowserAsync(sc.args.link) - } - }} - key={scIdx} - > - - - - {sc.icon} - {sc.title} - - - - - - - - ) - })} - - ) - })} - - ) -} - -export const Settings = () => { - const navio = getNavio() - const { bottom } = useSafeAreaInsets() - const { mutate: logout, isPending: isLoggingOut } = useLogout() - - const handleLogout = () => { - logout(undefined, { - onSettled: () => { - navio?.setRoot('stacks', 'AuthStack') - }, - }) - } - - const showWarningAlert = () => { - Alert.alert('Log out', 'Are you sure you want to log out?', [ - { - text: 'Cancel', - onPress: () => console.log('Cancel Pressed'), - style: 'cancel', - }, - {text: 'Log out', onPress: handleLogout}, - ]) - } - - return ( - - - { - navio.goBack() - }} - contentContainerClassName="absolute left-0 top-0" - > - - - - Settings - - - - - - - - - Version released {Application.nativeApplicationVersion} ( - {Application.nativeBuildVersion}) - {Updates.channel ? Updates.channel : 'Dev'} - - - - - ) -} - +export { Settings } from './main-settings' export { ContactUs } from './contact-us' export { EditProfile } from './edit-profile' diff --git a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/main-settings.tsx b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/main-settings.tsx new file mode 100644 index 000000000..f6c92e1da --- /dev/null +++ b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/main-settings.tsx @@ -0,0 +1,210 @@ +import { BButton } from '@components/button' +import { MaterialIcons, Ionicons, AntDesign } from '@expo/vector-icons' +import { useLogout, useUser } from '@services/user' +import * as Application from 'expo-application' +import * as Updates from 'expo-updates' +import { openBrowserAsync } from 'expo-web-browser' +import React from 'react' +import { Alert, ScrollView, Text, View } from 'react-native' +import { useSafeAreaInsets } from 'react-native-safe-area-context' +import { Bounceable } from 'rn-bounceable' +import { AppScreens, getNavio } from '..' +import colors from '@utils/colors' +import { Container } from '@components/container' + +import { BounceableWind } from '@components/styled' + +type SectionChild = { + title: string + icon: JSX.Element + args: + | { + screenName: AppScreens + screenProps?: Record + } + | { link: string } + } + + type Section = { + name: string + children: SectionChild[] + } + + const sections: Section[] = [ + { + name: 'About', + children: [ + { + title: 'Terms of Service', + icon: , + args: { + link: 'https://bebe-foodie.com/terms-of-use/', + }, + }, + { + title: 'Privacy Policy', + icon: , + args: { + link: 'https://bebe-foodie.com/privacy-policy/', + }, + }, + { + title: 'Contact us', + icon: , + args: { + screenName: 'ContactUs', + }, + }, + ], + }, + ] + + const UserCard = () => { + const { data: user } = useUser() + const navio = getNavio() + + const handlePress = () => { + navio.push('EditProfile') + } + if (!user) return <> + + return ( + + + + + + + + + {user.fullName} + + {user.email} + + + + + + + + + + ) + } + + const SectionList = () => { + const navio = getNavio() + return ( + + {sections.map((s, sIdx) => { + return ( + + + + {s.name} + + + {s.children.map((sc, scIdx) => { + return ( + { + if ('screenName' in sc.args) { + navio.push( + sc.args.screenName, + 'screenProps' in sc.args ? sc.args.screenProps : undefined, + ) + return + } else if ('link' in sc.args) { + openBrowserAsync(sc.args.link) + } + }} + key={scIdx} + > + + + + {sc.icon} + {sc.title} + + + + + + + + ) + })} + + ) + })} + + ) + } + + +export const Settings = () => { + const navio = getNavio() + const { bottom } = useSafeAreaInsets() + const { mutate: logout, isPending: isLoggingOut } = useLogout() + + const handleLogout = () => { + logout(undefined, { + onSettled: () => { + navio?.setRoot('stacks', 'AuthStack') + }, + }) + } + + const showWarningAlert = () => { + Alert.alert('Log out', 'Are you sure you want to log out?', [ + { + text: 'Cancel', + onPress: () => console.log('Cancel Pressed'), + style: 'cancel', + }, + {text: 'Log out', onPress: handleLogout}, + ]) + } + + return ( + + + { + navio.goBack() + }} + contentContainerClassName="absolute left-0 top-0" + > + + + + Settings + + + + + + + + + Version released {Application.nativeApplicationVersion} ( + {Application.nativeBuildVersion}) - {Updates.channel ? Updates.channel : 'Dev'} + + + + + ) + } \ No newline at end of file From ac9548812316f092e4595a2963d68f4a0f1493a2 Mon Sep 17 00:00:00 2001 From: Edwin Noriega Date: Wed, 28 Aug 2024 17:06:26 -0600 Subject: [PATCH 08/29] Refactor buttonProps in Settings screen --- .../mobile/react-native/src/screens/settings/main-settings.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/main-settings.tsx b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/main-settings.tsx index f6c92e1da..aad768bfe 100644 --- a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/main-settings.tsx +++ b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/main-settings.tsx @@ -191,7 +191,7 @@ export const Settings = () => { containerClassName="mb-7" isLoading={isLoggingOut} buttonProps={{ - disabled: isLoggingOut, + disabled: isLoggingOut }} /> Date: Wed, 28 Aug 2024 17:07:06 -0600 Subject: [PATCH 09/29] Refactor buttonProps in Settings screen --- .../mobile/react-native/src/screens/settings/main-settings.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/main-settings.tsx b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/main-settings.tsx index aad768bfe..0a119d590 100644 --- a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/main-settings.tsx +++ b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/main-settings.tsx @@ -190,9 +190,6 @@ export const Settings = () => { onPress={showWarningAlert} containerClassName="mb-7" isLoading={isLoggingOut} - buttonProps={{ - disabled: isLoggingOut - }} /> Date: Wed, 28 Aug 2024 17:08:07 -0600 Subject: [PATCH 10/29] Refactor paddingBottom style in Settings screen --- .../mobile/react-native/src/screens/settings/main-settings.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/main-settings.tsx b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/main-settings.tsx index 0a119d590..7410ef6dd 100644 --- a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/main-settings.tsx +++ b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/main-settings.tsx @@ -193,7 +193,7 @@ export const Settings = () => { /> From de537a9f5027c03ca079c8eacf156b69ecd7d0a7 Mon Sep 17 00:00:00 2001 From: Edwin Noriega Date: Wed, 28 Aug 2024 23:41:11 -0600 Subject: [PATCH 11/29] feat: Add React Native settings screens and related components --- cookiecutter.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cookiecutter.json b/cookiecutter.json index 795d7eaf9..05042e288 100644 --- a/cookiecutter.json +++ b/cookiecutter.json @@ -14,7 +14,9 @@ "clients/web/react/src/pages/app-or-auth.tsx", "clients/web/react/src/pages/index.ts", "clients/web/react/src/pages/layout.tsx", - "*/swagger-ui.html" + "*/swagger-ui.html", + "clients/mobile/react-native/src/screens/settings/edit-profile.tsx" + "clients/mobile/react-native/src/screens/settings/main-settings.tsx" ], "mail_service": [ "Mailgun", From d3d414217de615ce4277cc2b5b987fedb5816b2a Mon Sep 17 00:00:00 2001 From: Edwin Noriega Date: Wed, 28 Aug 2024 23:41:26 -0600 Subject: [PATCH 12/29] feat: Add React Native settings screens and related components --- cookiecutter.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cookiecutter.json b/cookiecutter.json index 05042e288..ac51bf314 100644 --- a/cookiecutter.json +++ b/cookiecutter.json @@ -15,7 +15,7 @@ "clients/web/react/src/pages/index.ts", "clients/web/react/src/pages/layout.tsx", "*/swagger-ui.html", - "clients/mobile/react-native/src/screens/settings/edit-profile.tsx" + "clients/mobile/react-native/src/screens/settings/edit-profile.tsx", "clients/mobile/react-native/src/screens/settings/main-settings.tsx" ], "mail_service": [ From 0403034c2a737c14127f4173ca96cad756ac67d5 Mon Sep 17 00:00:00 2001 From: Edwin Noriega Date: Thu, 29 Aug 2024 08:57:17 -0600 Subject: [PATCH 13/29] Refactor text styling in Settings screen --- .../src/screens/settings/main-settings.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/main-settings.tsx b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/main-settings.tsx index 7410ef6dd..60d6dacf2 100644 --- a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/main-settings.tsx +++ b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/main-settings.tsx @@ -77,8 +77,8 @@ type SectionChild = { - {user.fullName} - + {user.fullName} + {user.email} @@ -100,7 +100,7 @@ type SectionChild = { return ( - + {s.name} @@ -124,7 +124,7 @@ type SectionChild = { {sc.icon} - {sc.title} + {sc.title} @@ -196,7 +196,7 @@ export const Settings = () => { paddingBottom: bottom }} > - + Version released {Application.nativeApplicationVersion} ( {Application.nativeBuildVersion}) - {Updates.channel ? Updates.channel : 'Dev'} From a2a40e751921b5432ebd15dfd1944343f9a032be Mon Sep 17 00:00:00 2001 From: Edwin Noriega Date: Thu, 29 Aug 2024 09:06:38 -0600 Subject: [PATCH 14/29] Refactor component import casing for consistency --- .../mobile/react-native/src/screens/ComponentsPreview.tsx | 2 +- .../clients/mobile/react-native/src/screens/dashboard.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/ComponentsPreview.tsx b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/ComponentsPreview.tsx index 66af068a8..1b0b4dee5 100644 --- a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/ComponentsPreview.tsx +++ b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/ComponentsPreview.tsx @@ -2,7 +2,7 @@ import React from 'react' import { Text, View } from 'react-native' import { MultiPlatformSafeAreaView } from '@components/multi-platform-safe-area-view' -import { BButton } from '@components/Button' +import { BButton } from '@components/button' export function ComponentsPreview() { return ( diff --git a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/dashboard.tsx b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/dashboard.tsx index b33c6c93a..2a5febd07 100644 --- a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/dashboard.tsx +++ b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/dashboard.tsx @@ -1,4 +1,4 @@ -import { BButton } from '@components/Button' +import { BButton } from '@components/button' import { MultiPlatformSafeAreaView } from '@components/multi-platform-safe-area-view' import { SHEET_NAMES } from '@components/sheets' import { useLogout } from '@services/user' From 4146161f43a6122fa360bf393f239dccf6869cc3 Mon Sep 17 00:00:00 2001 From: Edwin Noriega Date: Thu, 29 Aug 2024 10:07:04 -0600 Subject: [PATCH 15/29] feat: Update user API to support partial updates This commit updates the user API to support partial updates. It adds a new custom service call `patchUser` that allows updating specific fields of a user object instead of sending the entire object. The `partialUserShape` is introduced to define the shape of the partial user object. Code changes: - Added `partialUserShape` to `user/models.ts` - Added `patchUser` custom service call to `userApi` in `user/api.ts` --- .../react-native/src/services/user/api.ts | 14 +++++++++++-- .../react-native/src/services/user/models.ts | 21 +++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/services/user/api.ts b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/services/user/api.ts index 5481e9267..05e4b227d 100644 --- a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/services/user/api.ts +++ b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/services/user/api.ts @@ -1,7 +1,7 @@ import { createApi, createCustomServiceCall } from '@thinknimble/tn-models' import { z } from 'zod' import { axiosInstance } from '../axios-instance' -import { forgotPasswordShape, loginShape, userCreateShape, userShape } from './models' +import { forgotPasswordShape, loginShape, partialUserShape, userCreateShape, userShape } from './models' const login = createCustomServiceCall({ inputShape: loginShape, @@ -34,6 +34,16 @@ const logout = createCustomServiceCall(async ({ client }) => { return client.post(`/logout/`) }) +const patchUser = createCustomServiceCall({ + inputShape: partialUserShape, + outputShape: userShape, + cb: async ({ client, slashEndingBaseUri, input, utils }) => { + const { id, ...body } = utils.toApi(input) + const res = await client.put(`${slashEndingBaseUri}${id}/`, body) + return utils.fromApi(res.data) + }, +}) + export const userApi = createApi({ client: axiosInstance, baseUri: '/users/', @@ -41,5 +51,5 @@ export const userApi = createApi({ create: userCreateShape, entity: userShape, }, - customCalls: { login, requestPasswordResetCode, resetPassword, logout }, + customCalls: { login, requestPasswordResetCode, resetPassword, logout, patchUser }, }) diff --git a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/services/user/models.ts b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/services/user/models.ts index ba4c4ae5c..21c661244 100644 --- a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/services/user/models.ts +++ b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/services/user/models.ts @@ -38,3 +38,24 @@ export const loginShape = { } export type LoginShape = GetInferredFromRaw + +type ZodOptionalRecord = { + [TKey in keyof T]: z.ZodOptional +} + +export const zodPartialize = (zShape: T) => { + return Object.fromEntries( + Object.entries(zShape).map(([k, v]) => { + return [k, v.optional()] + }), + ) as ZodOptionalRecord +} + +export const fullNameZod = z.string().refine( + (value) => { + return value.split(' ').filter(Boolean).length >= 2 + }, + { message: 'Please provide a full name (first and last name)' }, +) + +export const partialUserShape = { ...zodPartialize(userShape), id: z.string().uuid() } From 09a5bc85ff00374faff3287df25e5879287cbee5 Mon Sep 17 00:00:00 2001 From: Edwin Noriega Date: Thu, 29 Aug 2024 10:44:34 -0600 Subject: [PATCH 16/29] Refactor text styling in Settings screen --- .../mobile/react-native/src/screens/settings/main-settings.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/main-settings.tsx b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/main-settings.tsx index 60d6dacf2..db36d2e98 100644 --- a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/main-settings.tsx +++ b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/main-settings.tsx @@ -100,7 +100,7 @@ type SectionChild = { return ( - + {s.name} From 933b8e2fd1c1d67ad80faf5ff20f42dbaee8b224 Mon Sep 17 00:00:00 2001 From: Edwin Noriega Date: Thu, 29 Aug 2024 11:03:26 -0600 Subject: [PATCH 17/29] Refactor component import casing for consistency --- .../mobile/react-native/src/screens/ComponentsPreview.tsx | 2 +- .../clients/mobile/react-native/src/screens/dashboard.tsx | 2 +- .../mobile/react-native/src/screens/settings/contact-us.tsx | 2 +- .../mobile/react-native/src/screens/settings/edit-profile.tsx | 2 +- .../mobile/react-native/src/screens/settings/main-settings.tsx | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/ComponentsPreview.tsx b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/ComponentsPreview.tsx index 1b0b4dee5..66af068a8 100644 --- a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/ComponentsPreview.tsx +++ b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/ComponentsPreview.tsx @@ -2,7 +2,7 @@ import React from 'react' import { Text, View } from 'react-native' import { MultiPlatformSafeAreaView } from '@components/multi-platform-safe-area-view' -import { BButton } from '@components/button' +import { BButton } from '@components/Button' export function ComponentsPreview() { return ( diff --git a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/dashboard.tsx b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/dashboard.tsx index 2a5febd07..b33c6c93a 100644 --- a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/dashboard.tsx +++ b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/dashboard.tsx @@ -1,4 +1,4 @@ -import { BButton } from '@components/button' +import { BButton } from '@components/Button' import { MultiPlatformSafeAreaView } from '@components/multi-platform-safe-area-view' import { SHEET_NAMES } from '@components/sheets' import { useLogout } from '@services/user' diff --git a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/contact-us.tsx b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/contact-us.tsx index ec61b52b2..2bbef7cbf 100644 --- a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/contact-us.tsx +++ b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/contact-us.tsx @@ -1,5 +1,5 @@ import { Text, View } from 'react-native' -import { BButton } from '@components/button' +import { BButton } from '@components/Button' import { Container } from '@components/container' import { getNavio } from '..' import { ContactEmailButton } from '@components/contact-email-button' diff --git a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/edit-profile.tsx b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/edit-profile.tsx index 7c146c0e3..239661d78 100644 --- a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/edit-profile.tsx +++ b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/edit-profile.tsx @@ -9,7 +9,7 @@ import { getNavio } from '..' import { ErrorMessage } from '@components/errors' import { Ionicons } from '@expo/vector-icons' import colors from '@utils/colors' -import { BButton } from '@components/button' +import { BButton } from '@components/Button' import { isAxiosError } from 'axios' const Separator = () => { diff --git a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/main-settings.tsx b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/main-settings.tsx index db36d2e98..305208aa8 100644 --- a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/main-settings.tsx +++ b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/main-settings.tsx @@ -1,4 +1,4 @@ -import { BButton } from '@components/button' +import { BButton } from '@components/Button' import { MaterialIcons, Ionicons, AntDesign } from '@expo/vector-icons' import { useLogout, useUser } from '@services/user' import * as Application from 'expo-application' From eb9f51d293240378bc3c2b769fb4bce53e7f489a Mon Sep 17 00:00:00 2001 From: Edwin Noriega Date: Thu, 29 Aug 2024 11:05:30 -0600 Subject: [PATCH 18/29] feat: Add fullName field to userShape This commit adds the fullName field to the userShape in the models.ts file. This field is required to store the full name of the user. Code changes: - Added fullName field to userShape in models.ts --- .../clients/mobile/react-native/src/screens/dashboard.tsx | 2 +- .../clients/mobile/react-native/src/services/user/models.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/dashboard.tsx b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/dashboard.tsx index b33c6c93a..c4c7231a3 100644 --- a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/dashboard.tsx +++ b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/dashboard.tsx @@ -27,7 +27,7 @@ export const DashboardScreen = () => { - navio.stacks.push('SettingsStack')}> + navio?.stacks.push('SettingsStack')}> diff --git a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/services/user/models.ts b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/services/user/models.ts index 21c661244..8d45a5226 100644 --- a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/services/user/models.ts +++ b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/services/user/models.ts @@ -14,6 +14,7 @@ export const userShape = { email: z.string().email(), firstName: z.string(), lastName: z.string(), + fullName: z.string(), //TODO:add back `readonly` https://github.com/thinknimble/tn-models-fp/issues/161 token: z.string().nullable(), } From c0ca3dcaf68f3fc382c55ff908713549057a3ed8 Mon Sep 17 00:00:00 2001 From: Edwin Noriega Date: Thu, 29 Aug 2024 11:22:13 -0600 Subject: [PATCH 19/29] feat: Update Terms of Service and Privacy Policy links This commit updates the links for the Terms of Service and Privacy Policy in the main settings screen. The new links point to the correct URLs provided by the client. Code changes: - Updated link for Terms of Service in main-settings.tsx - Updated link for Privacy Policy in main-settings.tsx --- .../react-native/src/screens/settings/main-settings.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/main-settings.tsx b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/main-settings.tsx index 305208aa8..808cb9d7c 100644 --- a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/main-settings.tsx +++ b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/main-settings.tsx @@ -38,14 +38,14 @@ type SectionChild = { title: 'Terms of Service', icon: , args: { - link: 'https://bebe-foodie.com/terms-of-use/', + link: 'https://www.thinknimble.com/privacy-policy', }, }, { title: 'Privacy Policy', icon: , args: { - link: 'https://bebe-foodie.com/privacy-policy/', + link: 'https://www.thinknimble.com/privacy-policy', }, }, { From 49cc4546fe261135a2872c04a5bd9469a389332b Mon Sep 17 00:00:00 2001 From: Edwin Noriega Date: Thu, 29 Aug 2024 11:35:06 -0600 Subject: [PATCH 20/29] feat: Update StatusBar style in App.tsx This commit updates the StatusBar style in the App.tsx file to use the "dark" theme. This change ensures that the status bar text is displayed in dark color, providing better visibility for the user. Code changes: - Updated StatusBar style to "dark" in App.tsx --- .../clients/mobile/react-native/App.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/{{cookiecutter.project_slug}}/clients/mobile/react-native/App.tsx b/{{cookiecutter.project_slug}}/clients/mobile/react-native/App.tsx index 75754d26e..872edbc8a 100644 --- a/{{cookiecutter.project_slug}}/clients/mobile/react-native/App.tsx +++ b/{{cookiecutter.project_slug}}/clients/mobile/react-native/App.tsx @@ -65,7 +65,7 @@ export default Sentry.wrap((): JSX.Element => { - + From 5a2d1975a5c930c8f9881a5b8e7cdceb9befc1bf Mon Sep 17 00:00:00 2001 From: Edwin Noriega Date: Thu, 29 Aug 2024 12:11:36 -0600 Subject: [PATCH 21/29] feat: Improve save button behavior in EditProfile screen This commit updates the save button behavior in the EditProfile screen. The button is now disabled when there are unsaved changes and the form is invalid. This ensures that users cannot save invalid data. Code changes: - Updated save button behavior in EditProfile screen --- .../src/screens/settings/edit-profile.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/edit-profile.tsx b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/edit-profile.tsx index 239661d78..417956fe4 100644 --- a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/edit-profile.tsx +++ b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/edit-profile.tsx @@ -122,11 +122,18 @@ export const EditProfile = () => { Edit Profile - + {isSaving ? ( ) : ( - + Save )} From e1db08b0a01006fa61d65b062e5da568917f6cf8 Mon Sep 17 00:00:00 2001 From: Edwin Noriega Date: Thu, 29 Aug 2024 12:12:55 -0600 Subject: [PATCH 22/29] feat: Add DestroyModelMixin to UserViewSet This commit adds the `DestroyModelMixin` to the `UserViewSet` in the `views.py` file. This mixin allows for the deletion of user objects in the API. Code changes: - Added `DestroyModelMixin` to `UserViewSet` in `views.py` --- .../server/{{cookiecutter.project_slug}}/core/views.py | 1 + 1 file changed, 1 insertion(+) diff --git a/{{cookiecutter.project_slug}}/server/{{cookiecutter.project_slug}}/core/views.py b/{{cookiecutter.project_slug}}/server/{{cookiecutter.project_slug}}/core/views.py index 0a702dab9..42b4ce05b 100755 --- a/{{cookiecutter.project_slug}}/server/{{cookiecutter.project_slug}}/core/views.py +++ b/{{cookiecutter.project_slug}}/server/{{cookiecutter.project_slug}}/core/views.py @@ -55,6 +55,7 @@ class UserViewSet( mixins.RetrieveModelMixin, mixins.ListModelMixin, mixins.UpdateModelMixin, + mixins.DestroyModelMixin, ): queryset = User.objects.all() serializer_class = UserSerializer From fd08597b5ae312a4b1ea77873f2305d57cb3a6db Mon Sep 17 00:00:00 2001 From: Edwin Noriega Date: Thu, 29 Aug 2024 12:17:52 -0600 Subject: [PATCH 23/29] feat: Update dashboard screen layout and add settings button This commit updates the layout of the dashboard screen in the `dashboard.tsx` file. It adds a settings button to the top right corner of the screen, allowing users to navigate to the settings page. The button is implemented using the `BounceableWind` component and the `Ionicons` icon library. Code changes: - Updated layout of the dashboard screen - Added settings button to the top right corner --- .../react-native/src/screens/dashboard.tsx | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/dashboard.tsx b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/dashboard.tsx index c4c7231a3..9148dfce7 100644 --- a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/dashboard.tsx +++ b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/dashboard.tsx @@ -7,7 +7,7 @@ import { useAtomValue } from 'jotai' import React from 'react' import { Text, View } from 'react-native' import { SheetManager } from 'react-native-actions-sheet' -import Ionicons from '@expo/vector-icons/Ionicons'; +import Ionicons from '@expo/vector-icons/Ionicons' import { BounceableWind } from '@components/styled' export const DashboardScreen = () => { @@ -24,14 +24,19 @@ export const DashboardScreen = () => { return ( - - - - navio?.stacks.push('SettingsStack')}> - + + + navio?.stacks.push('SettingsStack')} + > + Settings + - Welcome to the Dashboard + + Welcome to the Dashboard + From 5b1a4f8c1f81052d1950386244e1c7acb5a053ef Mon Sep 17 00:00:00 2001 From: Edwin Noriega Date: Thu, 29 Aug 2024 12:22:45 -0600 Subject: [PATCH 24/29] feat: Add warning alert for account deletion in EditProfile screen This commit adds a warning alert in the EditProfile screen to inform users about the permanent nature of account deletion. The alert provides a cancel option and a delete option, allowing users to make an informed decision. The alert is triggered when the user taps on the delete button. Code changes: - Added warning alert for account deletion in EditProfile screen --- .../src/screens/settings/edit-profile.tsx | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/edit-profile.tsx b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/edit-profile.tsx index 417956fe4..9bfb9cd86 100644 --- a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/edit-profile.tsx +++ b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/edit-profile.tsx @@ -98,17 +98,24 @@ export const EditProfile = () => { } const showWarningAlert = () => { - Alert.alert('WARNING', 'Deleting your account is permanent and cannot be undone. If you would like to use this app again, you will need to create a new account.', [ - { - text: 'Cancel', - onPress: () => console.log('Cancel Pressed'), - style: 'cancel', - }, - {text: 'Delete', onPress: () => { - if (!user) return - deleteUser(user?.id) - }}, - ]) + Alert.alert( + 'WARNING', + 'Deleting your account is permanent and cannot be undone. If you would like to use this app again, you will need to create a new account.', + [ + { + text: 'Cancel', + onPress: () => console.log('Cancel Pressed'), + style: 'cancel', + }, + { + text: 'Delete', + onPress: () => { + if (!user) return + deleteUser(user?.id) + }, + }, + ], + ) } if (!user) return <> //never @@ -119,9 +126,7 @@ export const EditProfile = () => { - - Edit Profile - + Edit Profile { - - Full Name - + Full Name @@ -167,10 +170,8 @@ export const EditProfile = () => { ) : null} - - - Email - + + Email @@ -199,4 +200,3 @@ export const EditProfile = () => { ) } - From 89d2580264fc7b25b5ef03955ac18dd12bfec6bc Mon Sep 17 00:00:00 2001 From: Edwin Noriega Date: Thu, 29 Aug 2024 17:18:31 -0600 Subject: [PATCH 25/29] feat: Update logout behavior and navigation in EditProfile screen --- .../mobile/react-native/src/screens/settings/edit-profile.tsx | 1 + .../clients/mobile/react-native/src/services/user/hooks.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/edit-profile.tsx b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/edit-profile.tsx index 9bfb9cd86..5d18a31d5 100644 --- a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/edit-profile.tsx +++ b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/edit-profile.tsx @@ -80,6 +80,7 @@ export const EditProfile = () => { mutationFn: userApi.remove, onSuccess: () => { logout() + navio.stacks.setRoot('AuthStack') }, onError: () => { Alert.alert('Error', "Couldn't delete your account. Please try again later.", [ diff --git a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/services/user/hooks.ts b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/services/user/hooks.ts index 7b395360c..b7266b631 100644 --- a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/services/user/hooks.ts +++ b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/services/user/hooks.ts @@ -32,7 +32,7 @@ export const useLogout = () => { mutationFn: userApi.csc.logout, onSettled: () => { useAuth.getState().actions.clearAuth() - queryClient.invalidateQueries({ queryKey: ['user'] }) + queryClient.invalidateQueries({ queryKey: userQueries.all() }) }, }) } \ No newline at end of file From 8355dbee319641c0f2d49ff4de483e97b1c93c5a Mon Sep 17 00:00:00 2001 From: Edwin Noriega Date: Thu, 29 Aug 2024 17:20:43 -0600 Subject: [PATCH 26/29] feat: Add cancel option to delete account alert in EditProfile screen --- .../mobile/react-native/src/screens/settings/edit-profile.tsx | 2 -- .../mobile/react-native/src/screens/settings/main-settings.tsx | 1 - 2 files changed, 3 deletions(-) diff --git a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/edit-profile.tsx b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/edit-profile.tsx index 5d18a31d5..a7c652c26 100644 --- a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/edit-profile.tsx +++ b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/edit-profile.tsx @@ -86,7 +86,6 @@ export const EditProfile = () => { Alert.alert('Error', "Couldn't delete your account. Please try again later.", [ { text: 'Ok', - onPress: () => console.log('Ok Pressed'), }, ]) }, @@ -105,7 +104,6 @@ export const EditProfile = () => { [ { text: 'Cancel', - onPress: () => console.log('Cancel Pressed'), style: 'cancel', }, { diff --git a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/main-settings.tsx b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/main-settings.tsx index 808cb9d7c..ee5ef36b9 100644 --- a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/main-settings.tsx +++ b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/main-settings.tsx @@ -159,7 +159,6 @@ export const Settings = () => { Alert.alert('Log out', 'Are you sure you want to log out?', [ { text: 'Cancel', - onPress: () => console.log('Cancel Pressed'), style: 'cancel', }, {text: 'Log out', onPress: handleLogout}, From 65974df3dabcfa00893c7b16b5038d26bdfcac2e Mon Sep 17 00:00:00 2001 From: Edwin Noriega Date: Fri, 30 Aug 2024 11:43:20 -0600 Subject: [PATCH 27/29] feat: Remove unused patchUser service call in user API The patchUser service call in the user API is no longer used and can be safely removed. This commit removes the patchUser service call from the user API file. Code changes: - Removed patchUser service call in user API --- .../mobile/react-native/src/services/user/api.ts | 14 ++------------ .../react-native/src/services/user/models.ts | 14 -------------- 2 files changed, 2 insertions(+), 26 deletions(-) diff --git a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/services/user/api.ts b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/services/user/api.ts index 05e4b227d..5481e9267 100644 --- a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/services/user/api.ts +++ b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/services/user/api.ts @@ -1,7 +1,7 @@ import { createApi, createCustomServiceCall } from '@thinknimble/tn-models' import { z } from 'zod' import { axiosInstance } from '../axios-instance' -import { forgotPasswordShape, loginShape, partialUserShape, userCreateShape, userShape } from './models' +import { forgotPasswordShape, loginShape, userCreateShape, userShape } from './models' const login = createCustomServiceCall({ inputShape: loginShape, @@ -34,16 +34,6 @@ const logout = createCustomServiceCall(async ({ client }) => { return client.post(`/logout/`) }) -const patchUser = createCustomServiceCall({ - inputShape: partialUserShape, - outputShape: userShape, - cb: async ({ client, slashEndingBaseUri, input, utils }) => { - const { id, ...body } = utils.toApi(input) - const res = await client.put(`${slashEndingBaseUri}${id}/`, body) - return utils.fromApi(res.data) - }, -}) - export const userApi = createApi({ client: axiosInstance, baseUri: '/users/', @@ -51,5 +41,5 @@ export const userApi = createApi({ create: userCreateShape, entity: userShape, }, - customCalls: { login, requestPasswordResetCode, resetPassword, logout, patchUser }, + customCalls: { login, requestPasswordResetCode, resetPassword, logout }, }) diff --git a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/services/user/models.ts b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/services/user/models.ts index 8d45a5226..c59cc3eb2 100644 --- a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/services/user/models.ts +++ b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/services/user/models.ts @@ -40,23 +40,9 @@ export const loginShape = { export type LoginShape = GetInferredFromRaw -type ZodOptionalRecord = { - [TKey in keyof T]: z.ZodOptional -} - -export const zodPartialize = (zShape: T) => { - return Object.fromEntries( - Object.entries(zShape).map(([k, v]) => { - return [k, v.optional()] - }), - ) as ZodOptionalRecord -} - export const fullNameZod = z.string().refine( (value) => { return value.split(' ').filter(Boolean).length >= 2 }, { message: 'Please provide a full name (first and last name)' }, ) - -export const partialUserShape = { ...zodPartialize(userShape), id: z.string().uuid() } From 411bfd6c228c9368e818819a7a2ee43f0032fef5 Mon Sep 17 00:00:00 2001 From: Edwin Noriega Date: Fri, 30 Aug 2024 11:43:28 -0600 Subject: [PATCH 28/29] feat: Add useAuth hook to EditProfile screen This commit adds the useAuth hook to the EditProfile screen in order to retrieve the userId. The userId is used to retrieve the user's full name and display it in the EditProfile screen. This improves the user experience by pre-filling the full name input field with the user's current full name. Code changes: - Added useAuth hook to retrieve userId - Pre-filled full name input field with user's current full name --- .../src/screens/settings/edit-profile.tsx | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/edit-profile.tsx b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/edit-profile.tsx index a7c652c26..0329e9ecf 100644 --- a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/edit-profile.tsx +++ b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/edit-profile.tsx @@ -11,6 +11,7 @@ import { Ionicons } from '@expo/vector-icons' import colors from '@utils/colors' import { BButton } from '@components/Button' import { isAxiosError } from 'axios' +import { useAuth } from '@stores/auth' const Separator = () => { return ( @@ -26,6 +27,7 @@ export const EditProfile = () => { navio.goBack() } const { data: user } = useUser() + const { userId } = useAuth() const [fullName, setFullName] = useState(user?.fullName ?? '') const [errors, setErrors] = useState() @@ -39,11 +41,13 @@ export const EditProfile = () => { const qClient = useQueryClient() const { mutate: save, isPending: isSaving } = useMutation({ - mutationFn: userApi.csc.patchUser, + mutationFn: userApi.update, onMutate: async () => { - const userSnapshot = qClient.getQueryData(userQueries.all())?.fullName - await qClient.cancelQueries({ queryKey: userQueries.all() }) - await qClient.setQueryData(userQueries.all(), (input?: UserShape) => { + const userSnapshot = qClient.getQueryData( + userQueries.retrieve(userId).queryKey, + )?.fullName + await qClient.cancelQueries({ queryKey: userQueries.retrieve(userId).queryKey }) + qClient.setQueryData(userQueries.retrieve(userId).queryKey, (input?: UserShape) => { return input ? { ...input } : undefined }) return { userSnapshot } @@ -53,8 +57,10 @@ export const EditProfile = () => { }, onError: (e, _, context) => { //rollback update - qClient.setQueryData(userQueries.all(), (input?: UserShape) => { - return input ? { ...input, fullName: context?.userSnapshot ?? '' } : undefined + qClient.setQueryData(userQueries.retrieve(userId).queryKey, (input?: UserShape) => { + return input + ? { ...input, fullName: context?.userSnapshot ?? user?.fullName ?? '' } + : undefined }) if (isAxiosError(e)) { From 4724cc86461804ad8e7ed282dbcdf02a3ffe045b Mon Sep 17 00:00:00 2001 From: Edwin Noriega Date: Fri, 30 Aug 2024 11:44:46 -0600 Subject: [PATCH 29/29] feat: Update user hooks import in user service This commit updates the import statements for user hooks in the user service file. The import paths have been changed to reflect the new file structure. This ensures that the user hooks are correctly imported and used in the user service. Code changes: - Updated import paths for user hooks in user service --- .../clients/mobile/react-native/src/services/user/hooks.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/services/user/hooks.ts b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/services/user/hooks.ts index b7266b631..dc4b2a4e6 100644 --- a/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/services/user/hooks.ts +++ b/{{cookiecutter.project_slug}}/clients/mobile/react-native/src/services/user/hooks.ts @@ -6,8 +6,9 @@ import { useEffect } from 'react' import { useQuery, useMutation } from '@tanstack/react-query' import { useAuth } from '@stores/auth' -import { userApi, userQueries } from '.' import { queryClient } from '@utils/query-client' +import { userQueries } from './queries' +import { userApi } from './api' export const useUser = () => { const userId = useAuth.use.userId()