diff --git a/src/components/MyLatestReviews.tsx b/src/components/MyLatestReviews.tsx new file mode 100644 index 0000000..2378ff6 --- /dev/null +++ b/src/components/MyLatestReviews.tsx @@ -0,0 +1,96 @@ +import { ActivityIndicator, FlatList, View } from "react-native"; +import { useUser } from "../hooks"; +import { useUserReviews } from "../hooks/useUserReviews"; +import { Card } from "./Card"; +import { StyledText } from "./StyledText"; +import { useScreenInfo } from "../hooks/useScreenInfo"; +import { SmallReviewCard } from "./SmallReviewCard"; +import { useEffect, useMemo } from "react"; +import { usePlatformWideReviews } from "../hooks/usePlatformWideReviews"; + +export const MyLatestReviews = ({ + allowPlatformWideReviews = true, +}: { + allowPlatformWideReviews?: boolean; +}) => { + const me = useUser()!; + const userReviews = useUserReviews(me.id); + const platformWideReviews = usePlatformWideReviews(); + const { isPhone } = useScreenInfo(); + + useEffect(() => { + if ( + userReviews.isFetched && + userReviews.data && + userReviews.data.length == 0 && + !platformWideReviews.isFetched + ) { + platformWideReviews.refetch(); + } + }, [userReviews.data, userReviews.isFetched, platformWideReviews.isFetched]); + + const displayLoading = useMemo(() => { + if (!userReviews.isFetched) { + return true; + } + if ( + userReviews.data && + userReviews.data.length === 0 && + allowPlatformWideReviews && + !platformWideReviews.isFetched + ) { + return true; + } + + return false; + }, [ + userReviews.data, + userReviews.isFetched, + allowPlatformWideReviews, + platformWideReviews.isFetched, + ]); + + const body = useMemo(() => { + if (displayLoading) { + return ; + } + + let reviews = userReviews.data!; + + if (!reviews.length && allowPlatformWideReviews) { + reviews = platformWideReviews.data!; + } + + if (!reviews.length) { + return No reviews to display.; + } + + return ( + ( + + )} + /> + ); + }, [displayLoading, userReviews.data]); + + return ( + {body}} + hideHeaderDivider + headerComponent={ + + {userReviews.isLoading || platformWideReviews.data?.length + ? "Latest reviews" + : "My latest reviews"} + + } + /> + ); +}; diff --git a/src/components/ReviewsList.tsx b/src/components/ReviewsList.tsx deleted file mode 100644 index fcd1568..0000000 --- a/src/components/ReviewsList.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { View, FlatList, ActivityIndicator } from "react-native"; -import { useScreenInfo } from "../hooks/useScreenInfo"; -import { SmallReviewCard } from "./SmallReviewCard"; -import { StyledText } from "./StyledText"; -import { useUserReviews } from "../hooks/useUserReviews"; - -export const ReviewsList = ({ userId }: { userId: string }) => { - const reviews = useUserReviews(userId); - const { isPhone } = useScreenInfo(); - - if (reviews.isLoading) { - return ; - } - - return ( - - {!reviews.isFetched && ( - - )} - {reviews.isFetched && !!reviews.data && !reviews.data.length && ( - You have no reviews. - )} - {reviews.data && reviews.isFetched && !!reviews.data.length && ( - ( - - )} - /> - )} - - ); -}; diff --git a/src/hooks/usePlatformWideReviews.ts b/src/hooks/usePlatformWideReviews.ts new file mode 100644 index 0000000..f24b749 --- /dev/null +++ b/src/hooks/usePlatformWideReviews.ts @@ -0,0 +1,11 @@ +import { useQuery } from "@tanstack/react-query"; +import { User } from "../types"; +import { getPlatformWideReviews } from "../utils/api"; + +export function usePlatformWideReviews() { + return useQuery({ + queryKey: ["latest-reviews"], + queryFn: () => getPlatformWideReviews(), + enabled: false, + }); +} diff --git a/src/screens/HomeScreen/HomeScreen.tsx b/src/screens/HomeScreen/HomeScreen.tsx index e54b548..7393822 100644 --- a/src/screens/HomeScreen/HomeScreen.tsx +++ b/src/screens/HomeScreen/HomeScreen.tsx @@ -1,15 +1,13 @@ import { View, ScrollView } from "react-native"; import React from "react"; import { DrawerHeader } from "../../components/headers/DrawerHeader"; -import { Card, MyReviewsCard, StyledText } from "../../components"; +import { MyReviewsCard } from "../../components"; import { ConnectionReviewCard } from "../../components/ConnectionsReviewCard"; import { SocialMediaCard } from "../../icons/SocialMediaCard"; import { useUser } from "../../hooks"; -import { ReviewsList } from "../../components/ReviewsList"; +import { MyLatestReviews } from "../../components/MyLatestReviews"; const HomeScreen = ({}: {}) => { - const user = useUser()!; - return ( @@ -17,15 +15,7 @@ const HomeScreen = ({}: {}) => { - } - hideHeaderDivider - headerComponent={ - - My latest reviews - - } - /> + diff --git a/src/screens/MyProfileScreen/MyProfileScreen.tsx b/src/screens/MyProfileScreen/MyProfileScreen.tsx index 6c0e4ff..f397bc4 100644 --- a/src/screens/MyProfileScreen/MyProfileScreen.tsx +++ b/src/screens/MyProfileScreen/MyProfileScreen.tsx @@ -1,17 +1,10 @@ -import { View, FlatList, ScrollView, ActivityIndicator } from "react-native"; +import { View, ScrollView } from "react-native"; import React from "react"; import { DrawerHeader } from "../../components/headers/DrawerHeader"; -import { - Card, - HorizontalDivider, - MyReviewsCard, - ProfileCard, - StyledText, -} from "../../components"; +import { HorizontalDivider, ProfileCard } from "../../components"; -import { ReviewsList } from "../../components/ReviewsList"; import { useUser } from "../../hooks"; -import { useUserRatings } from "../../hooks/useUserRatings"; +import { MyLatestReviews } from "../../components/MyLatestReviews"; export const MyProfileScreen = ({}: {}) => { const me = useUser()!; @@ -26,15 +19,7 @@ export const MyProfileScreen = ({}: {}) => { - } - hideHeaderDivider - headerComponent={ - - My latest reviews - - } - /> + diff --git a/src/utils/api.ts b/src/utils/api.ts index 9ef4660..cd94993 100644 --- a/src/utils/api.ts +++ b/src/utils/api.ts @@ -1,9 +1,15 @@ -import { Connection, User, UserSettings, UserWithCounts, UserWithToken } from '../types'; -import { DbNotification } from '../types/Notification'; -import { Rating } from '../types/Rating'; -import { Review } from '../types/Review'; -import storage from './storage'; -import Base64 from 'Base64'; +import { + Connection, + User, + UserSettings, + UserWithCounts, + UserWithToken, +} from "../types"; +import { DbNotification } from "../types/Notification"; +import { Rating } from "../types/Rating"; +import { Review } from "../types/Review"; +import storage from "./storage"; +import Base64 from "Base64"; // @ts-ignore TODO: Fix this export const baseUrl = process.env.EXPO_PUBLIC_API_BASE_URL; @@ -18,7 +24,12 @@ type FetchInit = RequestInit; const enhancedFetch = async ( initialInput: FetchInput, initialInit: FetchInit, - preRequestMiddlewares?: Array<(input: FetchInput, init: FetchInit) => { input: FetchInput; init: FetchInit }>, + preRequestMiddlewares?: Array< + ( + input: FetchInput, + init: FetchInit + ) => { input: FetchInput; init: FetchInit } + >, afterRequestMiddlewares?: Array<(before: any) => any> ) => { const inputWithMiddlewares = (preRequestMiddlewares || []).reduce<{ @@ -30,10 +41,13 @@ const enhancedFetch = async ( }); const result = fetch(inputWithMiddlewares.input, inputWithMiddlewares.init); - const processedResult = await (afterRequestMiddlewares || []).reduce(async (previosPromise, nextFn) => { - const prevResult = await previosPromise; - return nextFn(prevResult); - }, result); + const processedResult = await (afterRequestMiddlewares || []).reduce( + async (previosPromise, nextFn) => { + const prevResult = await previosPromise; + return nextFn(prevResult); + }, + result + ); return processedResult; }; @@ -61,17 +75,20 @@ const normalizeResult = async (response: Response) => { return responseData; }; -const authorizedFetch = async (input: FetchInput, init: FetchInit | object): Promise => { +const authorizedFetch = async ( + input: FetchInput, + init: FetchInit | object +): Promise => { const token = await storage.getItem(storage.TOKEN_KEY); - console.log('token is ', token); + console.log("token is ", token); return enhancedFetch( input, init, [ addHeaders({ - 'Content-Type': 'application/json', + "Content-Type": "application/json", Authorization: `Bearer ${token}`, }), stringifyBody(), @@ -84,10 +101,10 @@ export type SignupInput = { email: string; }; -export async function signUp(email: SignupInput['email']): Promise { +export async function signUp(email: SignupInput["email"]): Promise { const response = await fetch(signUpUrl, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, + method: "POST", + headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email }), }); const responseData = await response.json(); @@ -95,7 +112,7 @@ export async function signUp(email: SignupInput['email']): Promise { if (response.ok) { return responseData; } else if (response.status === 409) { - throw new Error('There is already an account with this email.'); + throw new Error("There is already an account with this email."); } else { throw new Error(responseData.message); } @@ -105,10 +122,10 @@ export type SigninInput = { email: string; }; -export async function signIn(email: SigninInput['email']): Promise { +export async function signIn(email: SigninInput["email"]): Promise { const response = await fetch(signInUrl, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, + method: "POST", + headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email }), }); const responseData = await response.json(); @@ -125,12 +142,12 @@ export type VerifyEmailInput = { }; export async function verifyEmail( - email: VerifyEmailInput['email'], - code: VerifyEmailInput['code'] + email: VerifyEmailInput["email"], + code: VerifyEmailInput["code"] ): Promise { const response = await fetch(verifyEmailUrl, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, + method: "POST", + headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email, code }), }); const responseData = await response.json(); @@ -142,13 +159,16 @@ export async function verifyEmail( } export async function getSearchUserResult(query: string) { - return authorizedFetch(`${baseUrl}/connections/search/${encodeURIComponent(query)}`, { - method: 'GET', - }); + return authorizedFetch( + `${baseUrl}/connections/search/${encodeURIComponent(query)}`, + { + method: "GET", + } + ); } export async function getMe(): Promise { - return authorizedFetch(`${baseUrl}/user`, { method: 'GET' }); + return authorizedFetch(`${baseUrl}/user`, { method: "GET" }); } export type RegenerateCodeInput = { @@ -156,7 +176,7 @@ export type RegenerateCodeInput = { }; export async function regenerateCode(data: RegenerateCodeInput) { return enhancedFetch(`${baseUrl}/auth/regenerate-code/${data.email}`, { - method: 'PUT', + method: "PUT", }); } @@ -168,74 +188,87 @@ export type UpdateProfileData = { export async function updateProfileData(data: UpdateProfileData) { return authorizedFetch(`${baseUrl}/user`, { - method: 'PUT', + method: "PUT", body: data, }); } export async function updateProfilePicture(uri: string) { return authorizedFetch(`${baseUrl}/user/profile-picture`, { - method: 'PUT', + method: "PUT", body: { file: uri }, }); } -export async function getConnection(userId: User['id']): Promise { +export async function getConnection(userId: User["id"]): Promise { return authorizedFetch(`${baseUrl}/connections/${userId}`, { - method: 'GET', + method: "GET", }); } -export async function getUserAvgRatings(userId: User['id']): Promise { +export async function getUserAvgRatings(userId: User["id"]): Promise { return authorizedFetch(`${baseUrl}/reviews/avg-rating/${userId}`, { - method: 'GET', + method: "GET", }); } -export async function getUserReviews(userId: User['id']): Promise { +export async function getUserReviews(userId: User["id"]): Promise { return authorizedFetch(`${baseUrl}/reviews/${userId}`, { - method: 'GET', + method: "GET", }); } -export async function likeReview(reviewId: Review['id']): Promise { +export async function likeReview(reviewId: Review["id"]): Promise { return authorizedFetch(`${baseUrl}/reviews/${reviewId}/like`, { - method: 'POST', + method: "POST", }); } -export async function unlikeReview(reviewId: Review['id']): Promise { +export async function unlikeReview(reviewId: Review["id"]): Promise { return authorizedFetch(`${baseUrl}/reviews/${reviewId}/like`, { - method: 'DELETE', + method: "DELETE", }); } +export async function getPlatformWideReviews(): Promise { + return authorizedFetch(`${baseUrl}/reviews/latest`, { method: "GET" }); +} + export async function getConnections(): Promise { return authorizedFetch(`${baseUrl}/connections`, { - method: 'GET', + method: "GET", }); } -export async function getSuggestedForReviewConnections(): Promise { +export async function getSuggestedForReviewConnections(): Promise< + Connection[] +> { return authorizedFetch(`${baseUrl}/connections`, { - method: 'GET', + method: "GET", }); } -export async function connectWithUser(userId: Connection['id']): Promise { +export async function connectWithUser( + userId: Connection["id"] +): Promise { return authorizedFetch(`${baseUrl}/connections/connect/${userId}`, { - method: 'POST', + method: "POST", }); } -export async function unconnectWithUser(userId: Connection['id']): Promise { +export async function unconnectWithUser( + userId: Connection["id"] +): Promise { return authorizedFetch(`${baseUrl}/connections/connect/${userId}`, { - method: 'DELETE', + method: "DELETE", }); } export async function searchByUserLink(link: string): Promise { - return authorizedFetch(`${baseUrl}/connections/search-by-external-profile/${Base64.btoa(link)}`, { method: 'GET' }); + return authorizedFetch( + `${baseUrl}/connections/search-by-external-profile/${Base64.btoa(link)}`, + { method: "GET" } + ); } export type SendReviewData = { @@ -248,7 +281,7 @@ export type SendReviewData = { export async function sendReview(ratedUserId: string, data: SendReviewData) { return authorizedFetch(`${baseUrl}/reviews`, { - method: 'POST', + method: "POST", body: { postedToId: ratedUserId, review: data, @@ -258,57 +291,62 @@ export async function sendReview(ratedUserId: string, data: SendReviewData) { export async function getMyReviewForUser(userId: string): Promise { return authorizedFetch(`${baseUrl}/reviews/my-review/${userId}`, { - method: 'GET', + method: "GET", }); } export async function deleteReview(reviewId: string) { return authorizedFetch(`${baseUrl}/reviews/${reviewId}`, { - method: 'DELETE', + method: "DELETE", }); } -export async function updateReview(reviewId: string, reviewData: SendReviewData): Promise { +export async function updateReview( + reviewId: string, + reviewData: SendReviewData +): Promise { return authorizedFetch(`${baseUrl}/reviews/${reviewId}`, { - method: 'PUT', + method: "PUT", body: reviewData, }); } export async function addPushToken(token: string) { return authorizedFetch(`${baseUrl}/notifications/token`, { - method: 'POST', + method: "POST", body: { token }, }); } export async function getNotifications(): Promise { - return authorizedFetch(`${baseUrl}/notifications`, { method: 'GET' }); + return authorizedFetch(`${baseUrl}/notifications`, { method: "GET" }); } export async function getReviewsPostedByMe(): Promise { - return authorizedFetch(`${baseUrl}/reviews/posted`, { method: 'GET' }); + return authorizedFetch(`${baseUrl}/reviews/posted`, { method: "GET" }); } export async function getReviewdUsers(): Promise { - return authorizedFetch(`${baseUrl}/connections/reviewed`, { method: 'GET' }); + return authorizedFetch(`${baseUrl}/connections/reviewed`, { method: "GET" }); } export async function getUserSettings(): Promise { - return authorizedFetch(`${baseUrl}/user/settings`, { method: 'GET' }); + return authorizedFetch(`${baseUrl}/user/settings`, { method: "GET" }); } export type UpdateUserSettingsData = { reviewsVisible?: boolean; anonymous?: boolean; }; -export async function updateUserSettings(data: UpdateUserSettingsData): Promise { +export async function updateUserSettings( + data: UpdateUserSettingsData +): Promise { return authorizedFetch(`${baseUrl}/user/settings`, { - method: 'PUT', + method: "PUT", body: data, }); } export async function deleteAccount() { - return authorizedFetch(`${baseUrl}/user`, { method: 'DELETE' }); + return authorizedFetch(`${baseUrl}/user`, { method: "DELETE" }); }