-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'main' into fix/remove-mobile-files
- Loading branch information
Showing
18 changed files
with
581 additions
and
23 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
23 changes: 23 additions & 0 deletions
23
...cutter.project_slug}}/clients/mobile/react-native/src/components/contact-email-button.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 ( | ||
<Bounceable onPress={handleSendMail}> | ||
<Text className="text-lg text-center font-primary-bold"> | ||
{supportEmail} | ||
</Text> | ||
</Bounceable> | ||
) | ||
} |
22 changes: 22 additions & 0 deletions
22
{{cookiecutter.project_slug}}/clients/mobile/react-native/src/components/container.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 ( | ||
<MultiPlatformSafeAreaView safeAreaClassName={`h-full flex-1 flex-grow ${containerClassName}`}> | ||
<View | ||
className={`flex-1 flex-grow ${ | ||
hasHorizontalPadding ? 'px-4' : '' | ||
} ${innerContainerClassName}`} | ||
> | ||
{children} | ||
</View> | ||
</MultiPlatformSafeAreaView> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
50 changes: 50 additions & 0 deletions
50
...okiecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/contact-us.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 ( | ||
<Container> | ||
<View className="items-center"> | ||
<BounceableWind | ||
onPress={() => { | ||
navio.goBack() | ||
}} | ||
contentContainerClassName="absolute left-0 top-0" | ||
> | ||
<Ionicons size={26} name="chevron-back" color={colors.grey[280]} /> | ||
</BounceableWind> | ||
<Text className="text-xl font-primary-medium"> | ||
Contact Us | ||
</Text> | ||
</View> | ||
<View className="flex-grow items-center justify-center"> | ||
<View className="pt-10"> | ||
<Text className="text-grey-280 text-2xl text-center font-primary-bold"> | ||
Needing help? | ||
</Text> | ||
</View> | ||
<View className="pt-3 justify-center item-center"> | ||
<Text className="text-grey-280 text-lg text-center">Reach out to us at</Text> | ||
<ContactEmailButton /> | ||
</View> | ||
</View> | ||
<View> | ||
<BButton | ||
label="BACK TO SETTINGS" | ||
variant="primary" | ||
onPress={() => { | ||
navio.goBack() | ||
}} | ||
/> | ||
</View> | ||
</Container> | ||
) | ||
} |
207 changes: 207 additions & 0 deletions
207
...iecutter.project_slug}}/clients/mobile/react-native/src/screens/settings/edit-profile.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,207 @@ | ||
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' | ||
import { useAuth } from '@stores/auth' | ||
|
||
const Separator = () => { | ||
return ( | ||
<View className="py-3"> | ||
<View className="w-full h-[0.5px] bg-grey-160 rounded-full" /> | ||
</View> | ||
) | ||
} | ||
|
||
export const EditProfile = () => { | ||
const navio = getNavio() | ||
const onCancel = () => { | ||
navio.goBack() | ||
} | ||
const { data: user } = useUser() | ||
const { userId } = useAuth() | ||
const [fullName, setFullName] = useState(user?.fullName ?? '') | ||
const [errors, setErrors] = useState<string[] | undefined>() | ||
|
||
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.update, | ||
onMutate: async () => { | ||
const userSnapshot = qClient.getQueryData<UserShape>( | ||
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 } | ||
}, | ||
onSuccess: () => { | ||
qClient.invalidateQueries({ queryKey: userQueries.all() }) | ||
}, | ||
onError: (e, _, context) => { | ||
//rollback update | ||
qClient.setQueryData(userQueries.retrieve(userId).queryKey, (input?: UserShape) => { | ||
return input | ||
? { ...input, fullName: context?.userSnapshot ?? user?.fullName ?? '' } | ||
: 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() | ||
navio.stacks.setRoot('AuthStack') | ||
}, | ||
onError: () => { | ||
Alert.alert('Error', "Couldn't delete your account. Please try again later.", [ | ||
{ | ||
text: 'Ok', | ||
}, | ||
]) | ||
}, | ||
}) | ||
|
||
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', | ||
style: 'cancel', | ||
}, | ||
{ | ||
text: 'Delete', | ||
onPress: () => { | ||
if (!user) return | ||
deleteUser(user?.id) | ||
}, | ||
}, | ||
], | ||
) | ||
} | ||
|
||
if (!user) return <></> //never | ||
|
||
return ( | ||
<Container> | ||
<View className="items-center justify-between flex-row"> | ||
<Bounceable onPress={onCancel} disabled={isDeleting || isLoggingOut || isSaving}> | ||
<Ionicons size={26} name="chevron-back" color={colors.grey[280]} /> | ||
</Bounceable> | ||
<Text className="text-xl">Edit Profile</Text> | ||
<Bounceable | ||
onPress={handleSave} | ||
disabled={isDeleting || isLoggingOut || isSaving || (unsavedChanges && !isValid)} | ||
> | ||
{isSaving ? ( | ||
<ActivityIndicator /> | ||
) : ( | ||
<Text | ||
className={ | ||
'text-xl ' + (unsavedChanges && isValid ? 'text-grey-280' : 'text-grey-180') | ||
} | ||
> | ||
Save | ||
</Text> | ||
)} | ||
</Bounceable> | ||
</View> | ||
<ScrollView> | ||
<View className="pt-10"> | ||
<View> | ||
<View className="justify-center flex-1"> | ||
<Text className="text-grey-280 text-lg font-primary-bold">Full Name</Text> | ||
</View> | ||
<Separator /> | ||
<View className="justify-center flex-3"> | ||
<View> | ||
<TextInput | ||
className="text-grey-280 text-lg pb-2.5" | ||
value={fullName} | ||
onChangeText={handleFullNameChange} | ||
/> | ||
</View> | ||
</View> | ||
</View> | ||
{fullName && !isValid ? ( | ||
<View> | ||
<ErrorMessage> | ||
{parsedName.error.issues.map((i) => i.message).join(', ')} | ||
</ErrorMessage> | ||
</View> | ||
) : null} | ||
<Separator /> | ||
<View> | ||
<View className="flex"> | ||
<Text className="text-grey-280 text-lg font-primary-bold">Email</Text> | ||
</View> | ||
<Separator /> | ||
<View className="flex-3"> | ||
<Text className="text-disabled-gray text-lg">{user.email}</Text> | ||
</View> | ||
<Separator /> | ||
{errors?.map((error, idx) => ( | ||
<View className="py-3"> | ||
<ErrorMessage key={idx}>{error}</ErrorMessage> | ||
</View> | ||
))} | ||
</View> | ||
</View> | ||
</ScrollView> | ||
|
||
<BButton | ||
label="DELETE ACCOUNT" | ||
variant="primary" | ||
onPress={showWarningAlert} | ||
containerClassName="mb-7" | ||
isLoading={isDeleting || isLoggingOut || isSaving} | ||
buttonProps={{ | ||
disabled: isDeleting || isLoggingOut || isSaving, | ||
}} | ||
/> | ||
</Container> | ||
) | ||
} |
Oops, something went wrong.