Skip to content

Commit

Permalink
Merge pull request #11 from ayghon/feat/shopping-cart
Browse files Browse the repository at this point in the history
feat(shopping-cart): added logic to add/remove item in cart, added controls in article list
  • Loading branch information
ayghon committed Apr 14, 2023
2 parents f72f8ff + 4e19bc9 commit 3b0f9f3
Show file tree
Hide file tree
Showing 22 changed files with 827 additions and 506 deletions.
16 changes: 12 additions & 4 deletions app/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { NativeBaseProvider } from 'native-base';
import React from 'react';

import { LanguageSelector } from '../components/LanguageSelector';
import { ArticleListHeaderRight } from '../components/article-list/ArticleListHeaderRight';
import { ArticlesProvider } from '../context/articles.context';
import { CodeProvider } from '../context/code.context';
import { useInitI18n } from '../i18n/init-i18n';
Expand All @@ -26,16 +27,23 @@ export default function Layout() {
<NativeBaseProvider theme={customTheme}>
<ArticlesProvider>
<CodeProvider>
<Stack
screenOptions={{
headerRight: LanguageSelector,
}}>
<Stack>
<Stack.Screen
name="index"
options={{
headerTitle: (t || globalT)(
i18nKeys.app.layout.index.header_title
),
headerRight: ArticleListHeaderRight,
}}
/>
<Stack.Screen
name="shopping-cart"
options={{
headerRight: () => <LanguageSelector />,
headerTitle: (t || globalT)(
i18nKeys.app.layout.shopping_cart.header_title
),
}}
/>
<Stack.Screen
Expand Down
40 changes: 5 additions & 35 deletions app/index.tsx
Original file line number Diff line number Diff line change
@@ -1,46 +1,16 @@
import React, { useEffect, useState } from 'react';
import { useIsFocused } from '@react-navigation/core';
import React from 'react';

import { AddArticleModal } from '../components/article-list/AddArticleModal';
import { AddArticle } from '../components/AddArticle';
import { ArticleList } from '../components/article-list/ArticleList';
import { OpenScannerFab } from '../components/scanner/OpenScannerFab';
import { useArticlesState } from '../context/articles.context';
import { useCodeState } from '../context/code.context';
import { Article } from '../types';

export default function Index() {
const { code, setCode } = useCodeState();
const [isModalVisible, setModalVisible] = useState(false);
const { articles, addArticle, editArticle } = useArticlesState();

const hideModal = () => {
setCode(undefined);
setModalVisible(false);
};
const showModal = () => setModalVisible(true);

const addNewArticle = (data: Article) => {
if (articles.find((it) => it.id === data.id)) {
editArticle(data.id, data);
} else {
addArticle(data);
}
};

useEffect(() => {
if (code) {
showModal();
}
}, [code]);
const isFocused = useIsFocused();

return (
<>
<ArticleList />
<OpenScannerFab />
<AddArticleModal
isOpen={isModalVisible}
onSave={addNewArticle}
onClose={hideModal}
/>
{isFocused && <AddArticle />}
</>
);
}
34 changes: 34 additions & 0 deletions app/shopping-cart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { FlatList, Row, Text } from 'native-base';
import React from 'react';

import { AddArticle } from '../components/AddArticle';
import { ArticleRow } from '../components/ArticleRow';
import { useArticlesState } from '../context/articles.context';

export default function ShoppingCart() {
const { shoppingCart, addToCart } = useArticlesState();

const total = shoppingCart.reduce((acc, { price = 0, quantity = 0 }) => {
return parseFloat((acc + quantity * price).toFixed(2));
}, 0);

return (
<>
<FlatList
paddingX={4}
paddingY={2}
ListFooterComponent={
<Row marginTop={4} justifyContent="flex-end">
<Text>Total: {total}</Text>
</Row>
}
data={shoppingCart}
renderItem={({ item: { id, label, price } }) => (
<ArticleRow id={id} label={label} price={price} />
)}
keyExtractor={({ id }) => id}
/>
<AddArticle onAdd={addToCart} />
</>
);
}
49 changes: 49 additions & 0 deletions components/AddArticle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import React, { FC, useEffect, useState } from 'react';

import { AddArticleModal } from './article-list/AddArticleModal';
import { OpenScannerFab } from './scanner/OpenScannerFab';
import { useArticlesState } from '../context/articles.context';
import { useCodeState } from '../context/code.context';
import { Article } from '../types';

type AddArticleProps = {
onAdd?: (id: string) => void;
};

export const AddArticle: FC<AddArticleProps> = ({ onAdd }) => {
const { code, setCode } = useCodeState();
const [isModalVisible, setModalVisible] = useState(false);
const { articles, addArticle, editArticle } = useArticlesState();

const hideModal = () => {
setCode(undefined);
setModalVisible(false);
};
const showModal = () => setModalVisible(true);

const addNewArticle = async (data: Article) => {
if (articles.find((it) => it.id === data.id)) {
await editArticle(data.id, data);
} else {
await addArticle(data);
onAdd?.(data.id);
}
};

useEffect(() => {
if (code) {
showModal();
}
}, [code]);

return (
<>
<OpenScannerFab />
<AddArticleModal
isOpen={isModalVisible}
onSave={addNewArticle}
onClose={hideModal}
/>
</>
);
};
25 changes: 25 additions & 0 deletions components/ArticleRow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { IStackProps, Row } from 'native-base';
import React, { FC } from 'react';

import { ArticleLabelCell } from './article-list/ArticleLabelCell';
import { ArticlePriceCell } from './article-list/ArticlePriceCell';

type ArticleRowProps = IStackProps & {
label: string;
price: number;
id: string;
};

export const ArticleRow: FC<ArticleRowProps> = ({
label,
price,
id,
...rest
}) => {
return (
<Row {...rest}>
<ArticleLabelCell label={label} id={id} />
<ArticlePriceCell price={price} id={id} />
</Row>
);
};
18 changes: 13 additions & 5 deletions components/article-list/AddArticleModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import { Modal } from '../../ui/Modal';
import { validationRules } from '../../utils/validation';
import { ControlledInput } from '../ControlledInput';

type AddArticleFormState = Omit<Article, 'price'> & { price?: string };

type AddArticleModalProps = {
isOpen: boolean;
onClose: () => void;
Expand All @@ -29,8 +31,8 @@ export const AddArticleModal: FC<AddArticleModalProps> = ({
const { t } = useTranslation();
const { code, setCode } = useCodeState();

const methods = useForm<Article>({
defaultValues: values || {
const methods = useForm<AddArticleFormState>({
defaultValues: { ...values, price: values?.price.toString() } || {
id: code,
label: '',
price: undefined,
Expand All @@ -45,11 +47,17 @@ export const AddArticleModal: FC<AddArticleModalProps> = ({
onClose();
};

const saveHandler = (formValues: Article) => {
const saveHandler = (formValues: AddArticleFormState) => {
const parsedValues: Article = {
...formValues,
price: parseFloat(
parseFloat(String(formValues.price).replace(',', '.')).toFixed(2)
),
};
if (code) {
onSave({ ...formValues, id: code });
onSave({ ...parsedValues, id: code });
} else {
onSave(formValues);
onSave(parsedValues);
}

closeHandler();
Expand Down
18 changes: 18 additions & 0 deletions components/article-list/ArticleLabelCell.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Flex, Text } from 'native-base';
import React, { FC } from 'react';

type ArticleLabelCellProps = {
label: string;
id: string;
};

export const ArticleLabelCell: FC<ArticleLabelCellProps> = ({ label, id }) => {
return (
<Flex direction="row" width="65%" alignItems="center">
<Text>{label}</Text>
<Text fontSize={11} color="text.400" marginLeft={2}>
{id}
</Text>
</Flex>
);
};
4 changes: 2 additions & 2 deletions components/article-list/ArticleList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { FlatList } from 'react-native';

import { ArticleRow } from './ArticleRow';
import { ArticlesHeaderName, ArticlesTableHeader } from './ArticlesTableHeader';
import { SwipeableArticleRow } from './SwipeableArticleRow';
import { useArticlesState } from '../../context/articles.context';
import { i18nKeys } from '../../i18n/keys';
import { Article } from '../../types';
Expand Down Expand Up @@ -74,7 +74,7 @@ export const ArticleList = () => {
/>
}
renderItem={({ item: { id, label, price } }) => (
<ArticleRow
<SwipeableArticleRow
handleEdit={handleEdit}
setEditModalOpen={setEditModalOpen}
isEditModalOpen={isEditModalOpen}
Expand Down
31 changes: 31 additions & 0 deletions components/article-list/ArticleListHeaderRight.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { MaterialIcons } from '@expo/vector-icons';
import { Link } from 'expo-router';
import { Icon, IconButton, Row } from 'native-base';
import React from 'react';

import { LanguageSelector } from '../LanguageSelector';

export const ArticleListHeaderRight = () => {
return (
<Row space={4}>
<Link href="shopping-cart" asChild>
<IconButton
variant="ghost"
_pressed={{
backgroundColor: 'gray.100',
}}
padding={1}
icon={
<Icon
as={MaterialIcons}
name="shopping-cart"
size={6}
color="gray.700"
/>
}
/>
</Link>
<LanguageSelector />
</Row>
);
};
62 changes: 62 additions & 0 deletions components/article-list/ArticlePriceCell.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { MaterialIcons } from '@expo/vector-icons';
import { Icon, IconButton, Row, Text } from 'native-base';
import React, { FC } from 'react';

import { useArticlesState } from '../../context/articles.context';

type ArticlePriceCellProps = {
price: number;
id: string;
};

export const ArticlePriceCell: FC<ArticlePriceCellProps> = ({ price, id }) => {
const { addToCart, reduceQuantityFromCart, shoppingCart } =
useArticlesState();

const itemInShoppingCart = shoppingCart.find((item) => item.id === id);

return (
<Row space={1} width="35%" alignItems="center" justifyContent="center">
{!!itemInShoppingCart && (
<IconButton
onPress={() => reduceQuantityFromCart(id)}
variant="ghost"
_pressed={{
backgroundColor: 'gray.100',
}}
icon={
<Icon
size="lg"
color="gray.700"
name="remove-circle-outline"
as={MaterialIcons}
/>
}
/>
)}
<Text textAlign="center">{price}</Text>
{itemInShoppingCart && itemInShoppingCart.quantity > 0 && (
<Text fontSize="xs" color="text.400">
x{itemInShoppingCart.quantity}
</Text>
)}
{!!itemInShoppingCart && (
<IconButton
variant="ghost"
onPress={() => addToCart(id)}
_pressed={{
backgroundColor: 'gray.100',
}}
icon={
<Icon
size="lg"
color="gray.700"
name="add-circle-outline"
as={MaterialIcons}
/>
}
/>
)}
</Row>
);
};
4 changes: 2 additions & 2 deletions components/article-list/ArticlesTableHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export const ArticlesTableHeader: FC<ArticlesTableHeaderProps> = ({
return (
<Row paddingX={4} paddingY={2}>
<Pressable
width="80%"
width="65%"
onPress={() => sortColumn(ArticlesHeaderName.Label)}>
<Flex direction="row" align="center">
<Text>{t(i18nKeys.app.index.article_list.column_header.label)}</Text>
Expand All @@ -44,7 +44,7 @@ export const ArticlesTableHeader: FC<ArticlesTableHeaderProps> = ({
</Flex>
</Pressable>
<Pressable
width="20%"
width="35%"
alignItems="center"
onPress={() => sortColumn(ArticlesHeaderName.Price)}>
<Flex align="center" direction="row">
Expand Down

0 comments on commit 3b0f9f3

Please sign in to comment.