Browse Source

Merge pull request 'Edição do perfil' (#1) from newFeature into master

Reviewed-on: https://git.quijaua.com.br/cemaden-educacao/WPD-MobileApp/pulls/1
master
rbantu 6 months ago
parent
commit
7be64ee4e8
  1. 7
      src/App.js
  2. 27
      src/app/api/auth.js
  3. 4
      src/app/auth/authClient.js
  4. 2
      src/app/components/DatePicker.js
  5. 7
      src/app/navigation/AccountNavigator.js
  6. 6
      src/app/screens/AccountScreen.js
  7. 7
      src/app/screens/PasswordRecoveryChangePswdScreen.js
  8. 2
      src/app/screens/PasswordRecoveryScreen.js
  9. 5
      src/app/screens/RegisterScreen.js
  10. 605
      src/app/screens/UpdateUserInfoScreen.js

7
src/App.js

@ -50,6 +50,12 @@ export default function App() {
});
}
function clearPluviometerStation() {
setPluviometerStation(undefined)
}
useEffect(() => {
async function getNetInfo() {
if (netInfo.isInternetReachable) {
@ -134,6 +140,7 @@ export default function App() {
value={{
user,
setUser,
clearPluviometerStation
}}
>
<CurrentLocationProvider>

27
src/app/api/auth.js

@ -47,7 +47,7 @@ function signup({
async function userPersonalData() {
const authToken = await authStorage.getToken();
const localClient = create({
baseURL: "https://wpd.brazilsouth.cloudapp.azure.com/auth/users",
baseURL: "https://wpdauth.pinear.com.br/users",
});
localClient.setHeader("Authorization", `Bearer ${authToken}`);
@ -61,7 +61,7 @@ async function userActivation(code) {
// console.log("USER NAME: " + username);
const localClient = create({
baseURL: "https://wpd.brazilsouth.cloudapp.azure.com/auth/users",
baseURL: "https://wpdauth.pinear.com.br/users",
});
localClient.setHeader("Authorization", `Bearer ${authToken}`);
@ -97,16 +97,32 @@ function loginByUsernamAnswers(username, secQuestionId, secQuestionAnswer) {
function updatePassword(authToken, username, password) {
const localClient = create({
baseURL: "https://wpd.brazilsouth.cloudapp.azure.com/auth/forgotpasswords",
baseURL: "https://wpdauth.pinear.com.br/forgotpasswords",
});
localClient.setHeader("Authorization", `Bearer ${authToken}`);
return localClient.post(
`/passwordupdatebyusername?username=${username}&newPassword=${password}`
);
}
async function UpdateUserInfo(authToken,defaultValue,formValues) {
const localClient = create({
baseURL: "https://wpdauth.pinear.com.br/users",
});
localClient.setHeader("Authorization", `Bearer ${authToken}`);
const body = {
...defaultValue,
...formValues
};
return localClient.put(`/update`, body);
}
export {
login,
@ -117,4 +133,5 @@ export {
existUsername,
loginByUsernamAnswers,
updatePassword,
UpdateUserInfo,
};

4
src/app/auth/authClient.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};

2
src/app/components/DatePicker.js

@ -121,7 +121,7 @@ export default function DatePicker(props) {
return moment(selectedDate).format("HH:mm");
}
}} //formatar a data e hora do selected date
display={Platform.OS === "ios" ? "spinner" : "default"}
display={"spinner"}
onChange={Platform.OS === "ios" ? onChange : androidOnChange}
/>
);

7
src/app/navigation/AccountNavigator.js

@ -11,6 +11,7 @@ import ActivateInstitutionCode from "../screens/ActivateInstitutionCode";
import ActivateInstitutionShowCode from "../screens/ActivateInstitutionShowCode";
import PasswordRecovery from "../screens/PasswordRecoveryScreen";
import PasswordRecoveryChangePswd from "../screens/PasswordRecoveryChangePswdScreen";
import UpdateUserInfoScreen from "../screens/UpdateUserInfoScreen";
const Stack = createNativeStackNavigator();
@ -92,6 +93,12 @@ const AccountNavigator = () => (
initialParams={{ authToken : "" }}
options={{ headerTitle: "" }}
/>
<Stack.Screen
name="UpdateUserInfoScreen"
component={UpdateUserInfoScreen}
initialParams={{ title : "Atualizar informações de cadastro" }}
options={{ headerTitle: "" }}
/>
<Stack.Screen

6
src/app/screens/AccountScreen.js

@ -129,12 +129,12 @@ function AccountScreen(props) {
const profileItems = [
{
icon: "lock",
icon: "account-settings",
show: isRegistered,
IconProvider: MaterialCommunityIcons,
title: "alterar senha",
title: "Atualizar informações de cadastro",
onPress: () => {
props.navigation.navigate("PasswordRecovery", { user: user });
props.navigation.navigate("UpdateUserInfoScreen", { user: user });
},
},
{

7
src/app/screens/PasswordRecoveryChangePswdScreen.js

@ -1,4 +1,4 @@
import React, { useState } from "react";
import React, { useContext, useState } from "react";
import { StyleSheet, View, Text } from "react-native";
import * as Yup from "yup";
@ -9,6 +9,7 @@ import PasswordFormField from "../components/forms/PasswordFormField";
import ConfirmationModal from "../components/ConfirmationModal";
import { updatePassword } from "../api/auth";
import { KeyboardAwareScrollView } from "react-native-keyboard-aware-scroll-view";
import { AuthContext } from "../auth/context";
const validationSchema = Yup.object().shape({
password: Yup.string()
@ -24,6 +25,7 @@ const validationSchema = Yup.object().shape({
export default function PasswordRecoveryChangePswd({ navigation, route }) {
const authToken = route.params.authToken;
const username = route.params.username;
const authContext = useContext(AuthContext);
const [confirmatioModalData, setConfirmatioModalData] = useState({
show: false,
@ -44,7 +46,8 @@ export default function PasswordRecoveryChangePswd({ navigation, route }) {
message: "Senha alterada com sucesso",
show: true,
onConfirmAction: () => {
navigation.navigate("Login");
authContext.setUser();
navigation.popToTop();
},
});
break;

2
src/app/screens/PasswordRecoveryScreen.js

@ -136,6 +136,8 @@ export default function PasswordRecovery({ navigation, route }) {
<ConfirmationModal
show={confirmatioModalData.show}
description={confirmatioModalData.message}
confirmationLabel="OK"
onConfirm={() => setConfirmatioModalData({ show: false })}
/>
<KeyboardAwareScrollView>

5
src/app/screens/RegisterScreen.js

@ -161,11 +161,12 @@ function StatePicker({ name }) {
}
function CityPicker({ name }) {
const { values } = useFormikContext();
const { values, setValues } = useFormikContext();
const state = values["state"];
useEffect(() => {
state && setItems(statesToCities[state].cities);
setValues({ ...values, city: "" });
}, [state]);
const [items, setItems] = useState([]);
@ -550,7 +551,7 @@ export default function RegisterScreen(props) {
<MaterialCommunityIconsCustom name="bank" />
<InstitutionPicker name="institution" />
</View>
<View style={{ display: 'none' }}>
<View>
<Text style={styles.labelStyle}>Nome da instituição</Text>
<View style={{ flexDirection: "column", flex: 1 }}>
<View style={{ ...styles.iconField, marginBottom: 12 }}>

605
src/app/screens/UpdateUserInfoScreen.js

@ -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,
},
});
Loading…
Cancel
Save