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"; |
import { create } from "apisauce"; |
||||
|
|
||||
const authClient = create({ |
const authClient = create({ |
||||
baseURL: "https://wpd.brazilsouth.cloudapp.azure.com/auth/users", |
|
||||
|
baseURL: "https://wpdauth.pinear.com.br/users", |
||||
}); |
}); |
||||
|
|
||||
const authChangePswdClient = create({ |
const authChangePswdClient = create({ |
||||
baseURL: "https://wpd.brazilsouth.cloudapp.azure.com/auth/forgotpasswords" |
|
||||
|
baseURL: "https://wpdauth.pinear.com.br/forgotpasswords" |
||||
}) |
}) |
||||
|
|
||||
export { authClient, authChangePswdClient}; |
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