diff --git a/.idea/gruppo-1-app.iml b/.idea/gruppo-1-app.iml index d6ebd48..249aa4e 100644 --- a/.idea/gruppo-1-app.iml +++ b/.idea/gruppo-1-app.iml @@ -1,9 +1,24 @@ + + + + + + - - + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 61233a0..4df9a91 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -2,7 +2,7 @@ - + - - + + + + + + + + \ No newline at end of file diff --git a/App.js b/App.js index ceae05b..c78c529 100644 --- a/App.js +++ b/App.js @@ -1,13 +1,15 @@ -import { StatusBar } from 'expo-status-bar' -import React from 'react' -import { StyleSheet, Text, View } from 'react-native' +import React from 'react'; +import { StatusBar } from 'expo-status-bar'; + import Providers from './context/Providers' import Screens from './screens/Screens' export default function App() { + return ( + ) } diff --git a/assets/Guybrush_Threepwood.png b/assets/Guybrush_Threepwood.png new file mode 100644 index 0000000..199b4f2 Binary files /dev/null and b/assets/Guybrush_Threepwood.png differ diff --git a/assets/logo_minecraft.png b/assets/logo_minecraft.png new file mode 100644 index 0000000..1f7cb8d Binary files /dev/null and b/assets/logo_minecraft.png differ diff --git a/assets/logo_pokemon.png b/assets/logo_pokemon.png new file mode 100644 index 0000000..c2aa3c0 Binary files /dev/null and b/assets/logo_pokemon.png differ diff --git a/assets/logo_supermario.jpeg b/assets/logo_supermario.jpeg new file mode 100644 index 0000000..ed18b85 Binary files /dev/null and b/assets/logo_supermario.jpeg differ diff --git a/components/Alert.js b/components/Alert.js new file mode 100644 index 0000000..9703ad5 --- /dev/null +++ b/components/Alert.js @@ -0,0 +1,90 @@ +import React, { useRef, useEffect } from 'react' +import { Animated, StyleSheet, View, Text } from 'react-native' +import Button from './Button' +import colors from '../config/colors' +import spaces from '../config/spaces' + + +export default function Alert({ open, onClose, message = null, typology }) { + + // const animation = useRef(new Animated.Value(0)).current + + // useEffect(() => { + // Animated.timing(animation, { + // toValue: open ? 1 : 0, + // duration: 500, + // useNativeDriver: true + // }).start() + // }, [open]) + + let typologyContainerStyle = typology === "danger" ? styles.containerDanger : styles.containerSuccess + + return ( + // + <> + + { + message && {message} + } + + + onClose&& ( + + ) + + + + // + ) +} + +const styles = StyleSheet.create({ + container: { + paddingHorizontal: spaces.containerSpace, + width: '100%', + position: 'absolute', + top: 0, + left: 0, + zIndex: 1, + paddingHorizontal: spaces.containerSpace, + paddingTop: 10 + }, + containerInternal: { + backgroundColor: 'red', + padding: 15, + display: 'flex', + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + borderRadius: 15, + width: '100%' + }, + containerSuccess: { + backgroundColor: 'green' + }, + containerDanger: { + backgroundColor: 'red' + }, + message: { + color: 'red', + fontWeight: 'bold', + fontSize: 16, + paddingRight: 15, + flexBasis: '70%' + }, + button: { + flexBasis: '30%' + } +}) \ No newline at end of file diff --git a/components/Button.js b/components/Button.js index 5f5729e..24a9f5f 100644 --- a/components/Button.js +++ b/components/Button.js @@ -1,5 +1,27 @@ -import React from "react"; +import React from 'react' +import { TouchableOpacity, Text } from 'react-native' -export default function Button(){ - return <> +export default function Button(props) { + const textStyle = { + fontWeight: 'bold', + textAlign: 'center', + color: 'white', + } + const btnStyle = { + width: '50%', + borderColor: 'orange', + backgroundColor: 'orange', + borderWidth: 3, + borderRadius: 50, + padding: 10, + marginVertical: 20, + + } + return ( + + + {props.name} + + + ) } \ No newline at end of file diff --git a/components/CardItem.js b/components/CardItem.js new file mode 100644 index 0000000..7eb9c9d --- /dev/null +++ b/components/CardItem.js @@ -0,0 +1,99 @@ +import React from 'react'; +import { Text, View, StyleSheet, TouchableOpacity, Image, Dimensions, } from 'react-native'; +import { EvilIcons } from '@expo/vector-icons'; +// import { rootNavigation } from '../utility/navigation.js' + +const CardItem = ({ data }) => { + const { game, name } = data; + return ( + + + + + + + + + + + Name: {name} + + + Game: {game} + + + + + + + + + + ) +}; + +export default CardItem; + +const styles = StyleSheet.create({ + card: { + flexDirection: 'row', + justifyContent: 'space-between', + marginVertical: 5, + borderRadius: 2, + backgroundColor: 'white' + // borderWidth: 2, + // borderColor: 'blue', + }, + img: { + width: 100, + height: 100, + borderColor: 'black', + borderWidth: 0, + justifyContent: 'center', + alignItems: 'center', + }, + text: { + height: 100, + padding: 10, + // borderWidth: 1, + // borderColor: 'red', + justifyContent: 'center', + paddingLeft: 20, + }, + btnContainer: { + + height: 100, + // borderWidth: 1, + // borderColor: 'red', + justifyContent: 'center', + alignItems: 'center', + paddingRight: 20, + + }, + logoGame: { + width: '80%', + height: '80%', + justifyContent: 'center', + alignItems: 'center', + } +}) + +/* + +*/ \ No newline at end of file diff --git a/components/CardProfile.js b/components/CardProfile.js new file mode 100644 index 0000000..9d83316 --- /dev/null +++ b/components/CardProfile.js @@ -0,0 +1,115 @@ +import React from 'react'; +import { Text, View, StyleSheet, TouchableOpacity, ScrollView, Image } from 'react-native' + +import { EvilIcons } from '@expo/vector-icons'; + +import Button from '../components/Button' +import Spacer from '../components/Spacer' + + +const CardProfile = ({ navigation, route }) => { + const { created_at, description, game, id, name, updatet_at, user_uuid } = route.params; + + return ( + <> + + + navigation.goBack()}> + + + + {name} + + + Code: {id} + Year: {created_at.slice(0, 4)} + Game: {game} + {description.slice(1, -1)} + + + + + + + + + + + + + + + + + + + ); +}; + +export default CardProfile; + + +const styles = StyleSheet.create({ + mainContainer: { + flex: 1, + alignItems: 'center', + justifyContent: 'flex-start', + }, + goBack: { + height: 100, + width: '85%', + justifyContent: 'center', + /* borderWidth: 1, + borderColor: 'red', */ + }, + profileContainer: { + flex: 1, + width: '80%', + alignItems: 'flex-start', + justifyContent: 'flex-start', + backgroundColor: 'white', + borderRadius: 10, + overflow: 'hidden', + /* borderWidth: 1, + borderColor: 'red' */ + }, + title: { + width: '100%', + padding: 10, + fontSize: 24, + color: 'white', + backgroundColor: 'orange', + }, + subTitle: { + fontSize: 15, + }, + description: { + marginTop: 10, + fontSize: 20 + }, + img: { + width: 100, + height: 100, + //backgroundColor: 'black', + alignSelf: 'center', + marginTop: 15, + // borderWidth: 1, + // borderColor: 'red', + }, + logoGame: { + width: 95, + height: 95, + justifyContent: 'center', + alignItems: 'center' + } +}) \ No newline at end of file diff --git a/components/ExchangeScreen.js b/components/ExchangeScreen.js new file mode 100644 index 0000000..6c60f4b --- /dev/null +++ b/components/ExchangeScreen.js @@ -0,0 +1,216 @@ +import React, { useState, useEffect, useContext } from 'react'; +import { Text, View, TextInput, TouchableOpacity, ScrollView, StyleSheet, Dimensions } from 'react-native' + +import { BarCodeScanner } from 'expo-barcode-scanner'; +import { QRCode } from 'react-native-custom-qr-codes-expo'; +import { EvilIcons } from '@expo/vector-icons'; +import { AuthContext } from '../context/AuthContext' + +import api from '../utility/api' + +import Title from '../components/Title' +import Button from '../components/Button' + + +const ExchangeScreen = ({ navigation, route }) => { + const { created_at, description, game, id, name, updatet_at, user_uuid } = route.params.params; + const [qrAvailable, setQRAvailable] = useState(true) + const [hasPermission, setHasPermission] = useState(null); + const [scanned, setScanned] = useState(false); + const [qrData, setQrData] = useState(''); // qrData = portfolio code + const [error, setError] = useState(false) + const [messageOpen, setMessageOpen] = useState(false) + const { transferCounter, counter } = useContext(AuthContext) + + //Costanti per la dimensione del BarCodeScanner + const myScreenW = Dimensions.get('window').width; + const myScreenH = myScreenW / 9 * 16; + + useEffect(() => { + (async () => { + const { status } = await BarCodeScanner.requestPermissionsAsync(); + setHasPermission(status === 'granted'); + })(); + // console.log('route.params.params: ', route.params.params) + + }, []); + + const handleBarCodeScanned = ({ type, data }) => { + setScanned(true); + setQrData(data); + //alert(`Bar code with type ${type} and data ${data} has been scanned!`); + }; + + if (hasPermission === null) { + return Requesting for camera permission; + } + if (hasPermission === false) { + return No access to camera; + } + + const transferCard = async () => { + const cardData = { + "card_id": id, + "portfolio_code": qrData + } + // console.log('cardData: ', cardData) + + try { + const response = await api.post('move-card', cardData) + const { result, errors, payload } = response + // console.log('response:', response) + // console.log('result -----------', response) + + if (result) { + let newCounter = parseInt(counter || '0') + newCounter++ + transferCounter(JSON.stringify(newCounter)) + navigation.navigate('SuccessfulTransfer') + + } else { + setError(errors[0].message) + setMessageOpen(true) + } + } catch (err) { + console.warn(err) + setError(err) + setMessageOpen(true) + + } + } + + return ( + + + navigation.goBack()}> + + + + { + qrAvailable + ? <> + { + !scanned + ? <> + + + <View style={styles.img}> + <BarCodeScanner + onBarCodeScanned={scanned ? undefined : handleBarCodeScanned} + barCodeTypes={[BarCodeScanner.Constants.BarCodeType.qr]} + style={{ height: myScreenH, width: myScreenW }} + /> + </View> + </> + : <> + <Title + title={'Tap to Scan Again'} + color='#666' + style={{ alignSelf: 'center' }} + /> + + <TouchableOpacity + style={styles.img} + onPress={() => setScanned(false)} + > + <QRCode + content={qrData} + size={240} + /> + </TouchableOpacity> + {/*scanned && <Button name={'Tap to Scan Again'} submit={() => setScanned(false)} />*/} + + </> + } + {/* TO be changed with !qrCode*/} + <TouchableOpacity onPress={() => setQRAvailable(false)}> + <Text style={styles.isQRAvailable}>Non ho un QR Code</Text> + </TouchableOpacity> + </> + : <> + <Title + title={'Inserisci il codice univoco del destinatario'} + color='#666' + style={{ alignSelf: 'center', textAlign: 'center' }} /> + <TextInput + style={styles.input} + placeholder={'Inserisci il codice qui'} + onChangeText={qrData => setQrData(qrData)} + value={qrData} + /> + + <TouchableOpacity onPress={() => setQRAvailable(true)}> + <Text style={styles.isQRAvailable}>Voglio usare il QR Code</Text> + </TouchableOpacity> + </> + } + + <TouchableOpacity style={{ alignSelf: 'center' }}> + <Button + name={'TRASFERISCI'} + // submit={() => navigation.navigate('SuccessfulTransfer')} + submit={() => transferCard()} + // submit={() => console.log(qrData)} + /> + </TouchableOpacity> + </View> + </View> + + </ScrollView> + ); +}; + +export default ExchangeScreen; + +const styles = StyleSheet.create({ + mainContainer: { + flex: 1, + alignItems: 'center', + justifyContent: 'flex-start', + }, + goBack: { + height: 100, + width: '85%', + justifyContent: 'center', + // borderWidth: 1, + // borderColor: 'red', + }, + profileContainer: { + flex: 1, + width: '80%', + alignItems: 'center', + justifyContent: 'flex-start', + // borderWidth: 1, + // borderColor: 'red' + }, + img: { + width: 250, + height: 250, + backgroundColor: 'orange', + marginTop: 15, + borderRadius: 10, + borderWidth: 1, + borderColor: 'black', + + justifyContent: 'center', + alignItems: 'center', + overflow: 'hidden', + }, + isQRAvailable: { + textDecorationLine: 'underline', + marginTop: 10 + }, + input: { + height: 40, + borderColor: 'gray', + borderWidth: 1, + borderRadius: 10, + padding: 10 + } +}) \ No newline at end of file diff --git a/components/Form.js b/components/Form.js new file mode 100644 index 0000000..d4b0510 --- /dev/null +++ b/components/Form.js @@ -0,0 +1,37 @@ +import React from 'react' +import PropTypes from 'prop-types' +import { View } from 'react-native' +import Input from './Input' + + +export default function Form({ inputs, updateInputValue }) { + return ( + <> + { + inputs.map(({ label, name, autoCapitalize, secureTextEntry }, index) => { + return ( + <View key={index}> + <Input + label={label} + onTextChange={(text) => updateInputValue(name, text)} + autoCapitalize={autoCapitalize} + secureTextEntry={secureTextEntry} + /> + </View> + ) + }) + } + + </> + ) +} + +Form.propTypes = { + inputs: PropTypes.arrayOf(PropTypes.shape({ + label: PropTypes.string, + name: PropTypes.string, + autoCapitalize: PropTypes.string, + secureTextEntry: PropTypes.bool + })).isRequired, + updateInputValue: PropTypes.func.isRequired +} \ No newline at end of file diff --git a/components/Header.js b/components/Header.js new file mode 100644 index 0000000..a3b121f --- /dev/null +++ b/components/Header.js @@ -0,0 +1,75 @@ +import React, { useContext } from 'react' +import { AuthContext } from '../context/AuthContext' +import { View, Text, StyleSheet } from 'react-native' +import { EvilIcons } from '@expo/vector-icons'; + +export default function Header() { + const { token, user } = useContext(AuthContext) + + return (<> + + { + !token + ? + <View style={withoutToken}> + <Text style={styles.logoText}> + CARDS + </Text> + + </View> + : + <View style={withToken}> + <Text style={styles.logoText}> + CARDS + </Text> + <View style={styles.childrenView}> + <Text style={styles.userText} > + {user.surname} + </Text> + <EvilIcons name="user" size={50} color="yellow" /> + </View> + </View> + } + </>) +} + + +const styles = StyleSheet.create({ + headerContainer: { + backgroundColor: 'black', + width: '100%', + height: 80, + paddingBottom: 5, + + flexDirection: 'row', + alignItems: 'flex-end', + }, + center: { + justifyContent: 'center', + }, + spaceBetween: { + justifyContent: 'space-between', + paddingLeft: 10, + paddingRight: 5, + }, + logoText: { + color: 'orange', + fontSize: 30, + fontWeight: 'bold', + }, + childrenView: { + flexDirection: 'row', + justifyContent: 'flex-end', + alignItems: 'center', + }, + userText: { + color: 'orange', + fontSize: 16, + paddingRight: 7, + }, + + +}) + +const withToken = StyleSheet.compose(styles.headerContainer, styles.spaceBetween); +const withoutToken = StyleSheet.compose(styles.headerContainer, styles.center); \ No newline at end of file diff --git a/components/Input.js b/components/Input.js index f5c1166..8f79c81 100644 --- a/components/Input.js +++ b/components/Input.js @@ -1,5 +1,53 @@ -import React from "react"; +import React, { useState, useEffect } from "react"; +import { TextInput, View, StyleSheet } from 'react-native' +import Label from './Label' -export default function Input(){ - return <></> -} \ No newline at end of file + +export default function Input({ + label, + type, //'default', 'email-address', 'numeric', 'phone-pad', 'ascii-capable', 'numbers-and-punctuation', 'url', 'number-pad', 'name-phone-pad', 'decimal-pad', 'twitter', 'web-search', 'visible-password' + isPassword, + onTextChange = () => { }, + ...props +}) { + const [text, setText] = useState('') + + + useEffect(() => { + onTextChange(text) + }, [text]) + + return ( + <View style={[styles.container]}> + <Label label={label}></Label> + <TextInput + style={styles.textInputStyle} + value={text} + onChangeText={value => setText(value)} + keyboardType={type} + secureTextEntry={isPassword} + {...props} + /> + </View> + ) +} + +const styles = StyleSheet.create({ + container: { + // flex: 1, + // justifyContent: 'center', + alignItems: 'center', + // paddingBottom: 20, + width: 400, + // backgroundColor: 'pink' + }, + textInputStyle: { + width: '70%', + borderColor: 'black', + borderWidth: 3, + borderRadius: 50, + padding: 10, + marginVertical: 15, + backgroundColor: 'white' + } +}) diff --git a/components/Label.js b/components/Label.js new file mode 100644 index 0000000..8a8270d --- /dev/null +++ b/components/Label.js @@ -0,0 +1,14 @@ +import React from 'react' +import { Text } from 'react-native' +export default function Label({ + label, + +}) { + return <Text style={{ + fontSize: 18, + fontWeight: '500', + textTransform: 'capitalize' + }}> + {label} + </Text> +} \ No newline at end of file diff --git a/components/Paragraph.js b/components/Paragraph.js new file mode 100644 index 0000000..f2e1dc0 --- /dev/null +++ b/components/Paragraph.js @@ -0,0 +1,25 @@ +import React from 'react' +import { View, Text } from 'react-native' +export default function Paragraph({ + children, + align + +}) { + return ( + <View style={{ + width: '80%', + alignItems: 'center', + justifyContent: 'center', + }}> + <Text style={{ + fontSize: 15, + lineHeight: 25, + fontWeight: '500', + textAlign: align, + }}> + {children} + + </Text> + </View> + ) +} \ No newline at end of file diff --git a/components/Row.js b/components/Row.js index 8d988aa..35a7c85 100644 --- a/components/Row.js +++ b/components/Row.js @@ -1,10 +1,10 @@ import React from "react"; -import { StyleSheet, View } from 'react-native'; +import { View } from 'react-native'; export default function Row({ children, justify, align = "center", ...props }) { - return <View style={{ flexDirection: "row", justifyContent:justify, alignItems:align }} {...props}>{children}</View> + return <View style={{ flexDirection: "row", justifyContent: justify, alignItems: align }} {...props}>{children}</View> } diff --git a/components/Spacer.js b/components/Spacer.js index 0aa2094..9447b05 100644 --- a/components/Spacer.js +++ b/components/Spacer.js @@ -1,4 +1,5 @@ import React from "react"; +import { View } from 'react-native' import spaces from "../config/spaces.js"; export default function Spacer({ size = 0, diff --git a/components/SuccessfulTransfer.js b/components/SuccessfulTransfer.js new file mode 100644 index 0000000..a5516d8 --- /dev/null +++ b/components/SuccessfulTransfer.js @@ -0,0 +1,62 @@ +import React from 'react'; +import { Text, View, ScrollView, StyleSheet } from 'react-native'; + +import { AntDesign } from '@expo/vector-icons'; + +import Button from '../components/Button' +import Spacer from '../components/Spacer' + + +const SuccessfulTransfer = ({ navigation }) => { + return ( + <ScrollView> + <View style={styles.main}> + + <Spacer size="20" /> + <Text style={styles.text}> + Trasferimento avvenuto con successo + </Text> + + <View style={styles.check}> + <AntDesign name="checkcircleo" size={100} color="#28B463" /> + </View> + + <Button + name={'TORNA ALLA LISTA'} + submit={() => navigation.navigate('CardsScreen')} + /> + + </View> + </ScrollView> + + ); +}; +export default SuccessfulTransfer; + + +const styles = StyleSheet.create({ + main: { + flex: 1, + justifyContent: 'flex-start', + alignItems: 'center' + }, + text: { + textAlign: 'center', + fontSize: 25, + fontWeight: 'bold', + paddingHorizontal: 30, + color: '#666', + }, + check: { + width: 200, + height: 200, + borderRadius: 20, + borderColor: '#999', + borderWidth: 2, + marginTop: 40, + marginBottom: 25, + + justifyContent: 'center', + alignItems: 'center', + } +}) \ No newline at end of file diff --git a/components/Title.js b/components/Title.js new file mode 100644 index 0000000..5a3963d --- /dev/null +++ b/components/Title.js @@ -0,0 +1,23 @@ +import React from "react"; +import { Text } from 'react-native'; + +const Title = ({ title, color = '#000' }) => { + + const styles = { + titleStyle: { + color: color, + fontSize: 24, + fontWeight: 'bold', + textTransform: 'uppercase', + marginVertical: 10, + textAlign: 'center' + } + } + + return ( + <Text style={styles.titleStyle}>{title}</Text> + ); +}; + + +export default Title; \ No newline at end of file diff --git a/config/api.js b/config/api.js deleted file mode 100644 index 6bee717..0000000 --- a/config/api.js +++ /dev/null @@ -1,3 +0,0 @@ -export default { - url:"https://tree-rn-server.herokuapp.com" -} \ No newline at end of file diff --git a/config/apis.js b/config/apis.js new file mode 100644 index 0000000..f40e643 --- /dev/null +++ b/config/apis.js @@ -0,0 +1,4 @@ +export default { + baseUrl: "https://tree-rn-server.herokuapp.com", + // logged: true +} \ No newline at end of file diff --git a/config/colors.js b/config/colors.js index a460ca3..bb3ea9c 100644 --- a/config/colors.js +++ b/config/colors.js @@ -1,4 +1,6 @@ export default { black: "#000", - white: "#fff" + white: "#fff", + yellow: "#ffe933", + red: '#ff3333' } \ No newline at end of file diff --git a/context/AuthContext.js b/context/AuthContext.js index c222976..231f2e8 100644 --- a/context/AuthContext.js +++ b/context/AuthContext.js @@ -1,11 +1,78 @@ -import {createContext, useState} from "react" +import React, { createContext, useCallback, useState } from 'react' +import { setToken } from '../utility/api' +import AsyncStorage from '@react-native-community/async-storage' +import { rootNavigation } from '../utility/navigation' +import { CommonActions } from '@react-navigation/native' +import api from '../utility/api' export const AuthContext = createContext() -export default function AuthProvider ({ children }) { +export default function AuthProvider({ children }) { const [user, setUser] = useState() + const [token, setTokenProv] = useState() + const [counter, setCounter] = useState() + const [cards, setCards] = useState([]) + + const [error, setError] = useState(false) + const [messageOpen, setMessageOpen] = useState(false) + + const manageUserData = useCallback(async (userData) => { + // console.log('userData in manageuserData', userData) + setUser(userData.user) + setToken(userData.token) + setTokenProv(userData.token) + await AsyncStorage.setItem('AuthToken', userData.token) + }, []) + + const getCards = useCallback(async () => { + try { + const response = await api.get('get-cards') + const { result, errors, payload } = response + // console.log(result) + if (result) { + // console.log('payload--------------------', payload.cards) + setCards(payload.cards) + }else { + setError(errors[0].message) + setMessageOpen(true) + } + } catch (err) { + console.warn(err) + setError(err) + setMessageOpen(true) + } + }, []) + + + const transferCounter = useCallback(async (cardsMoved) => { + // console.log('cardsMoved in COntext: ', cardsMoved) + setCounter(cardsMoved) + + // await AsyncStorage.setItem('counter', counter) + }, []) + + const onLogout = useCallback(async () => { + setUser(' ') + setToken('') + setTokenProv('') + await AsyncStorage.removeItem('AuthToken') // cancello token dalla memoria + + // cancello la storia di navigazione e vado sulla schermata di autenticazione + rootNavigation.current.dispatch(CommonActions.reset({ + index: 0, + routes: [{ name: "Auth" }] + })) + }, []) return ( - <AuthContext.Provider value={{user}}>{children}</AuthContext.Provider> + <AuthContext.Provider value={{ + token, setTokenProv, + user, manageUserData, + onLogout, + transferCounter, counter, + getCards, cards, + }}> + {children} + </AuthContext.Provider> ) } \ No newline at end of file diff --git a/debug.log b/debug.log new file mode 100644 index 0000000..9564ae0 --- /dev/null +++ b/debug.log @@ -0,0 +1 @@ +[1216/111316.634:ERROR:directory_reader_win.cc(43)] FindFirstFile: Impossibile trovare il percorso specificato. (0x3) diff --git a/hooks/useForm.js b/hooks/useForm.js new file mode 100644 index 0000000..01dcfab --- /dev/null +++ b/hooks/useForm.js @@ -0,0 +1,35 @@ +import React, { useState } from 'react' + +/** + * @function useForm + * @param {array} requiredInputs -> Lista dei campi del form obbligatori. + */ +export default function useForm(requiredInputs) { + const [formValues, setFormValues] = useState({}) + const [formValid, setFormValid] = useState(false) + + const setFormValue = (name, value) => { + const newFormValues = { ...formValues } + newFormValues[name] = value + setFormValues(newFormValues) + + const notEmptyKeys = Object.keys(newFormValues).filter((key) => newFormValues[key] !== '') + + // esempio esplicito di funzionamento del metodo every() (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every) + // let formIsValid = true + // requiredInputs.forEach((input) => { + // if (!notEm ptyKeys.includes(input)) { + // formIsValid = false + // } + // }) + + setFormValid(requiredInputs.every((el) => notEmptyKeys.includes(el))) + } + + const formData = { + values: formValues, + valid: formValid + } + + return [formData, setFormValue] +} \ No newline at end of file diff --git a/hooks/useLoader.js b/hooks/useLoader.js index e69de29..b0f23dc 100644 --- a/hooks/useLoader.js +++ b/hooks/useLoader.js @@ -0,0 +1,34 @@ +import { useEffect, useState, useContext } from "react"; +import { AuthContext } from "../context/AuthContext.js"; +import AsyncStorage from "@react-native-community/async-storage"; +import api, { setToken } from "../utility/api.js"; + +export default function useLoader() { + const [loading, setLoading] = useState(true); + const { manageUserData, setTokenProv } = useContext(AuthContext); + + useEffect(() => { + const load = async () => { + const token = await AsyncStorage.getItem('AuthToken'); + console.log('token in useLoader', token) + if (token) { + setToken(token); + setTokenProv(token); + + // commenta se l'endpoint non รจ ancora esistente + try { + const { result, payload } = await api.post("refresh-token") + if (result) { + manageUserData(payload); + } + } catch (err) { + console.log(err); + } + } + setLoading(false); + } + load(); + }, []) + + return loading +} \ No newline at end of file diff --git a/navigators/AppNavigator.js b/navigators/AppNavigator.js new file mode 100644 index 0000000..457fbe1 --- /dev/null +++ b/navigators/AppNavigator.js @@ -0,0 +1,28 @@ +import React, { useContext } from 'react' +import { createStackNavigator } from '@react-navigation/stack' +import Auth from "./Auth.js"; +import NavigationTab from './NavigationTab' +import { AuthContext } from '../context/AuthContext' + +const AppStack = createStackNavigator() + +export default function AppNavigator() { + const { token } = useContext(AuthContext) + + return ( + <AppStack.Navigator + initialRouteName={token ? "NavigationTab" : "Auth"} + screenOptions={{ + headerShown: false, + cardStyle: { paddingTop: 0 }, + + }} + > + + + <AppStack.Screen name="Auth" component={Auth} /> + <AppStack.Screen name="NavigationTab" component={NavigationTab} /> + + </AppStack.Navigator> + ) +} diff --git a/navigators/Auth.js b/navigators/Auth.js index 359ce69..a1bbafd 100644 --- a/navigators/Auth.js +++ b/navigators/Auth.js @@ -1,17 +1,23 @@ -import React from "react" +import React from 'react' import { createStackNavigator } from '@react-navigation/stack' import Login from "../screens/Login.js" -import Welcome from "../screens/Welcome.js" import SignUp from "../screens/SignUp.js" +import Greeting from "../screens/Greeting.js" const Stack = createStackNavigator() export default function Auth() { - return ( - <Stack.Navigator initialRouteName="Welcome"> - <Stack.Screen name="Welcome" component={Welcome} /> + + return (<> + + <Stack.Navigator + screenOptions={{ headerShown: false }} + initialRouteName="Login" + > + <Stack.Screen name="Login" component={Login} /> <Stack.Screen name="SignUp" component={SignUp} /> + <Stack.Screen name="Greeting" component={Greeting} /> </Stack.Navigator> - ) + </>) } \ No newline at end of file diff --git a/navigators/CardsScreen.js b/navigators/CardsScreen.js new file mode 100644 index 0000000..dc5df0c --- /dev/null +++ b/navigators/CardsScreen.js @@ -0,0 +1,111 @@ +import React, { useContext, useEffect, useState } from 'react'; +import { View, StyleSheet, FlatList, TouchableOpacity, ActivityIndicator } from 'react-native'; +import { AuthContext } from '../context/AuthContext' +import { useIsFocused } from "@react-navigation/native" + +import Spacer from '../components/Spacer' +import Title from '../components/Title' +import Button from '../components/Button' +import CardItem from '../components/CardItem' + + +const CardsScreen = ({ navigation }) => { + const { getCards, cards } = useContext(AuthContext) + const isFocused = useIsFocused() + const [timer, setTimer] = useState(false) + + useEffect(() => { + if (isFocused) { + getCards() + } + }, [isFocused]) + + if (cards < 1) { + setTimeout(() => { setTimer(true) }, 3000) + + if (!timer) { + return ( + <> + <ActivityIndicator size={150} color="blue" style={{ flex: 1, flexDirection: 'row', justifyContent: 'center' }} /> + </> + ) + } else { + return ( + <> + <View style={styles.main}> + <Title title={`NON CI SONO CARTE`} /> + <Button + name={'TORNA ALLA SCHERMATA PRINCIPALE'} + submit={() => navigation.navigate('Main') + } + /> + </View> + </> + ) + } + + } else { + return ( + <> + + <View style={styles.container}> + + <View style={styles.main}> + <Spacer size={2} /> + + <Title title={`Le mie Carte`} /> + <FlatList + style={{ + width: '100%' + }} + data={cards} + renderItem={({ item }) => ( + <TouchableOpacity + onPress={() => navigation.navigate('CardProfile', item)} + > + <CardItem data={item} /> + </TouchableOpacity> + + )} + keyExtractor={item => JSON.stringify(item.id)} + /> + </View> + </View> + </> + ); + + } + +}; +export default CardsScreen; + + +const styles = StyleSheet.create({ + + container: { + width: '100%', + flex: 1, + alignItems: 'center', + }, + main: { + flex: 1, + width: '95%', + justifyContent: 'flex-start', + alignItems: 'center', + // backgroundColor: 'pink', + }, + input: { + height: 40, + borderColor: 'gray', + borderWidth: 1, + borderRadius: 10, + padding: 10 + }, + input: { + height: 40, + borderColor: 'gray', + borderWidth: 1, + borderRadius: 10, + padding: 10 + } +}) \ No newline at end of file diff --git a/navigators/CardsStackNav.js b/navigators/CardsStackNav.js new file mode 100644 index 0000000..4f5c03b --- /dev/null +++ b/navigators/CardsStackNav.js @@ -0,0 +1,31 @@ +import React from 'react'; +import { createStackNavigator } from '@react-navigation/stack' +import CardsScreen from './CardsScreen' +import CardProfile from '../components/CardProfile' +import ExchangeScreen from '../components/ExchangeScreen' +import SuccessfulTransfer from '../components/SuccessfulTransfer' + +const CardsStack = createStackNavigator() + +const CardsStackNav = () => { + + + return ( + <> + <CardsStack.Navigator + initialRouteName={"CardsScreen"} + screenOptions={{ + headerShown: false, + cardStyle: { paddingTop: 0 }, + }} + > + <CardsStack.Screen name="CardsScreen" component={CardsScreen} /> + <CardsStack.Screen name="CardProfile" component={CardProfile} /> + <CardsStack.Screen name="ExchangeScreen" component={ExchangeScreen} /> + <CardsStack.Screen name="SuccessfulTransfer" component={SuccessfulTransfer} /> + </CardsStack.Navigator> + </> + ) +}; + +export default CardsStackNav; \ No newline at end of file diff --git a/navigators/Main.js b/navigators/Main.js index 93d5b79..d38f151 100644 --- a/navigators/Main.js +++ b/navigators/Main.js @@ -1,9 +1,150 @@ -import React from "react" +import React, { useState, useEffect, useContext } from "react" +import { View, StyleSheet, Text, ScrollView } from 'react-native' + +import { EvilIcons } from '@expo/vector-icons'; +import { AuthContext } from '../context/AuthContext' +import { Ionicons } from '@expo/vector-icons'; +import { useIsFocused } from "@react-navigation/native" + +import Title from '../components/Title' +import Button from '../components/Button' +import Spacer from '../components/Spacer' // usare createBottommTabNavigator: https://reactnavigation.org/docs/bottom-tab-navigator/ -export default function Main(){ - return ( - <> - </> - ) -} \ No newline at end of file +export default function Main() { + const [currentDate, setCurrentDate] = useState(''); + const { user, counter, getCards, cards, onLogout } = useContext(AuthContext) + const [cardsRender, setCardsRender] = useState([]) + const isFocused = useIsFocused() + + // const [error, setError] = useState(false) + // const [messageOpen, setMessageOpen] = useState(false) + + // const submitGet = async () => { + // try { + // const response = await api.get('get-cards') + // const { result, errors, payload } = response + // // console.log(result) + // if (result) { + // // console.log('payload--------------------', payload.cards) + // setCardsRender(payload.cards) + + // } else { + // setError(errors[0].message) + // setMessageOpen(true) + // } + // } catch (err) { + // console.warn(err) + // setError(err) + // setMessageOpen(true) + // } + // manageCards(cardsRender) + // // console.log('user from AuthCont ------------------------------------', user.name) + // // console.log('cards ------------------------------------', cards) + // } + + useEffect(() => { + var date = new Date().getDate(); //Current Date + var month = new Date().getMonth() + 1; //Current Month + var year = new Date().getFullYear(); //Current Year + setCurrentDate(date + '/' + month + '/' + year); + + }, []); + + // RN non smonta componenti in navigation + useEffect(() => { + if (isFocused) { + getCards() + } + }, [isFocused]) + + return ( + <ScrollView + showsVerticalScrollIndicator={false} + keyboardShouldPersistTaps="handled"> + <View style={styles.mainContainer}> + <Spacer size={2} /> + + <Title title={'BENVEnuto'} /> + <Title title={`${user.name + ' ' + user.surname} `} /> + < EvilIcons name="user" size={200} color="black" /> + <Title title={`${currentDate}`} /> + + <View style={styles.cardsSummaryContainer}> + <View style={styles.infoBox}> + <View style={styles.infoBoxNumber}> + <Text style={styles.infoBoxNumberT}> + { + cards.length > 0 + ? <Text>{cards.length}</Text> + : <Text>0</Text> + } + </Text> + </View> + <View style={styles.infoBoxText}> + <Text style={styles.infoBoxTextT}>Carte in lista</Text> + </View> + </View> + <View style={styles.infoBox}> + <View style={styles.infoBoxNumber}> + <Text style={styles.infoBoxNumberT}>{counter || 0}</Text> + </View> + <View style={styles.infoBoxText}> + <Text style={styles.infoBoxTextT}>Transazioni recenti</Text> + </View> + </View> + </View> + <Button + name={"LOG OUT"} + submit={onLogout} + icon={<Ionicons name="exit-outline" size={30} color="white" />} + /> + </View> + </ScrollView> + ) +} + +const styles = StyleSheet.create({ + mainContainer: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + }, + cardsSummaryContainer: { + flex: 1, + width: '100%', + flexDirection: 'row', + justifyContent: 'space-around', + // borderWidth: 2, + // borderColor: 'orange', + }, + infoBox: { + width: 150, + height: 150, + borderWidth: 1, + borderColor: 'black', + borderRadius: 10, + backgroundColor: 'orange', + alignItems: 'center', + }, + infoBoxNumber: { + flex: 5, + padding: 'auto', + }, + infoBoxNumberT: { + fontSize: 60, + color: 'white', + fontWeight: 'bold' + }, + infoBoxText: { + flex: 5, + // borderWidth: 1, + // borderColor: 'black', + }, + infoBoxTextT: { + fontSize: 25, + color: 'white', + fontWeight: 'bold', + textAlign: 'center' + } +}) \ No newline at end of file diff --git a/navigators/NavigationTab.js b/navigators/NavigationTab.js new file mode 100644 index 0000000..762e8cb --- /dev/null +++ b/navigators/NavigationTab.js @@ -0,0 +1,44 @@ +import React from 'react'; +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; +import { FontAwesome } from '@expo/vector-icons'; + +import Main from './Main' +import CardsStackNav from './CardsStackNav' +import ProfileScreen from './ProfileScreen' + + +const Tab = createBottomTabNavigator(); + +export default function App() { + + return ( + <> + <Tab.Navigator + tabBarOptions={{ + showLabel: false, + showIcon: true, + inactiveBackgroundColor: 'black', // Not working at all. + activeBackgroundColor: 'orange', + style: { backgroundColor: '#3498DB', height: 40, padding: 0, margin: 0 } + }}> + + <Tab.Screen + name="Main" + component={Main} + options={{ tabBarLabel: '', tabBarIcon: () => (<FontAwesome name="home" size={24} color="white" />), }} + /> + <Tab.Screen + name="Cards" + component={CardsStackNav} + options={{ tabBarLabel: '', tabBarIcon: () => (<FontAwesome name="list" size={24} color="white" />), }} + /> + <Tab.Screen + name="Profile" + component={ProfileScreen} + options={{ tabBarLabel: '', tabBarIcon: () => (<FontAwesome name="user" size={24} color="white" />), }} + /> + + </Tab.Navigator> + </> + ); +} \ No newline at end of file diff --git a/navigators/ProfileScreen.js b/navigators/ProfileScreen.js new file mode 100644 index 0000000..8700ef7 --- /dev/null +++ b/navigators/ProfileScreen.js @@ -0,0 +1,91 @@ +import React, { useContext, useState } from 'react'; +import { Text, View, StyleSheet } from 'react-native'; +import { ScrollView } from 'react-native-gesture-handler'; +import { AuthContext } from '../context/AuthContext' + +import { QRCode } from 'react-native-custom-qr-codes-expo'; +import { EvilIcons } from '@expo/vector-icons'; + +import api from '../utility/api' + +import Title from '../components/Title' +import Button from '../components/Button' +import Spacer from '../components/Spacer' + + +const CardsScreen = () => { + const { user } = useContext(AuthContext) + const [code, setCode] = useState('') + const [error, setError] = useState(false) + const [messageOpen, setMessageOpen] = useState(false) + + const generateCode = async () => { + try { + const response = await api.post('refresh-portfolio-code',) + const { result, errors, payload } = response + // console.log('result is: ', payload) + if (result) { + setCode(payload) + } + else { + setError(errors[0].message) + setMessageOpen(true) + } + } catch (err) { + console.warn(err) + setError(err) + setMessageOpen(true) + } + + } + return ( + <ScrollView + showsVerticalScrollIndicator={false} + keyboardShouldPersistTaps="handled"> + <View style={styles.main}> + <Spacer size={2} /> + + <Title title={`${user.name + ' ' + user.surname} `} /> + < EvilIcons name="user" size={150} color="black" /> + <Text style={styles.paragraph}>Email: {user.email}</Text> + <Button + name={'GENERA CODICE'} + submit={generateCode} + /> + { + code + ? <> + <Text style={styles.paragraph}>Codice: {code.portfolio_code}</Text> + <Title title={`Il tuo QR Code`} /> + <View style={styles.qrCodeContainer}> + <QRCode + logo={require('../assets/Guybrush_Threepwood.png')} + logoSize={70} + content={code.portfolio_code} + size={240} + /> + </View> + </> + : <Text></Text> + } + </View> + <Spacer size={10} /> + </ScrollView> + ); +}; + +export default CardsScreen; + +const styles = StyleSheet.create({ + main: { flex: 1, justifyContent: 'flex-start', alignItems: 'center' }, + qrCodeContainer: { + width: 240, + height: 240, + borderWidth: 1, + borderColor: 'black', + borderRadius: 10, + }, + paragraph: { + fontSize: 20 + } +}) \ No newline at end of file diff --git a/package.json b/package.json index 5b67d13..0d3af68 100644 --- a/package.json +++ b/package.json @@ -9,14 +9,24 @@ }, "dependencies": { "@react-native-community/async-storage": "^1.12.1", + "@react-native-community/checkbox": "^0.5.6", + "@react-native-community/masked-view": "0.1.10", "@react-navigation/bottom-tabs": "^5.11.2", "@react-navigation/native": "^5.8.10", "@react-navigation/stack": "^5.12.8", - "expo": "~40.0.0", + "expo": "^40.0.0", + "expo-barcode-scanner": "~9.1.0", "expo-status-bar": "~1.0.3", "react": "16.13.1", "react-dom": "16.13.1", "react-native": "https://github.com/expo/react-native/archive/sdk-40.0.1.tar.gz", + "react-native-custom-qr-codes-expo": "^2.2.0", + "react-native-gesture-handler": "~1.8.0", + "react-native-paper": "^4.5.0", + "react-native-reanimated": "~1.13.0", + "react-native-safe-area-context": "3.1.9", + "react-native-screens": "~2.15.0", + "react-native-svg": "^12.1.0", "react-native-web": "~0.13.12" }, "devDependencies": { diff --git a/screens/Greeting.js b/screens/Greeting.js new file mode 100644 index 0000000..9fdd0ef --- /dev/null +++ b/screens/Greeting.js @@ -0,0 +1,38 @@ +import React from "react"; +import { View, StyleSheet } from 'react-native' +import Title from '../components/Title' +import Button from '../components/Button' +import Paragraph from '../components/Paragraph' + +export default function Greeting({ navigation }) { + return ( + <View style={styles.container}> + <View style={styles.header}> + </View> + <View style={styles.loginSpace}> + <Title title={'Grazie'} /> + <Paragraph align="center"> + Benvenuto nella nostra app. Prima di continuare controlla la tua mail e verifica l'indirizzo cliccando sul link che ti abbiamo inviato + </Paragraph> + <Button + name={'LOGIN'} + submit={() => navigation.navigate('Login')} + /> + </View> + </View> + ) +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + }, + header: { + flex: 0.2, + }, + loginSpace: { + flex: 0.8, + alignItems: 'center', + justifyContent: 'center', + }, +}) \ No newline at end of file diff --git a/screens/Login.js b/screens/Login.js index 344d0ba..ff74d9b 100644 --- a/screens/Login.js +++ b/screens/Login.js @@ -1,8 +1,132 @@ -import React from "react"; +import React, { useState, useContext } from "react"; +import { View, StyleSheet, Text, TouchableOpacity, ScrollView, TouchableHighlight } from 'react-native' +import { AuthContext } from '../context/AuthContext' +import { rootNavigation } from '../utility/navigation.js' + +import Title from '../components/Title' +import Spacer from '../components/Spacer' +import Button from '../components/Button' +// import Alert2 from '../components/Alert2' + +import useForm from '../hooks/useForm' +import Form from '../components/Form' +import api from '../utility/api' + + +const inputs = [ + { label: 'Username', name: 'username_email' }, + { label: 'Password', name: 'password', secureTextEntry: true }, +] + +// Credentials +// Lorenzo name: utente_26@mail.com +// Alessandro name: utente_4@mail.com +// Alfonso name: utente_7@mail.com + + +// password: Password1! + +export default function Login({ navigation }) { + const requiredInputs = ['username_email', 'password'] + const [formData, setFormValue] = useForm(requiredInputs) + const [loading, setLoading] = useState(false) + const { manageUserData } = useContext(AuthContext) + + const [error, setError] = useState(false) + const [messageOpen, setMessageOpen] = useState(false) + + const submitLogin = async () => { + try { + setLoading(true) + const response = await api.post('authentication/login-action', formData.values) + const { result, errors, payload } = response + if (result) { + manageUserData(payload) + rootNavigation.current.navigate('NavigationTab') + } else { + setError(errors[0].message) + setMessageOpen(true) + } + } catch (err) { + console.warn(err) + setError(err) + setMessageOpen(true) + + } finally { + setLoading(false) + } + } -export default function Login(){ return ( <> + + <ScrollView + showsVerticalScrollIndicator={false} + keyboardShouldPersistTaps="handled"> + + <View style={styles.loginSpace}> + <Spacer size={10} /> + { + messageOpen + ? <View style={styles.errorContainer}> + <Text style={styles.textError}>ATTENTION! {error}</Text> + </View> + : null + } + <Title title={'Accedi'}> +
+