diff --git a/App.tsx b/App.tsx index 14e952c..ac346f3 100644 --- a/App.tsx +++ b/App.tsx @@ -5,10 +5,10 @@ import { useFonts, Roboto_400Regular, Roboto_700Bold } from '@expo-google-fonts/ import { THEME } from './src/theme'; import { Loading } from '@components/Loading'; -import { SignIn } from '@screens/Signin'; import { Routes } from '@routes/index'; +import { AuthContextProvider } from '@contexts/AuthContext'; export default function App() { const [fontsLoaded] = useFonts({ @@ -22,7 +22,9 @@ export default function App() { backgroundColor='transparent' translucent /> - {fontsLoaded ? : } + + {fontsLoaded ? : } + ); } \ No newline at end of file diff --git a/package.json b/package.json index 4fc08d8..f41fb2c 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,8 @@ "react-native-screens": "~3.18.0", "react-native-svg": "13.4.0", "react-native-web": "~0.18.9", - "yup": "^1.0.0" + "yup": "^1.0.0", + "@react-native-async-storage/async-storage": "~1.17.3" }, "devDependencies": { "@babel/core": "^7.12.9", diff --git a/src/components/ExerciseCard.tsx b/src/components/ExerciseCard.tsx index 6436a8b..db20c1d 100644 --- a/src/components/ExerciseCard.tsx +++ b/src/components/ExerciseCard.tsx @@ -2,16 +2,21 @@ import { Heading, HStack, Icon, Image, Text, VStack } from "native-base"; import { Entypo } from '@expo/vector-icons' import { TouchableOpacity, TouchableOpacityProps } from "react-native"; -interface ExerciseCardProps extends TouchableOpacityProps { } +import { ExerciseDTO } from "@dtos/ExerciseDTO"; +import { api } from "@services/http.service"; -export function ExerciseCard({ ...rest }: ExerciseCardProps) { +interface ExerciseCardProps extends TouchableOpacityProps { + data: ExerciseDTO + } + +export function ExerciseCard({data, ...rest }: ExerciseCardProps) { return ( Imagem do exercício - Remada Alternada + {data.name} - 2 séries x 12 repetições + {data.series} séries x {data.repetitions} repetições diff --git a/src/components/HistoryCard.tsx b/src/components/HistoryCard.tsx index 1b74935..fcf07b3 100644 --- a/src/components/HistoryCard.tsx +++ b/src/components/HistoryCard.tsx @@ -1,20 +1,25 @@ +import { HistoryDTO } from "@dtos/historyDTO"; import { Heading, HStack, Text, VStack } from "native-base"; -export function HistoryCard() { +interface HistoryCardProps { + data: HistoryDTO +} + +export function HistoryCard({ data }: HistoryCardProps) { return ( - Costas + {data.group} - Puxada frontal + {data.name} - 08:56 + {data.hour} ) diff --git a/src/components/HomeHeader.tsx b/src/components/HomeHeader.tsx index 04f0e75..46ef64c 100644 --- a/src/components/HomeHeader.tsx +++ b/src/components/HomeHeader.tsx @@ -1,13 +1,27 @@ import { Heading, HStack, Icon, Text, VStack } from "native-base"; import { MaterialIcons } from '@expo/vector-icons' import { TouchableOpacity } from "react-native"; + +import { api } from "@services/http.service"; + +import { useAuth } from "@hooks/useAuth"; + +import defaulUserPhotoImg from '@assets/userPhotoDefault.png'; + import { UserPhoto } from "./UserPhoto"; export function HomeHeader() { + + const { user, signOut } = useAuth() + + console.info(`${api.defaults.baseURL}/avatar/${user.avatar}`) + return ( - Carlos + {user.name} - + Promise; + updateUserProfile: (userUpdated: UserDTO) => Promise; + signOut: () => Promise; + isLoadingUserStorageData: boolean; +}; + +type AuthContextProviderProps = { + children: ReactNode; +}; + +export const AuthContext = createContext( + {} as AuthContextDataProps +); + +export function AuthContextProvider({ children }: AuthContextProviderProps) { + const [user, setUser] = useState({} as UserDTO); + const [isLoadingUserStorageData, setIsLoadingUserStorageData] = + useState(true); + + async function userAndTokenUpdate(userData: UserDTO, token: string) { + api.defaults.headers.common["Authorization"] = `Bearer ${token}`; + + setUser(userData); + } + + async function storageUserAndTokenSave( + userData: UserDTO, + token: string, + refresh_token: string + ) { + try { + setIsLoadingUserStorageData(true); + await storageUserSave(userData); + await storageAuthTokenSave({ token, refresh_token }); + } catch (error) { + throw error; + } finally { + setIsLoadingUserStorageData(false); + } + } + + async function singIn(email: string, password: string) { + try { + const { data } = await api.post("/sessions", { email, password }); + + if (data.user && data.token && data.refresh_token) { + await storageUserAndTokenSave( + data.user, + data.token, + data.refresh_token + ); + userAndTokenUpdate(data.user, data.token); + } + } catch (error) { + throw error; + } finally { + setIsLoadingUserStorageData(false); + } + } + + async function signOut() { + try { + setIsLoadingUserStorageData(true); + setUser({} as UserDTO); + await storageUserRemove(); + await storageAuthTokenRemove(); + } catch (error) { + throw error; + } finally { + setIsLoadingUserStorageData(false); + } + } + + async function updateUserProfile(userUpdated: UserDTO) { + try { + setUser(userUpdated); + await storageUserSave(userUpdated); + } catch (error) { + throw error; + } + } + + async function loadUserData() { + try { + setIsLoadingUserStorageData(true); + + const userLogged = await storageUserGet(); + const { token } = await storageAuthTokenGet(); + + if (token && userLogged) { + userAndTokenUpdate(userLogged, token); + } + } catch (error) { + throw error; + } finally { + setIsLoadingUserStorageData(false); + } + } + + useEffect(() => { + loadUserData(); + }, []); + + useEffect(() => { + const subscribe = api.registerInterceptTokenManager(signOut); + + return () => { + subscribe(); + }; + }, [signOut]); + + return ( + + {children} + + ); +} diff --git a/src/dtos/ExerciseDTO.ts b/src/dtos/ExerciseDTO.ts new file mode 100644 index 0000000..e0989a4 --- /dev/null +++ b/src/dtos/ExerciseDTO.ts @@ -0,0 +1,10 @@ +export interface ExerciseDTO { + id: string, + demo: string, + group: string, + name: string, + repetitions: string, + series: number, + thumb: string, + updated_at: string, +} \ No newline at end of file diff --git a/src/dtos/HistoryByDayDTO.ts b/src/dtos/HistoryByDayDTO.ts new file mode 100644 index 0000000..7ab5ee3 --- /dev/null +++ b/src/dtos/HistoryByDayDTO.ts @@ -0,0 +1,5 @@ +import { HistoryDTO } from '@dtos/historyDTO'; +export interface HistoryByDayDTO{ + title: string, + data: HistoryDTO[] +} \ No newline at end of file diff --git a/src/dtos/HistoryDTO.ts b/src/dtos/HistoryDTO.ts new file mode 100644 index 0000000..2abdd6b --- /dev/null +++ b/src/dtos/HistoryDTO.ts @@ -0,0 +1,7 @@ +export interface HistoryDTO { + id: string, + name: string, + group: string, + hour: string, + created_at: string +} \ No newline at end of file diff --git a/src/dtos/UserDTO.ts b/src/dtos/UserDTO.ts new file mode 100644 index 0000000..3e9ada1 --- /dev/null +++ b/src/dtos/UserDTO.ts @@ -0,0 +1,6 @@ +export interface UserDTO { + id: string; + name: string; + email: string; + avatar: string; +} diff --git a/src/hooks/useAuth.tsx b/src/hooks/useAuth.tsx new file mode 100644 index 0000000..fb00a1c --- /dev/null +++ b/src/hooks/useAuth.tsx @@ -0,0 +1,7 @@ +import { useContext } from "react"; +import { AuthContext } from "@contexts/AuthContext"; + +export function useAuth() { + const context = useContext(AuthContext) + return context +} \ No newline at end of file diff --git a/src/routes/app.routes.tsx b/src/routes/app.routes.tsx index 784d346..57c4a43 100644 --- a/src/routes/app.routes.tsx +++ b/src/routes/app.routes.tsx @@ -13,7 +13,7 @@ import { Profile } from '@screens/Profile'; type AppRoutes = { home: undefined; - exercise: undefined; + exercise: { exerciseId: string}; profile: undefined; history: undefined; } diff --git a/src/routes/index.tsx b/src/routes/index.tsx index 9c527dc..44090a5 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -1,21 +1,32 @@ + import { DefaultTheme, NavigationContainer } from "@react-navigation/native"; import { SafeAreaView } from "react-native-safe-area-context"; import { useTheme } from "native-base"; +import { useAuth } from '@hooks/useAuth' import { AuthRoutes } from "./auth.routes"; import { AppRoutes } from "./app.routes"; +import { Loading } from "@components/Loading"; + export function Routes() { const { colors } = useTheme() + const { user, isLoadingUserStorageData } = useAuth() const theme = DefaultTheme theme.colors.background = colors.gray[700] + if (isLoadingUserStorageData) { + return ( + + ) + } + return ( - + {user.id ? : } ) diff --git a/src/screens/Exercise.tsx b/src/screens/Exercise.tsx index 41ee7f4..9a89a04 100644 --- a/src/screens/Exercise.tsx +++ b/src/screens/Exercise.tsx @@ -1,24 +1,95 @@ -import { useNavigation } from "@react-navigation/native"; -import { Box, Heading, HStack, Icon, Image, ScrollView, Text, VStack } from "native-base"; +import { useEffect, useState } from "react"; import { Platform, TouchableOpacity } from 'react-native' +import { useNavigation, useRoute } from "@react-navigation/native"; + +import { Box, Heading, HStack, Icon, Image, ScrollView, Text, VStack, useToast } from "native-base"; import { Feather } from '@expo/vector-icons' import { AppNavigatorRoutesProps } from "@routes/app.routes"; +import { api } from "@services/http.service"; + import BodySvg from '@assets/body.svg' import SeriesSvg from '@assets/series.svg' import RepetitionSvg from '@assets/repetitions.svg' + import { Button } from "@components/Button"; +import { Loading } from "@components/Loading"; + +import { AppError } from "@utils/AppError"; + +import { ExerciseDTO } from "@dtos/ExerciseDTO"; + +interface RouteParamsProps { + exerciseId: string +} export function Exercise() { + const [sendingRegister, setSendingRegister] = useState(false) + const [isLoading, setIsLoading] = useState(true) + const [exercise, setExercise] = useState({} as ExerciseDTO) const navigation = useNavigation() + const toast = useToast() + const route = useRoute() + + const { exerciseId } = route.params as RouteParamsProps + + function handleGoBack() { navigation.goBack() } + async function fetchExerciseDetails() { + setIsLoading(true) + try { + const response = await api.get(`/exercises/${exerciseId}`) + // console.info(`${api.defaults.baseURL}/exercise/demo/${response.data?.demo}`) + setExercise(response.data) + } catch (error) { + const isAppError = error instanceof AppError + const title = isAppError ? error.message : 'Não foi possível carregar os detalhes do exercício.' + toast.show({ + title: title, + placement: 'top', + bgColor: 'red.500' + }) + } finally { + setIsLoading(false) + } + } + + async function handleSendingRegister() { + try { + setSendingRegister(true) + await api.post('/history', { exercise_id: exerciseId }) + + toast.show({ + title: "Parabens! Exercício registrado no seu histórico.", + placement: 'top', + bgColor: 'green.700' + }) + + navigation.navigate('history') + } catch (error) { + const isAppError = error instanceof AppError + const title = isAppError ? error.message : 'Não foi possível registrar o exercício.' + toast.show({ + title: title, + placement: 'top', + bgColor: 'red.500' + }) + } finally { + setSendingRegister(false) + } + } + + useEffect(() => { + fetchExerciseDetails() + }, [exerciseId]) + return ( @@ -28,52 +99,56 @@ export function Exercise() { - Puxada frontal + {exercise.name} - Costas + {exercise.group} - - - Nome do exercício - - - - - - - 3 séries - - + {isLoading ? : + + + + Nome do exercício + - - - - 12 repetições - + + + + + + {exercise.series} séries + + + + + + + {exercise.repetitions} repetições + + - - -