Skip to content

Commit

Permalink
App GoBarber Finished
Browse files Browse the repository at this point in the history
  • Loading branch information
rogerio410 committed Dec 26, 2020
1 parent 499b341 commit 1eb76d2
Show file tree
Hide file tree
Showing 8 changed files with 265 additions and 9 deletions.
6 changes: 6 additions & 0 deletions gobarber/appgobarber/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,8 @@ PODS:
- React-cxxreact (= 0.63.3)
- React-jsi (= 0.63.3)
- React-jsinspector (0.63.3)
- react-native-image-picker (3.1.2):
- React
- react-native-safe-area-context (3.1.9):
- React-Core
- React-RCTActionSheet (0.63.3):
Expand Down Expand Up @@ -353,6 +355,7 @@ DEPENDENCIES:
- React-jsi (from `../node_modules/react-native/ReactCommon/jsi`)
- React-jsiexecutor (from `../node_modules/react-native/ReactCommon/jsiexecutor`)
- React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector`)
- react-native-image-picker (from `../node_modules/react-native-image-picker`)
- react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`)
- React-RCTActionSheet (from `../node_modules/react-native/Libraries/ActionSheetIOS`)
- React-RCTAnimation (from `../node_modules/react-native/Libraries/NativeAnimation`)
Expand Down Expand Up @@ -419,6 +422,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native/ReactCommon/jsiexecutor"
React-jsinspector:
:path: "../node_modules/react-native/ReactCommon/jsinspector"
react-native-image-picker:
:path: "../node_modules/react-native-image-picker"
react-native-safe-area-context:
:path: "../node_modules/react-native-safe-area-context"
React-RCTActionSheet:
Expand Down Expand Up @@ -485,6 +490,7 @@ SPEC CHECKSUMS:
React-jsi: df07aa95b39c5be3e41199921509bfa929ed2b9d
React-jsiexecutor: b56c03e61c0dd5f5801255f2160a815f4a53d451
React-jsinspector: 8e68ffbfe23880d3ee9bafa8be2777f60b25cbe2
react-native-image-picker: a2b7ee36dabe1e511dab6fb79918633a4fd3d7ce
react-native-safe-area-context: b6e0e284002381d2ff29fa4fff42b4d8282e3c94
React-RCTActionSheet: 53ea72699698b0b47a6421cb1c8b4ab215a774aa
React-RCTAnimation: 1befece0b5183c22ae01b966f5583f42e69a83c2
Expand Down
2 changes: 2 additions & 0 deletions gobarber/appgobarber/ios/appgobarber/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -59,5 +59,7 @@
<string>RobotoSlab-Regular.ttf</string>
<string>Feather.ttf</string>
</array>
<key>NSPhotoLibraryUsageDescription</key>
<string>This app requires access to the photo library.</string>
</dict>
</plist>
1 change: 1 addition & 0 deletions gobarber/appgobarber/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"react": "16.13.1",
"react-native": "0.63.3",
"react-native-gesture-handler": "^1.8.0",
"react-native-image-picker": "^3.1.2",
"react-native-iphone-x-helper": "^1.3.1",
"react-native-reanimated": "^1.13.2",
"react-native-safe-area-context": "^3.1.9",
Expand Down
6 changes: 3 additions & 3 deletions gobarber/appgobarber/src/components/Input/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { useField } from '@unform/core'
interface InputProps extends TextInputProps {
name: string
icon: string
containerStyle?: {}
}

interface InputValueReference {
Expand All @@ -17,7 +18,7 @@ interface InputRef {
focus(): void
}

const Input: React.ForwardRefRenderFunction<InputRef, InputProps> = ({ name, icon, ...props }, ref) => {
const Input: React.ForwardRefRenderFunction<InputRef, InputProps> = ({ name, icon, containerStyle = {}, ...props }, ref) => {

const { fieldName, defaultValue = '', error, registerField } = useField(name)
const inputValueRef = useRef<InputValueReference>({ value: defaultValue })
Expand Down Expand Up @@ -55,8 +56,7 @@ const Input: React.ForwardRefRenderFunction<InputRef, InputProps> = ({ name, ico
}, [fieldName, registerField])

return (

<Container isFocused={isFocused} isErrored={!!error}>
<Container style={containerStyle} isFocused={isFocused} isErrored={!!error}>
<Icon name={icon} size={20} color={isFocused || isFilled ? "#ff9000" : "#666360"} />
<TextInput
ref={inputElementRef}
Expand Down
15 changes: 14 additions & 1 deletion gobarber/appgobarber/src/context_hooks/AuthContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ interface AuthContextData {
user: User
signIn(credentials: SignInCredentials): Promise<void>
signOut(): void
updateUser(user: User): Promise<void>
loading: boolean
}

Expand Down Expand Up @@ -79,8 +80,20 @@ export const AuthProvider: React.FC = ({ children }) => {
setData({} as AuthState)
}, [])

const updateUser = useCallback(
async (user: User) => {
await AsyncStorage.setItem('@GoBarber:user', JSON.stringify(user))

setData({
token: data.token,
user
})
},
[setData, data.token]
)

return (
<AuthContext.Provider value={{ user: data.user, signIn, signOut, loading }}>
<AuthContext.Provider value={{ user: data.user, signIn, signOut, updateUser, loading }}>
{children}
</AuthContext.Provider>
)
Expand Down
205 changes: 200 additions & 5 deletions gobarber/appgobarber/src/pages/Profile/index.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,208 @@
import React from 'react'
import { View } from 'react-native'
import React, { useCallback, useRef } from 'react'
import { launchCamera, launchImageLibrary } from 'react-native-image-picker';
import { View, KeyboardAvoidingView, Platform, ScrollView, TextInput, Alert } from 'react-native'
import Input from '../../components/Input'
import Button from '../../components/Button'
import { Container, Title, UserAvatarButton, UserAvatar, BackButton } from './styles'
import { useNavigation } from '@react-navigation/native'
import { Form } from '@unform/mobile'
import * as Yup from 'yup'
import Icon from 'react-native-vector-icons/Feather'
import getValidationErrors from '../../utils/getValidationErrors'
import { FormHandles } from '@unform/core'
import api from '../../services/api'
import { useAuth } from '../../context_hooks/AuthContext'

interface ProfileFormData {
name: string
email: string
password: string
old_password: string
password_confirmation: string
}

const Profile: React.FC = () => {
const { user, updateUser } = useAuth()
const navigation = useNavigation()
const formRef = useRef<FormHandles>(null)
const emailInputRef = useRef<TextInput>(null)
const oldPasswordInputRef = useRef<TextInput>(null)
const passwordInputRef = useRef<TextInput>(null)
const confirmPasswordInputRef = useRef<TextInput>(null)

const handleUpdateProfile = useCallback(
async (data: ProfileFormData) => {
try {
formRef.current?.setErrors({})

const schema = Yup.object().shape({
name: Yup.string().required('Nome obrigatório'),
email: Yup.string()
.required('E-mail obrigatório')
.email('Digite um email válido'),
old_password: Yup.string(),
password: Yup.string().when('old_password', {
is: val => !!val.length,
then: Yup.string().required('Campo obrigatório'),
otherwise: Yup.string()
}),
password_confirmation: Yup.string()
.when('old_password', {
is: val => !!val.length,
then: Yup.string().required('Campo obrigatório'),
otherwise: Yup.string()
})
.oneOf([Yup.ref('password'), undefined], 'Confirmação incorreta!')
})

await schema.validate(data, {
abortEarly: false
})

const { name, email, old_password } = data

let response

if (old_password) {
response = await api.put('/profile', data)
} else {
const formData = { name, email }
response = await api.put('/profile', formData)
}

return (
<View>
</ View >
updateUser(response.data)
Alert.alert('Perfil atualizado com sucesso')

navigation.goBack()
} catch (error) {
if (error instanceof Yup.ValidationError) {
const errors = getValidationErrors(error)
formRef.current?.setErrors(errors)
return
}

Alert.alert('Error na Atualização do Perfil',
'Ocorreu um erro ao atualizar perfil, verifique os dados'
)
}
},
[navigation, updateUser]
)

const handleUpdateAvatar = useCallback(() => {
launchImageLibrary({
mediaType: 'photo'
}, response => {
if (response.didCancel) {
return
}

if (response.error) {
Alert.alert('Erro ao selecionar foto', response.error);
return
}

const data = new FormData()
data.append('avatar', {
uri: response.uri,
name: `${user.id}.jpg`,
type: 'image/jpeg',
})

api.patch('/users/avatar', data).then(apiResponse => {
updateUser(apiResponse.data)

Alert.alert('Avatar atualizado')
})

})
}, [updateUser, user.id])

const handleGoBack = useCallback(() => {
navigation.goBack()
}, [navigation])

return (<>
<KeyboardAvoidingView
style={{ flex: 1 }}
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
enabled
>
<ScrollView keyboardShouldPersistTaps="handled" contentContainerStyle={{ flex: 1 }}>
<Container>
<BackButton onPress={handleGoBack}>
<Icon name="chevron-left" size={24} color="#999591" />
</BackButton>
<UserAvatarButton onPress={handleUpdateAvatar}>
<UserAvatar source={{ uri: user.avatar_url }} />
</UserAvatarButton>

<View>
<Title>Meu Perfil</Title>
</View>

<Form initialData={user} onSubmit={handleUpdateProfile} ref={formRef}>
<Input
autoCapitalize='words'
name="name"
icon="user"
placeholder="Nome"
returnKeyType="next"
onSubmitEditing={() => {
emailInputRef.current?.focus()
}} />

<Input
ref={emailInputRef}
keyboardType='email-address'
autoCorrect={false}
autoCapitalize='none'
name="email"
icon="mail"
placeholder="E-mail"
returnKeyType="next"
onSubmitEditing={() => {
oldPasswordInputRef.current?.focus()
}} />

<Input
ref={oldPasswordInputRef}
secureTextEntry
name="old_password"
icon="lock"
textContentType="newPassword"
placeholder="Senha atual"
returnKeyType="next"
containerStyle={{ marginTop: 16 }}
onSubmitEditing={() => { passwordInputRef.current?.focus() }} />

<Input
ref={passwordInputRef}
secureTextEntry
name="password"
icon="lock"
textContentType="newPassword"
placeholder="Nova Senha"
returnKeyType="next"
onSubmitEditing={() => { confirmPasswordInputRef.current?.focus() }} />

<Input
ref={confirmPasswordInputRef}
secureTextEntry
name="password_confirmation"
icon="lock"
textContentType="newPassword"
placeholder="Confirmar Senha"
returnKeyType="send"
onSubmitEditing={() => { formRef.current?.submitForm() }} />
</Form>

<Button onPress={() => { formRef.current?.submitForm() }}>Confirmar mudanças</Button>

</Container>
</ScrollView>
</KeyboardAvoidingView>
</>)
}

export default Profile
34 changes: 34 additions & 0 deletions gobarber/appgobarber/src/pages/Profile/styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import styled from 'styled-components/native'
import { getBottomSpace } from 'react-native-iphone-x-helper'
import { Platform } from 'react-native'

export const Container = styled.View`
flex: 1;
/* align-items: center; */
justify-content: center;
padding: 0 30px ${Platform.OS === 'android' ? 120 : 40}px;
`

export const BackButton = styled.TouchableOpacity`
margin-top: 40px;
`

export const Title = styled.Text`
font-size: 24px;
color: #f3ede8;
font-family:'RobotoSlab-Medium';
margin: 24px 0;
`

export const UserAvatarButton = styled.TouchableOpacity`
margin-top: 32px;
`

export const UserAvatar = styled.Image`
width: 186px;
height: 186px;
border-radius: 98px;
align-self: center;
`


5 changes: 5 additions & 0 deletions gobarber/appgobarber/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5882,6 +5882,11 @@ react-native-gesture-handler@^1.8.0:
invariant "^2.2.4"
prop-types "^15.7.2"

react-native-image-picker@^3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/react-native-image-picker/-/react-native-image-picker-3.1.2.tgz#f8dda5f8005aa033fd45d77ccbd04d509e783e0c"
integrity sha512-aUmzRL7D1n8wEs3b9EwRNh0wiyaPSI/WX7uzqUB9BKxN0lucADECrzLeXOdKIJ6kg4h6+Qe/u81kz1WRh+XIFA==

react-native-iphone-x-helper@^1.3.0, react-native-iphone-x-helper@^1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/react-native-iphone-x-helper/-/react-native-iphone-x-helper-1.3.1.tgz#20c603e9a0e765fd6f97396638bdeb0e5a60b010"
Expand Down

0 comments on commit 1eb76d2

Please sign in to comment.