Skip to content

Commit

Permalink
Merge pull request #18 from SofiaCantero24/favorites
Browse files Browse the repository at this point in the history
feat: favorites view and API integration
  • Loading branch information
SofiaCantero24 authored Dec 17, 2024
2 parents 42b74a2 + 23582c2 commit f511213
Show file tree
Hide file tree
Showing 20 changed files with 511 additions and 105 deletions.
14 changes: 14 additions & 0 deletions src/api/common/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
useMutation,
} from '@tanstack/react-query';

import { API_CONSTS } from '../consts';
import type { PaginateQuery } from '../types';

type KeyParams = {
Expand Down Expand Up @@ -123,3 +124,16 @@ export const parseAxiosError = (error: any) => {
}
return DEFAULT_ERROR_MESSAGE;
};

export const getPage = (lastPage: any): number | undefined => {
const nextUrl = lastPage.pagination.next_url;
const queryString = nextUrl.split('?')[1];
const urlParams = new URLSearchParams(queryString);
const page = urlParams.get('page');
return page ? parseInt(page, 10) : undefined;
};

export const DEFAULT_PAGE_PARAMS = {
getNextPageParam: (lastPage: any) => getPage(lastPage),
initialPageParam: API_CONSTS.INITIAL_PAGE,
};
1 change: 1 addition & 0 deletions src/api/consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export const QUERY_KEYS = {
PRODUCTS: ['products'],
ITEM_DETAILS: ['getItemDetails'],
PURCHASES: ['purchases'],
FAVORITES: ['favorites'],
};

export const API_CONSTS = {
Expand Down
35 changes: 35 additions & 0 deletions src/api/favorites/add-favorite.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { type UseMutationOptions, useQueryClient } from '@tanstack/react-query';

import { showErrorMessage } from '@/ui';

import { client, parseAxiosError, useBaseMutation } from '../common';
import type { FavoriteProduct } from './types';

export const addFavorite = async (id: number): Promise<FavoriteProduct> => {
try {
const { data } = await client.post(`/products/${id}/favorite`, {});
return data;
} catch (error) {
throw parseAxiosError(error);
}
};

export const useAddFavorite = (
props: UseMutationOptions<FavoriteProduct, string, number, any>
) => {
const queryClient = useQueryClient();

return useBaseMutation<number, FavoriteProduct>({
mutationKey: ['addFavorite'],
mutationFn: addFavorite,
onSuccess: (data, variables, context) => {
queryClient.invalidateQueries({ queryKey: ['products'] });
props?.onSuccess?.(data, variables, context);
},
onError: (error, variables, context) => {
showErrorMessage('Something went wrong');
props?.onError?.(error, variables, context);
},
...props,
});
};
35 changes: 35 additions & 0 deletions src/api/favorites/remove-favorite.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { type UseMutationOptions, useQueryClient } from '@tanstack/react-query';

import { showErrorMessage } from '@/ui';

import { client } from '../common';
import { parseAxiosError, useBaseMutation } from '../common/utils';

export const removeFavorite = async (id: number): Promise<null> => {
try {
const { data } = await client.delete(`/products/${id}/favorite`);
return data;
} catch (error) {
throw parseAxiosError(error);
}
};

export const useRemoveFavorite = (
props: UseMutationOptions<null, string, number, any>
) => {
const queryClient = useQueryClient();

return useBaseMutation<number, null>({
mutationKey: ['removeFavorite'],
mutationFn: removeFavorite,
onSuccess: (data, variables, context) => {
queryClient.invalidateQueries({ queryKey: ['products'] });
props?.onSuccess?.(data, variables, context);
},
onError: (error, variables, context) => {
showErrorMessage('Something went wrong');
props?.onError?.(error, variables, context);
},
...props,
});
};
11 changes: 11 additions & 0 deletions src/api/favorites/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { Pagination, Product } from '../types';

export type FavoriteProduct = {
id: number;
product: Product;
};

export type FavoriteProductsResponse = {
data: FavoriteProduct[];
pagination: Pagination;
};
29 changes: 29 additions & 0 deletions src/api/favorites/use-favorites.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { createInfiniteQuery } from 'react-query-kit';

import { client, DEFAULT_PAGE_PARAMS } from '../common';
import { API_CONSTS, QUERY_KEYS } from '../consts';
import type { FavoriteProductsResponse } from './types';

type Variables = {
items: number;
};

export const useFavorites = createInfiniteQuery({
queryKey: QUERY_KEYS.FAVORITES,
fetcher: async (
variables: Variables,
{ pageParam = API_CONSTS.INITIAL_PAGE }
): Promise<FavoriteProductsResponse> => {
const { data } = await client.get<FavoriteProductsResponse>(
'/products/favorites',
{
params: {
page: pageParam,
items: variables.items,
},
}
);
return data;
},
...DEFAULT_PAGE_PARAMS,
});
18 changes: 16 additions & 2 deletions src/api/products/use-details.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,22 @@ import { useQuery } from '@tanstack/react-query';

import { client, parseAxiosError } from '../common';
import { QUERY_KEYS } from '../consts';
import type { Product } from './types';
export const getItemDetails = async (id: number): Promise<Product> => {
import type { Category } from '../types';

type DetailProduct = {
id: number;
title: string;
description: string;
state: string;
stock: number;
isFavorite: boolean;
unit_price: string;
pictures: string[];
category: Category;
subcategories: Category[];
};

export const getItemDetails = async (id: number): Promise<DetailProduct> => {
try {
const { data } = await client.get(`products/${id}`);
return data;
Expand Down
11 changes: 2 additions & 9 deletions src/api/products/use-products.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { createInfiniteQuery } from 'react-query-kit';

import { client } from '../common';
import { client, DEFAULT_PAGE_PARAMS } from '../common';
import { API_CONSTS, QUERY_KEYS } from '../consts';
import type { FetchProductsResponse } from './types';

Expand All @@ -24,12 +24,5 @@ export const useProducts = createInfiniteQuery({
});
return data;
},
getNextPageParam: (lastPage) => {
const nextUrl = lastPage.pagination.next_url;
const queryString = nextUrl.split('?')[1];
const urlParams = new URLSearchParams(queryString);
const page = urlParams.get('page');
return page ? parseInt(page, 10) : undefined;
},
initialPageParam: 1,
...DEFAULT_PAGE_PARAMS,
});
11 changes: 2 additions & 9 deletions src/api/purchase/get-purchases.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { createInfiniteQuery } from 'react-query-kit';

import { client } from '../common';
import { client, DEFAULT_PAGE_PARAMS } from '../common';
import { API_CONSTS, QUERY_KEYS } from '../consts';
import type { PurchasesResponse } from './types';

Expand All @@ -22,12 +22,5 @@ export const usePurchases = createInfiniteQuery({
});
return data;
},
getNextPageParam: (lastPage) => {
const nextUrl = lastPage.pagination.next_url;
const queryString = nextUrl.split('?')[1];
const urlParams = new URLSearchParams(queryString);
const page = urlParams.get('page');
return page ? parseInt(page, 10) : undefined;
},
initialPageParam: 1,
...DEFAULT_PAGE_PARAMS,
});
19 changes: 19 additions & 0 deletions src/api/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,22 @@
export type Product = {
id: number;
title: string;
description: string;
state: string;
stock: number;
is_favorite: boolean;
unit_price: string;
pictures: string[];
category: Category;
subcategories: Category[];
};

export type Category = {
id: number;
name: string;
description: string;
};

export type PaginateQuery<T> = {
results: T[];
count: number;
Expand Down
14 changes: 7 additions & 7 deletions src/app/(app)/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,24 +50,24 @@ const tabs = [
headerShown: false,
},
{
name: 'settings',
name: 'favorites',
title: 'Settings',
icon: FavoriteIcon,
testID: 'setting-tab',
testID: 'payment-tab',
headerShown: false,
},
{
name: 'favorites',
name: 'settings',
title: 'Settings',
icon: MenuIcon,
testID: 'settin-tab',
testID: 'setting-tab',
headerShown: false,
},
{
name: 'payment',
title: 'Payment',
icon: FavoriteIcon,
testID: 'payment-tab',
title: 'Settings',
icon: MenuIcon,
testID: 'setting-tab',
headerShown: false,
},
];
Expand Down
77 changes: 62 additions & 15 deletions src/app/(app)/favorites.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,68 @@
import { Buttons } from '@/components/buttons';
import { Colors } from '@/components/colors';
import { Inputs } from '@/components/inputs';
import { Typography } from '@/components/typography';
import { FocusAwareStatusBar, SafeAreaView, ScrollView } from '@/ui';
import { useFocusEffect } from 'expo-router';
import { useCallback } from 'react';
import { FlatList } from 'react-native';
import { twMerge } from 'tailwind-merge';

import { API_CONSTS } from '@/api/consts';
import { useFavorites } from '@/api/favorites/use-favorites';
import { FavoriteCard } from '@/components/favourites/favorite-card';
import { HeaderLogo } from '@/components/header-logo';
import { SafeAreaView, Text, View } from '@/ui';

export default function Favorite() {
const { data, hasNextPage, fetchNextPage, isFetchingNextPage, refetch } =
useFavorites({ variables: { items: API_CONSTS.INITIAL_ITEMS } });

const handleLoadMore = () => {
if (hasNextPage && !isFetchingNextPage) {
fetchNextPage();
}
};

useFocusEffect(
useCallback(() => {
refetch();
}, [refetch])
);

const productsToDisplay = data?.pages.flatMap((page) => page.data) || [];

export default function Style() {
return (
<>
<FocusAwareStatusBar />
<ScrollView className="px-4">
<SafeAreaView className="flex-1">
<Typography />
<Colors />
<Buttons />
<Inputs />
</SafeAreaView>
</ScrollView>
<SafeAreaView className="flex-1">
<View className="bg-light_background pb-52">
<HeaderLogo />
<Text className="my-4 px-4 text-lg">My favourites</Text>
<FlatList
className="pb-6"
onEndReached={handleLoadMore}
data={productsToDisplay}
renderItem={({ item, index }) => {
const isFirstItem = index === 0;
const isLastItem = index === productsToDisplay.length - 1;

return (
<View
className={twMerge(
'mx-4 border',
isFirstItem && 'rounded-t-lg',
isLastItem && 'rounded-b-lg'
)}
>
<FavoriteCard
id={item.product.id}
price={item.product.unit_price}
state={item.product.state}
name={item.product.title}
image_url={item.product.pictures[0]}
/>
</View>
);
}}
keyExtractor={(item) => item.id.toString()}
/>
</View>
</SafeAreaView>
</>
);
}
Loading

0 comments on commit f511213

Please sign in to comment.