Browse Source
Merge pull request 'Edição do perfil' (#1) from newFeature into master
Merge pull request 'Edição do perfil' (#1) from newFeature into master
Reviewed-on: https://git.quijaua.com.br/cemaden-educacao/WPD-MobileApp/pulls/1master
rbantu
8 months ago
10 changed files with 657 additions and 15 deletions
-
7src/App.js
-
27src/app/api/auth.js
-
4src/app/auth/authClient.js
-
2src/app/components/DatePicker.js
-
7src/app/navigation/AccountNavigator.js
-
6src/app/screens/AccountScreen.js
-
7src/app/screens/PasswordRecoveryChangePswdScreen.js
-
2src/app/screens/PasswordRecoveryScreen.js
-
5src/app/screens/RegisterScreen.js
-
605src/app/screens/UpdateUserInfoScreen.js
@ -1,11 +1,11 @@ |
|||
import { create } from "apisauce"; |
|||
|
|||
const authClient = create({ |
|||
baseURL: "https://wpd.brazilsouth.cloudapp.azure.com/auth/users", |
|||
baseURL: "https://wpdauth.pinear.com.br/users", |
|||
}); |
|||
|
|||
const authChangePswdClient = create({ |
|||
baseURL: "https://wpd.brazilsouth.cloudapp.azure.com/auth/forgotpasswords" |
|||
baseURL: "https://wpdauth.pinear.com.br/forgotpasswords" |
|||
}) |
|||
|
|||
export { authClient, authChangePswdClient}; |
@ -0,0 +1,605 @@ |
|||
import * as Yup from "yup"; |
|||
import colors from '../config/colors'; |
|||
import moment from "moment"; |
|||
import Screen from '../components/Screen'; |
|||
import SearchablePicker from '../components/SearchablePicker'; |
|||
import FormDatePicker from '../components/forms/FormDatePicker'; |
|||
import institutions from "../assets/institutions"; |
|||
import defaultStyles from "../config/styles"; |
|||
import constants from "../config/constants"; |
|||
import ConfirmationModal from '../components/ConfirmationModal'; |
|||
import React, { useContext, useEffect, useState } from 'react'; |
|||
import { Modal, ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; |
|||
import { Form, FormField, SubmitButton } from '../components/forms'; |
|||
import { UpdateUserInfo, login, userPersonalData } from '../api/auth'; |
|||
import { dimensions } from '../config/dimensions'; |
|||
import { MaterialCommunityIcons } from "@expo/vector-icons"; |
|||
import { useFormikContext } from 'formik'; |
|||
import { states, statesToCities } from "../assets/cities_states"; |
|||
import PasswordFormField from "../components/forms/PasswordFormField"; |
|||
import storage from "../auth/storage"; |
|||
import { AuthContext } from "../auth/context"; |
|||
|
|||
//#region PICKERS
|
|||
|
|||
function InstitutionNamePicker({ name }) { |
|||
const { values } = useFormikContext(); |
|||
const state = values["state"]; |
|||
const instType = values["institution"]; |
|||
const [items, setItems] = useState([]); |
|||
|
|||
useEffect(() => { |
|||
try { |
|||
if (state && instType) { |
|||
const insts = institutions[state] && institutions[state][instType]; |
|||
insts ? setItems(insts) : setItems([]); |
|||
} |
|||
} catch (e) { |
|||
console.log(e); |
|||
} |
|||
}, [state, instType]); |
|||
|
|||
return ( |
|||
<SearchablePicker |
|||
name={name} |
|||
items={items} |
|||
setItems={setItems} |
|||
formPlaceholder={"Selecione o nome da instituição"} |
|||
doubleItemLine={true} |
|||
nothingToShow={ |
|||
institutions?.state?.instType |
|||
? "Não encontramos nada com esse termo" |
|||
: state && instType |
|||
? `Nenhuma instituição do tipo ${constants.institutionMap[instType]} no ${constants.statesMap[state]}` |
|||
: "Selecione o Estado e o tipo da instituição primeiro" |
|||
} |
|||
searchPlaceholder={"Busca..."} |
|||
/> |
|||
); |
|||
} |
|||
|
|||
function RolePicker({ name }) { |
|||
const [items, setItems] = useState([ |
|||
{ value: "ROLE_INSTITUTION", label: "Responsável" }, |
|||
{ value: "ROLE_CLIENT", label: "Não responsável" }, |
|||
]); |
|||
|
|||
return ( |
|||
<SearchablePicker |
|||
name={name} |
|||
items={items} |
|||
setItems={setItems} |
|||
formPlaceholder={"Selecione o vínculo institucional"} |
|||
searchPlaceholder={"Busca..."} |
|||
/> |
|||
); |
|||
} |
|||
|
|||
function MaterialCommunityIconsCustom({ |
|||
name, |
|||
color = colors.primary, |
|||
size = 25, |
|||
}) { |
|||
return ( |
|||
<View justifyContent={"center"} height={48}> |
|||
<MaterialCommunityIcons name={name} size={size} color={color} /> |
|||
</View> |
|||
); |
|||
} |
|||
|
|||
function CityPicker({ name }) { |
|||
const [items, setItems] = useState([]); |
|||
const [isInitialRender, setIsInitialRender] = useState(true); |
|||
|
|||
const { values, setValues } = useFormikContext(); |
|||
|
|||
const state = values["state"]; |
|||
|
|||
useEffect(() => { |
|||
if (isInitialRender) { |
|||
state && setItems(statesToCities[state].cities); |
|||
setIsInitialRender(false); |
|||
} else { |
|||
state && setItems(statesToCities[state].cities); |
|||
setValues({ ...values, city: "" }); |
|||
} |
|||
}, [state]); |
|||
|
|||
return ( |
|||
<SearchablePicker |
|||
name={name} |
|||
items={items} |
|||
setItems={setItems} |
|||
formPlaceholder={"Selecione a sua cidade"} |
|||
nothingToShow={ |
|||
state |
|||
? "Não encontramos nada com esse termo" |
|||
: "Selecione o Estado primeiro" |
|||
} |
|||
searchPlaceholder={"Busca..."} |
|||
/> |
|||
); |
|||
} |
|||
|
|||
function InstitutionPicker({ name }) { |
|||
const [items, setItems] = useState([ |
|||
{ value: "E", label: "Escola" }, |
|||
{ value: "D", label: "Defesa civil" }, |
|||
{ value: "N", label: "Não governamental" }, |
|||
{ value: "O", label: "Outra" }, |
|||
{ value: "X", label: "Nenhuma" }, |
|||
]); |
|||
return ( |
|||
<SearchablePicker |
|||
name={name} |
|||
items={items} |
|||
setItems={setItems} |
|||
formPlaceholder={"Selecione o tipo da instituição"} |
|||
searchPlaceholder={"Busca..."} |
|||
/> |
|||
); |
|||
} |
|||
|
|||
function GenderPicker({ name }) { |
|||
const [items, setItems] = useState([ |
|||
{ value: "F", label: "Feminino" }, |
|||
{ value: "M", label: "Masculino" }, |
|||
{ value: "N", label: "Prefiro não dizer" }, |
|||
]); |
|||
return ( |
|||
<SearchablePicker |
|||
name={name} |
|||
items={items} |
|||
setItems={setItems} |
|||
formPlaceholder={"Selecione o seu gênero"} |
|||
searchPlaceholder={"Busca..."} |
|||
/> |
|||
); |
|||
} |
|||
|
|||
function StatePicker({ name }) { |
|||
const [items, setItems] = useState(states); |
|||
return ( |
|||
<SearchablePicker |
|||
name={name} |
|||
items={items} |
|||
setItems={setItems} |
|||
formPlaceholder={"Selecione o seu estado"} |
|||
searchPlaceholder={"Busca..."} |
|||
/> |
|||
); |
|||
} |
|||
|
|||
function LocalDatePicker({ date, setDate, _moment }) { |
|||
const formatDate = () => date.format("DD/MM/YYYY"); |
|||
|
|||
return ( |
|||
<View flex={1}> |
|||
<FormDatePicker |
|||
onDateChange={(value) => setDate(value)} |
|||
minimumDate={new Date(moment().subtract(110, "year"))} |
|||
date={date} |
|||
> |
|||
<View style={{ flex: 1, paddingRight: 2, paddingLeft: 16 }}> |
|||
<View |
|||
style={{ |
|||
...defaultStyles.shadow, |
|||
height: 58, |
|||
paddingLeft: 12, |
|||
backgroundColor: colors.white, |
|||
borderColor: colors.grayBG, |
|||
borderWidth: 1, |
|||
padding: 5, |
|||
borderRadius: 6, |
|||
flexDirection: "row", |
|||
alignItems: "center", |
|||
}} |
|||
> |
|||
<Text |
|||
style={{ |
|||
color: colors.medium, |
|||
fontSize: 18, |
|||
}} |
|||
> |
|||
{date != _moment |
|||
? formatDate() |
|||
: "Selecione a data de nascimento"} |
|||
</Text> |
|||
</View> |
|||
</View> |
|||
</FormDatePicker> |
|||
</View> |
|||
); |
|||
} |
|||
|
|||
//#endregion
|
|||
|
|||
export default function UpdateUserInfoScreen({ route, navigation }) { |
|||
|
|||
//#region STATES
|
|||
const _moment = moment(); |
|||
const [date, setDate] = useState(_moment); |
|||
const [scroll, setScroll] = useState(); |
|||
const [showModal, setShowModal] = useState(false); |
|||
const [showpasswordModal, setShowPasswordModal] = useState(false); |
|||
const [formValue, setFormValue] = useState(null); |
|||
const [modalMessage, setModalMessage] = useState(""); |
|||
const user = route.params.user; |
|||
const title = route.params.title; |
|||
const authContext = useContext(AuthContext); |
|||
//#endregion
|
|||
|
|||
useEffect(() => { |
|||
setDate(moment(user.dateofborn)); |
|||
}, []); |
|||
|
|||
const defaultValue = { |
|||
active: user.active, |
|||
city: user.city, |
|||
dateofborn: user.dateofborn, |
|||
gender: user.gender, |
|||
id: user.id, |
|||
institution: user.institution, |
|||
institutiontype: user.institutiontype, |
|||
nickname: user.nickname, |
|||
securityanswer: user.securityanswer, |
|||
securityquestion: user.securityquestion, |
|||
state: user.state, |
|||
termsofusage: user.termsofusage, |
|||
}; |
|||
|
|||
const validationSchema = Yup.object().shape({ |
|||
name: Yup.string() |
|||
.required("O nome é obrigatório") |
|||
.matches(/[a-zA-Z]/, "O nome só pode conter letras"), |
|||
state: Yup.string().required("O estado é obrigatório"), |
|||
city: Yup.string().required("A cidade é obrigatória") |
|||
}); |
|||
|
|||
const passwordSchema = Yup.object().shape({ |
|||
password: Yup.string() |
|||
.required("A senha é obrigatória") |
|||
.min(8, "Senha muito curta, minimo 8 caracteres") |
|||
.matches(/[a-zA-Z]/, "A senha só pode conter letras"), |
|||
}); |
|||
|
|||
const ItensList = ({ icon, IconProvider, title, onPress }) => { |
|||
return ( |
|||
<View> |
|||
<TouchableOpacity disabled={false} onPress={onPress}> |
|||
<View |
|||
style={{ |
|||
marginVertical: 16, |
|||
flexDirection: "row", |
|||
alignItems: "center", |
|||
}} |
|||
> |
|||
<IconProvider name={icon} size={25} color={"#F23"} /> |
|||
<Text |
|||
style={{ |
|||
fontSize: 17, |
|||
marginLeft: 20, |
|||
textTransform: "uppercase", |
|||
fontWeight: "500", |
|||
color: "#F23" |
|||
}} |
|||
> |
|||
{title} |
|||
</Text> |
|||
<View |
|||
style={{ |
|||
alignItems: "flex-end", |
|||
flex: 1, |
|||
}} |
|||
> |
|||
<MaterialCommunityIcons |
|||
name={"chevron-right"} |
|||
size={25} |
|||
color="#F56" |
|||
/> |
|||
</View> |
|||
</View> |
|||
</TouchableOpacity> |
|||
</View> |
|||
) |
|||
}; |
|||
|
|||
const ListComponent = ({ navigation }) => { |
|||
|
|||
const profileItems = [ |
|||
{ |
|||
icon: "lock", |
|||
IconProvider: MaterialCommunityIcons, |
|||
title: "alterar senha", |
|||
onPress: () => { |
|||
navigation.navigate("PasswordRecovery", { user: user }); |
|||
}, |
|||
} |
|||
]; |
|||
|
|||
return ( |
|||
<View> |
|||
{profileItems.map( |
|||
({ icon, IconProvider, title, onPress }) => |
|||
<View key={title}> |
|||
<ItensList |
|||
icon={icon} |
|||
IconProvider={IconProvider} |
|||
title={title} |
|||
onPress={onPress} |
|||
/> |
|||
</View> |
|||
)} |
|||
</View> |
|||
) |
|||
} |
|||
|
|||
const PasswordModal = ({ children, show, onClose }) => { |
|||
return ( |
|||
<Modal |
|||
visible={show} |
|||
transparent={true} |
|||
animationType="fade" |
|||
onRequestClose={() => setShowModal(false)} |
|||
> |
|||
<View style={{ |
|||
flex: 1, |
|||
justifyContent: "center", |
|||
alignItems: "center", |
|||
backgroundColor: "rgba(0, 0, 0, 0.5)", |
|||
}}> |
|||
<View style={{ |
|||
width: "90%", |
|||
justifyContent: "center", |
|||
alignItems: "center", |
|||
paddingVertical: 10, |
|||
paddingHorizontal: 10, |
|||
backgroundColor: colors.lightestGray, |
|||
borderColor: colors.primary, |
|||
borderWidth: 2, |
|||
borderRadius: 12, |
|||
}}> |
|||
<TouchableOpacity style={{ position: "absolute", top: 10, right: 10 }} onPress={onClose}> |
|||
<MaterialCommunityIcons name="close-circle" size={24} color={colors.primary} /> |
|||
</TouchableOpacity> |
|||
{children} |
|||
</View> |
|||
</View> |
|||
</Modal> |
|||
) |
|||
} |
|||
|
|||
|
|||
const handleUpdatUserInfo = async (password) => { |
|||
|
|||
const username = user.username; |
|||
|
|||
const parsedForm = { |
|||
securityquestion: defaultValue.securityquestion, |
|||
securityanswer: defaultValue.securityanswer, |
|||
id: defaultValue.id, |
|||
termsofusage: defaultValue.termsofusage, |
|||
city: formValue.city, |
|||
gender: formValue.gender, |
|||
institution: formValue.institutionName, |
|||
institutiontype: formValue.institution, |
|||
nickname: formValue.name, |
|||
active: defaultValue.active, |
|||
dateofborn: date.format("YYYY-MM-DD"), |
|||
state: formValue.state, |
|||
}; |
|||
|
|||
try { |
|||
|
|||
const apiResponse = await login(username, password); |
|||
|
|||
const authToken = apiResponse.data; |
|||
|
|||
const result = await UpdateUserInfo(authToken, defaultValue, parsedForm); |
|||
|
|||
if (result.status == 200) { |
|||
|
|||
storage.setToken(authToken) |
|||
|
|||
const getPersonalData = await userPersonalData() |
|||
|
|||
authContext.clearPluviometerStation() |
|||
|
|||
authContext.setUser(getPersonalData.data); |
|||
|
|||
setShowModal(true); |
|||
setModalMessage("Informações atualizadas com sucesso"); |
|||
|
|||
} else { |
|||
setShowModal(true); |
|||
setModalMessage("Algum erro inesperado ocorreu"); |
|||
} |
|||
} catch (error) { |
|||
console.error('Erro ao obter token de autenticação:', error); |
|||
setShowModal(true); |
|||
setModalMessage("Algum erro inesperado ocorreu"); |
|||
} |
|||
|
|||
}; |
|||
|
|||
return ( |
|||
<Screen style={styles.container}> |
|||
<ConfirmationModal |
|||
show={showModal} |
|||
description={modalMessage} |
|||
confirmationLabel="OK" |
|||
onConfirm={() => { |
|||
setShowModal(false), |
|||
navigation.goBack(); |
|||
}} |
|||
/> |
|||
|
|||
<PasswordModal show={showpasswordModal} onClose={() => setShowPasswordModal(false)}> |
|||
<View style={{ width: "100%", height: 250, justifyContent: "space-evenly" }}> |
|||
<Form |
|||
initialValues={{ password: "" }} |
|||
validationSchema={passwordSchema} |
|||
onSubmit={({ password }) => { |
|||
handleUpdatUserInfo(password); |
|||
setShowPasswordModal(false); |
|||
}}> |
|||
<Text style={{ |
|||
fontSize: dimensions.text.secondary, |
|||
fontWeight: "bold", |
|||
textAlign: "left", |
|||
color: colors.secondary, |
|||
paddingLeft: 15 |
|||
}}>Senha*</Text> |
|||
<View style={{ paddingBottom: 24 }}> |
|||
<PasswordFormField |
|||
maxLength={20} |
|||
name="password" |
|||
placeholder="Senha" |
|||
/> |
|||
</View> |
|||
<SubmitButton title={"CONFIRMAR"} flex={1} backgroundColor={colors.primary} /> |
|||
</Form> |
|||
</View> |
|||
</PasswordModal> |
|||
|
|||
<Form |
|||
initialValues={{ |
|||
city: defaultValue.city, |
|||
gender: defaultValue.gender, |
|||
institutionName: defaultValue.institution, |
|||
institution: defaultValue.institutiontype, |
|||
name: defaultValue.nickname, |
|||
role: user.roles[0], |
|||
state: defaultValue.state, |
|||
}} |
|||
validationSchema={validationSchema} |
|||
onSubmit={(form) => { |
|||
setFormValue(form); |
|||
setShowPasswordModal(true) |
|||
}} |
|||
> |
|||
<ScrollView |
|||
scrollToOverflowEnabled={true} |
|||
ref={(ref) => { |
|||
setScroll(ref); |
|||
}} |
|||
style={{ paddingHorizontal: 20 }} |
|||
> |
|||
<Text style={styles.title}>{title}</Text> |
|||
{/*--------------------------------------- NAME ---------------------------------------*/} |
|||
<Text style={styles.labelStyle}>Apelido de usuário*</Text> |
|||
<View style={styles.iconField}> |
|||
<MaterialCommunityIconsCustom name="account" /> |
|||
<FormField |
|||
paddingRight={2} |
|||
flex={1} |
|||
maxLength={40} |
|||
name="name" |
|||
placeholder="Digite o apelido de usuário" |
|||
defaultValue={defaultValue.nickname} |
|||
/> |
|||
</View> |
|||
{/*--------------------------------------- DATEOFBORN ---------------------------------------*/} |
|||
<Text style={styles.labelStyle}>Data de nascimento:</Text> |
|||
<View style={styles.iconField}> |
|||
<MaterialCommunityIconsCustom name="calendar-today" /> |
|||
<LocalDatePicker |
|||
date={date} |
|||
setDate={setDate} |
|||
_moment={_moment} |
|||
/> |
|||
</View> |
|||
{/*--------------------------------------- GENDER ---------------------------------------*/} |
|||
<Text style={styles.labelStyle}>Gênero:</Text> |
|||
<View style={[styles.iconField]}> |
|||
<MaterialCommunityIconsCustom name="account" /> |
|||
<GenderPicker name="gender" /> |
|||
</View> |
|||
{/*--------------------------------------- STATE ---------------------------------------*/} |
|||
<Text style={styles.labelStyle}>Estado*:</Text> |
|||
<View style={[styles.iconField]}> |
|||
<MaterialCommunityIconsCustom name="map-marker" /> |
|||
<StatePicker name="state" /> |
|||
</View> |
|||
{/*--------------------------------------- CITY ---------------------------------------*/} |
|||
<Text style={styles.labelStyle}>Cidade*:</Text> |
|||
<View style={[styles.iconField]}> |
|||
<MaterialCommunityIconsCustom name="map-marker" /> |
|||
<CityPicker name={"city"} /> |
|||
</View> |
|||
{/*--------------------------------------- INSTITUTION ---------------------------------------*/} |
|||
<Text style={styles.labelStyle}>Tipo de instituição:</Text> |
|||
<View style={[styles.iconField]}> |
|||
<MaterialCommunityIconsCustom name="bank" /> |
|||
<InstitutionPicker name="institution" /> |
|||
</View> |
|||
{/* --------------------------------------- INSTITUTIONNAME ---------------------------------------*/} |
|||
<View> |
|||
<Text style={styles.labelStyle}>Nome da instituição</Text> |
|||
<View style={{ flexDirection: "column", flex: 1 }}> |
|||
<View style={{ ...styles.iconField, marginBottom: 12 }}> |
|||
<MaterialCommunityIconsCustom name="bank" /> |
|||
<InstitutionNamePicker name="institutionName" /> |
|||
</View> |
|||
<Text style={styles.warningText}> |
|||
O nome da instituição é fornecido pelo Cemaden Educação |
|||
</Text> |
|||
</View> |
|||
{/* --------------------------------------- ROLE ---------------------------------------*/} |
|||
<Text style={styles.labelStyle}>Vínculo institucional:</Text> |
|||
<View style={[styles.iconField]}> |
|||
<MaterialCommunityIconsCustom name="bank" /> |
|||
<RolePicker name="role" /> |
|||
</View> |
|||
</View> |
|||
|
|||
<ListComponent navigation={navigation} /> |
|||
|
|||
<SubmitButton |
|||
flex={1} |
|||
title="confirmar" |
|||
backgroundColor={colors.primary} |
|||
/> |
|||
</ScrollView> |
|||
</Form> |
|||
</Screen> |
|||
); |
|||
} |
|||
|
|||
const styles = StyleSheet.create({ |
|||
container: { |
|||
justifyContent: "center", |
|||
textAlign: "center", |
|||
}, |
|||
iconField: { |
|||
alignItems: "center", |
|||
width: "100%", |
|||
flex: 1, |
|||
flexDirection: "row", |
|||
marginTop: 12, |
|||
marginBottom: 24, |
|||
}, |
|||
title: { |
|||
fontSize: 22, |
|||
fontWeight: "bold", |
|||
textAlign: "center", |
|||
color: colors.black, |
|||
marginVertical: 24 |
|||
}, |
|||
textSubtitle: { |
|||
textAlign: "center", |
|||
fontSize: dimensions.text.tertiary, |
|||
}, |
|||
labelStyle: { |
|||
fontSize: dimensions.text.secondary, |
|||
fontWeight: "bold", |
|||
textAlign: "left", |
|||
color: colors.secondary, |
|||
}, |
|||
warningText: { |
|||
color: colors.primary, |
|||
fontSize: dimensions.text.primary, |
|||
textAlign: "left", |
|||
marginBottom: 24, |
|||
}, |
|||
}); |
Write
Preview
Loading…
Cancel
Save
Reference in new issue