diff --git a/src/api/hooks.ts b/src/api/hooks.ts index 1bcae3a..2e7b05d 100644 --- a/src/api/hooks.ts +++ b/src/api/hooks.ts @@ -11,6 +11,7 @@ export const useGetPosts = (type: string | null) => { fetch(getUrl(type!)) .then((res) => res.json()) .then((data) => data.posts), + select: (data) => data.reverse(), staleTime: 1000 * 60 * 5, }); }; diff --git a/src/app/(app)/index.tsx b/src/app/(app)/index.tsx index a5821f7..6b32b60 100644 --- a/src/app/(app)/index.tsx +++ b/src/app/(app)/index.tsx @@ -1,28 +1,56 @@ import { useGetPosts } from "@api/hooks"; import { isRouteAuthenticated } from "@auth/routes"; +import LoadingSpinner from "@components/LoadingSpinner"; import { PostCard } from "@components/PostCard"; +import { Text, makeStyles } from "@rneui/themed"; import { usePostStore } from "@stores/postStore"; import { useUserStore } from "@stores/userStore"; -import { usePathname } from "expo-router"; -import { StatusBar } from "expo-status-bar"; -import { ScrollView } from "react-native"; +import { Redirect, usePathname } from "expo-router"; +import { ScrollView, View } from "react-native"; -import Login from "./login"; +const useStyles = makeStyles((theme) => ({ + container: { + display: "flex", + justifyContent: "center", + alignItems: "center", + marginTop: 20, + }, + text: { + color: theme.colors.white, + }, + loadingSpinner: { + marginTop: 20, + }, +})); const App = () => { + const styles = useStyles(); const { user } = useUserStore(); const route = usePathname(); const postsType = usePostStore((state) => state.type); const { data } = useGetPosts(postsType); if (!user && isRouteAuthenticated(route)) { - return ; + return ; } return ( - - {data?.map((post) => )} + {postsType && + data && + data.map((post) => )} + + {postsType && !data && ( + + + + )} + + {!postsType && ( + + Please select a type! + + )} ); }; diff --git a/src/app/(app)/login.tsx b/src/app/(app)/login.tsx index c2e3fe7..7c44df8 100644 --- a/src/app/(app)/login.tsx +++ b/src/app/(app)/login.tsx @@ -1,6 +1,4 @@ import { useLoginUser } from "@auth/hooks"; -import ErrorScreen from "@components/ErrorScreen"; -import LoadingSpinner from "@components/LoadingSpinner"; import LoginField from "@components/LoginField"; import { Redirect } from "expo-router"; import { useState } from "react"; @@ -14,13 +12,11 @@ const Login = () => { return ; } - if (loading) { - return ; - } - - if (error) { - return ; - } + const isDisabled = + !email || + !password || + !/^[^@]+@[^@]+\.[^@]+$/g.test(email) || + password.length < 5; return ( { onEmailChange={setEmail} password={password} onPasswordChange={setPassword} - disabled={!email || !password} + disabled={isDisabled} onButtonClick={() => login(email, password)} + isLoading={loading ?? false} + error={error} /> ); }; diff --git a/src/app/(app)/logout.tsx b/src/app/(app)/logout.tsx new file mode 100644 index 0000000..998bcb3 --- /dev/null +++ b/src/app/(app)/logout.tsx @@ -0,0 +1,12 @@ +import { useLogoutUser } from "@auth/hooks"; +import { Redirect } from "expo-router"; + +const Logout = () => { + const { logout } = useLogoutUser(); + + logout(); + + return ; +}; + +export default Logout; diff --git a/src/app/(app)/register.tsx b/src/app/(app)/register.tsx index 9eecf7b..e325ca8 100644 --- a/src/app/(app)/register.tsx +++ b/src/app/(app)/register.tsx @@ -1,6 +1,4 @@ import { useRegisterUser } from "@auth/hooks"; -import ErrorScreen from "@components/ErrorScreen"; -import LoadingSpinner from "@components/LoadingSpinner"; import LoginField from "@components/LoginField"; import { Redirect } from "expo-router"; import { useState } from "react"; @@ -14,13 +12,11 @@ const Register = () => { return ; } - if (loading) { - return ; - } - - if (error) { - return ; - } + const isDisabled = + !email || + !password || + !/^[^@]+@[^@]+\.[^@]+$/g.test(email) || + password.length < 5; return ( { onEmailChange={setEmail} password={password} onPasswordChange={setPassword} - disabled={!email || !password} + disabled={isDisabled} onButtonClick={() => register(email, password)} + isLoading={loading ?? false} + error={error} /> ); }; diff --git a/src/auth/hooks.ts b/src/auth/hooks.ts index 18de9b4..b0b5822 100644 --- a/src/auth/hooks.ts +++ b/src/auth/hooks.ts @@ -1,5 +1,7 @@ +import { queryClient } from "@api/queryClient"; import { firebaseApp } from "@auth/firebase"; import AsyncStorage from "@react-native-async-storage/async-storage"; +import { usePostStore } from "@stores/postStore"; import { useUserStore } from "@stores/userStore"; import { createUserWithEmailAndPassword, @@ -64,6 +66,7 @@ export const useLogoutUser = () => { const { setUser } = useUserStore(); const [loading, setLoading] = useState(null); const [error, setError] = useState(null); + const setType = usePostStore((state) => state.setType); const logout = useCallback(() => { setLoading(true); @@ -72,7 +75,11 @@ export const useLogoutUser = () => { .signOut() .then(() => { setUser(null); + setType(null); setLoading(false); + queryClient.invalidateQueries({ + predicate: (query) => query.queryKey[0] === "posts", + }); }) .catch((error) => { setError(error); diff --git a/src/components/AppHeader.tsx b/src/components/AppHeader.tsx index 78a5ced..00fe707 100644 --- a/src/components/AppHeader.tsx +++ b/src/components/AppHeader.tsx @@ -1,7 +1,9 @@ -import { Header, Text, makeStyles } from "@rneui/themed"; +import { useLogoutUser } from "@auth/hooks"; +import { Button, Chip, Header, Text, makeStyles } from "@rneui/themed"; import { usePostStore } from "@stores/postStore"; +import { useUserStore } from "@stores/userStore"; import React from "react"; -import { ScrollView, TouchableOpacity } from "react-native"; +import { ScrollView } from "react-native"; const useStyles = makeStyles((theme) => ({ headerContainer: { @@ -9,20 +11,34 @@ const useStyles = makeStyles((theme) => ({ justifyContent: "space-around", height: 100, }, - scrollView: { - flexGrow: 0, - }, option: { paddingHorizontal: 10, }, optionText: { color: theme.colors.white, }, + loginText: { + color: theme.colors.white, + }, + chip: { + marginHorizontal: 5, + }, + selectedChip: { + borderStyle: "solid", + borderWidth: 1, + borderColor: theme.colors.white, + }, })); const AppHeader = () => { const styles = useStyles(); - const { setType, types } = usePostStore((state) => state); + const { + setType, + types: postTypes, + type: selectedType, + } = usePostStore((state) => state); + const user = useUserStore((state) => state.user); + const { logout, loading } = useLogoutUser(); const handlePostTypeSelect = (type: string) => { setType(type); @@ -30,21 +46,46 @@ const AppHeader = () => { return (
{ + logout(); + }} + loading={Boolean(loading)} + type="solid" + /> + ) : undefined + } centerComponent={ - {types.map((type, index) => ( - handlePostTypeSelect(type)} - style={styles.option} - > - {type.toUpperCase()} - - ))} + {user ? ( + postTypes.map((postType) => ( + handlePostTypeSelect(postType)} + size="md" + /> + )) + ) : ( + Please login to see posts! + )} } containerStyle={styles.headerContainer} diff --git a/src/components/ErrorScreen.tsx b/src/components/ErrorScreen.tsx index e4b8af4..132261a 100644 --- a/src/components/ErrorScreen.tsx +++ b/src/components/ErrorScreen.tsx @@ -13,6 +13,10 @@ const useStyles = makeStyles((theme) => ({ margin: "auto", color: theme.colors.error, }, + message: { + color: theme.colors.white, + marginBottom: theme.spacing.xl, + }, })); type Props = { @@ -24,7 +28,9 @@ const ErrorScreen = ({ error }: Props) => { return ( - The application has crashed. + + The application has crashed :'( + {error} ); diff --git a/src/components/LoginField.tsx b/src/components/LoginField.tsx index d4b2495..b1ac93f 100644 --- a/src/components/LoginField.tsx +++ b/src/components/LoginField.tsx @@ -1,4 +1,11 @@ -import { Button, Input, Text, makeStyles } from "@rneui/themed"; +import { + Button, + Divider, + Input, + Text, + makeStyles, + useTheme, +} from "@rneui/themed"; import { Link } from "expo-router"; import { NativeSyntheticEvent, @@ -13,13 +20,36 @@ const useStyles = makeStyles((theme) => ({ justifyContent: "center", flex: 1, backgroundColor: theme.colors.background, + width: "100%", }, title: { marginHorizontal: "auto", marginVertical: theme.spacing.xl, + color: theme.colors.white, }, button: { - marginVertical: theme.spacing.xl, + marginTop: theme.spacing.lg, + borderRadius: 2.5, + }, + center: { + display: "flex", + justifyContent: "center", + alignItems: "center", + }, + inputContainer: { + width: "60%", + backgroundColor: theme.colors.background, + paddingTop: theme.spacing.lg, + }, + input: { + color: theme.colors.white, + }, + link: { + color: theme.colors.white, + marginVertical: theme.spacing.lg, + }, + spacing: { + marginVertical: theme.spacing.md, }, })); @@ -31,6 +61,8 @@ type Props = { onPasswordChange: (password: string) => void; disabled: boolean; onButtonClick: () => void; + isLoading: boolean; + error?: Error | null; }; const LoginField = ({ @@ -41,8 +73,11 @@ const LoginField = ({ onPasswordChange, disabled, onButtonClick, + isLoading, + error, }: Props) => { - const classes = useStyles(); + const styles = useStyles(); + const { theme } = useTheme(); const handleKeyPress = ( event: NativeSyntheticEvent, @@ -53,11 +88,19 @@ const LoginField = ({ }; return ( - - - - {mode.toUpperCase()} - + + + {mode.toUpperCase()} + + + + - + + - {mode === "login" ? "No account? Register" : "Have an account? Login"} + {mode === "login" + ? "No account? Register!" + : "Have an account? Login!"} diff --git a/src/components/PostCard.tsx b/src/components/PostCard.tsx index 69211c1..af3987d 100644 --- a/src/components/PostCard.tsx +++ b/src/components/PostCard.tsx @@ -5,6 +5,10 @@ import { Linking, Text, TouchableOpacity } from "react-native"; import { Post } from "../types/Post"; const useStyles = makeStyles((theme) => ({ + card: { + backgroundColor: "#100f38", + borderRadius: 10, + }, image: { width: "100%", height: 200, @@ -33,10 +37,10 @@ type Props = { export const PostCard = ({ post }: Props) => { const styles = useStyles(); - const formatedDate = dateTimeFormat.format(new Date(post.timestamp)); + const formattedDate = dateTimeFormat.format(new Date(post.timestamp)); return ( - + Linking.openURL(post.url)}> {post.thumbnail && ( { {post.description} - {} - Posted on: {formatedDate} + {formattedDate} ); }; diff --git a/src/stores/postStore.ts b/src/stores/postStore.ts index c4c9e14..ca49ed9 100644 --- a/src/stores/postStore.ts +++ b/src/stores/postStore.ts @@ -5,7 +5,7 @@ type PostStore = { type: string | null; isLoading: boolean; setTypes: (types: string[]) => void; - setType: (type: string) => void; + setType: (type: string | null) => void; setIsLoading: (isLoading: boolean) => void; }; @@ -14,6 +14,6 @@ export const usePostStore = create((set) => ({ type: null, isLoading: false, setTypes: (types: string[]) => set({ types }), - setType: (type: string) => set({ type }), + setType: (type: string | null) => set({ type }), setIsLoading: (isLoading: boolean) => set({ isLoading }), })); diff --git a/src/themes/theme.ts b/src/themes/theme.ts index 0d81c8f..e85757a 100644 --- a/src/themes/theme.ts +++ b/src/themes/theme.ts @@ -3,20 +3,23 @@ import { createTheme } from "@rneui/themed"; export const theme = createTheme({ lightColors: { primary: "#e7e7e8", + secondary: "#f5f5f5", + background: "#fff", error: "#ff0000", + warning: "#ffae42", + success: "#00a86b", + white: "#fff", + black: "#000", }, darkColors: { - primary: "#000", - error: "#ff0000", + primary: "#100f38", + secondary: "#6f6f6f", background: "#070624", - secondary: "##0d0c36", - }, - components: { - Card: { - containerStyle: { - backgroundColor: "#100f38", - }, - }, + error: "#ff0000", + warning: "#ffae42", + success: "#00a86b", + white: "#fff", + black: "#000", }, mode: "dark", });