Skip to content

Commit

Permalink
feat: [P4PU-886] implement notifications and enhance cart functionali…
Browse files Browse the repository at this point in the history
…ty with item count badge (#250)
  • Loading branch information
stratoivandiluccio authored Feb 25, 2025
1 parent 7707469 commit 2e829ea
Show file tree
Hide file tree
Showing 11 changed files with 98 additions and 48 deletions.
18 changes: 15 additions & 3 deletions src/components/Cart/CartDrawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import Button from '@mui/material/Button';
import CloseIcon from '@mui/icons-material/Close';
import { Alert, Divider, useTheme } from '@mui/material';
import { Alert, Divider, useTheme, Link } from '@mui/material';
import { toggleCartDrawer } from 'store/CartStore';
import { ButtonNaked } from '@pagopa/mui-italia/dist/components/ButtonNaked';
import { useTranslation } from 'react-i18next';
import { Trans, useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import { ArcRoutes } from 'routes/routes';
import { cartDrawerStyles } from './CartDrawer.styles';
Expand Down Expand Up @@ -78,7 +78,19 @@ export const CartDrawer = () => {
{/* Cart Content */}
{cart.items.length > 0 && (
<Stack sx={styles.items}>
<Alert severity="info">{t('app.cart.items.alert')}</Alert>
<Alert severity="info">
<Trans
i18nKey="app.cart.items.alert"
components={{
link1: (
<Link
target="_blank"
href="https://assistenza.ioapp.it/hc/it/articles/31008000237585-L-importo-%C3%A8-diverso-da-quello-previsto"
/>
)
}}
/>
</Alert>
<Stack mt={2} divider={<Divider orientation="horizontal" flexItem />}>
{cart.items.map((item) => (
<CartItem
Expand Down
11 changes: 10 additions & 1 deletion src/components/Header/SubHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { toggleCartDrawer } from 'store/CartStore';
import { useTranslation } from 'react-i18next';
import utils from 'utils'; // Adjust the import path as necessary
import { useStore } from 'store/GlobalStore';
import { Badge } from '@mui/material';

export const SubHeader = () => {
const { spacing } = useTheme();
Expand All @@ -28,7 +29,15 @@ export const SubHeader = () => {
<Typography variant="inherit" aria-hidden="true" id="header-cart-amount">
{utils.converters.toEuro(state.cart.amount)}
</Typography>
<ShoppingCartIcon fontSize="small" aria-hidden="true" />
<Badge
badgeContent={state.cart.items.length}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'right'
}}
color="primary">
<ShoppingCartIcon fontSize="small" aria-hidden="true" />
</Badge>
</Button>
);
};
Expand Down
11 changes: 10 additions & 1 deletion src/components/Layout.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import { Container, Grid } from '@mui/material';
import { Alert, Container, Grid, Snackbar } from '@mui/material';
import { grey } from '@mui/material/colors';
import { Footer } from './Footer';
import { Sidebar } from './Sidebar/Sidebar';
Expand Down Expand Up @@ -41,6 +41,15 @@ export function Layout() {

return (
<>
<Snackbar
autoHideDuration={6000}
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
onClose={utils.notify.dismiss}
open={utils.notify.status.isVisible.value}>
<Alert severity={utils.notify.status.payload.value?.severity} variant="outlined">
{utils.notify.status.payload.value?.text}
</Alert>
</Snackbar>
<ModalSystem />
<Container
maxWidth={false}
Expand Down
9 changes: 6 additions & 3 deletions src/components/PaymentNotice/Detail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
} from 'models/PaymentNotice';
import { addItem, deleteItem, toggleCartDrawer, isItemInCart } from 'store/CartStore';
import { useStore } from 'store/GlobalStore';
import notify from 'utils/notify';
/**
* This component is considered private and should not be used directly.
* Instead, use `PaymentNotice.Card` for rendering the payment notice card.
Expand All @@ -42,8 +43,7 @@ export const _Detail = ({ paymentNotice }: { paymentNotice: PaymentNoticeDetails
// this is not a problem, because we not manage multiple payment options in any case
const paymentNoticeSigleOption = paymentNotice.paymentOptions as PaymentOptionsDetailsType;
const { iuv, amountValue: amount, nav, description } = paymentNoticeSigleOption;
// add a notification if the cart is full
if (cart.items.length >= 5) return;
if (cart.items.length >= 5) return notify.emit(t('app.cart.items.full'), 'error');
if (isItemInCart(iuv)) return deleteItem(iuv);
addItem({
amount,
Expand Down Expand Up @@ -293,7 +293,7 @@ export const _Detail = ({ paymentNotice }: { paymentNotice: PaymentNoticeDetails
</Typography>
</Grid>
</Grid>
<Grid container>
<Grid container justifyContent={'center'}>
<Grid item xs={12}>
<Button
id="payment-notice-add-button"
Expand All @@ -320,6 +320,9 @@ export const _Detail = ({ paymentNotice }: { paymentNotice: PaymentNoticeDetails
</Typography>
</Button>
</Grid>
<Typography variant="body1" fontSize={16} mt={2}>
{t('app.cart.items.info')}
</Typography>
</Grid>
</Stack>
</CardActions>
Expand Down
34 changes: 6 additions & 28 deletions src/components/Transactions/TransactionDetail.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,23 @@
import { Download } from '@mui/icons-material';
import {
Alert,
Box,
Button,
Divider,
Grid,
Snackbar,
Stack,
Typography,
useTheme
} from '@mui/material';
import { Alert, Box, Button, Divider, Grid, Stack, Typography, useTheme } from '@mui/material';
import { CopyToClipboardButton } from '@pagopa/mui-italia';
import BRAND from './Brand';
import { BRANDS } from '../../models/NoticeDetail';
import paypal from '../../assets/paypal.png';
import { type NoticeDetail as NoticeDetailType } from '../../models/NoticeDetail';
import React, { useState } from 'react';
import React from 'react';
import { Trans, useTranslation } from 'react-i18next';
import { downloadReceiptPDF } from 'utils/files';
import utils from 'utils';

export default function TransactionDetail({ noticeData }: { noticeData: NoticeDetailType }) {
const theme = useTheme();
const { t } = useTranslation();
const [toastOpen, setToastOpen] = useState(false);

const getReceipt = async (transactionId: string) => {
try {
await downloadReceiptPDF(transactionId);
} catch (err) {
setToastOpen(true);
await utils.files.downloadReceiptPDF(transactionId);
} catch {
utils.notify.emit(t('app.transactionDetail.downloadReceiptError'));
}
};

Expand Down Expand Up @@ -74,17 +63,6 @@ export default function TransactionDetail({ noticeData }: { noticeData: NoticeDe
</Alert>
)}
<Stack spacing={2} mt={3} width={'100%'}>
<Snackbar
autoHideDuration={6000}
onClose={() => {
setToastOpen(false);
}}
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
open={toastOpen}>
<Alert severity="error" variant="outlined">
{t('app.transactionDetail.downloadReceiptError')}
</Alert>
</Snackbar>
<Grid container>
{/* LEFT COLUMN: PAID NOTICE INFO */}
<Grid container item xs={12} md={7}>
Expand Down
11 changes: 5 additions & 6 deletions src/components/Transactions/transactionDetail.test.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import * as React from 'react';
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import { fireEvent, render, screen } from '@testing-library/react';
import { dummyTransactionsData } from 'stories/utils/mocks';
import { TransactionDetails } from './';
import '@testing-library/jest-dom';
import { downloadReceiptPDF } from 'utils/files';
import utils from 'utils';
import { i18nTestSetup } from '__tests__/i18nTestSetup';

i18nTestSetup({});

vi.mock('utils/files');

const mockUseReceiptData = vi.mocked(downloadReceiptPDF);
const mockUseReceiptData = vi.mocked(utils.files.downloadReceiptPDF);

describe('TransactionDetails component', () => {
it('should render as expected', () => {
Expand All @@ -22,11 +22,10 @@ describe('TransactionDetails component', () => {
mockUseReceiptData.mockImplementation(() => {
throw new Error();
});
const notifySpy = vi.spyOn(utils.notify, 'emit');
render(<TransactionDetails noticeData={dummyTransactionsData.transactionData} />);
fireEvent.click(screen.getByTestId('receipt-download-btn'));
await waitFor(() =>
expect(screen.queryByText('app.transactionDetail.downloadReceiptError')).toBeInTheDocument()
);
expect(notifySpy).toHaveBeenCalledWith('app.transactionDetail.downloadReceiptError');
});

it('should truncate transactionId if longer than 20 ', () => {
Expand Down
6 changes: 4 additions & 2 deletions src/translations/it/translations.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,11 @@
"button": "Torna agli avvisi"
},
"items": {
"alert": "L’importo è aggiornato in automatico, così paghi sempre quanto dovuto ed eviti more o altri interessi.",
"alert": "L’importo si aggiorna in automatico, così paghi sempre quanto dovuto ed eviti more o altri interessi. <link1>Vuoi saperne di più?</link1>",
"back": "Torna agli avvisi",
"pay": "Vai al pagamento"
"pay": "Vai al pagamento",
"full": "Non puoi aggiungere più di 5 avvisi al carrello",
"info": "Puoi pagare fino a 5 avvisi insieme"
}
},
"assistance": {
Expand Down
5 changes: 2 additions & 3 deletions src/utils/files.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import '@testing-library/jest-dom';
import { downloadReceiptPDF } from './files';
import utils from 'utils';

describe('downloadReceiptPDF function', () => {
Expand All @@ -11,11 +10,11 @@ describe('downloadReceiptPDF function', () => {

URL.createObjectURL = vitest.fn();
URL.revokeObjectURL = vitest.fn();
expect(downloadReceiptPDF('1')).resolves.toBeUndefined();
expect(utils.files.downloadReceiptPDF('1')).resolves.toBeUndefined();
});

it('should trhow an Error when something goes wrong', () => {
vi.spyOn(utils.loaders, 'getReceiptPDF').mockResolvedValue(null);
expect(downloadReceiptPDF('1')).rejects.toThrowError('Error getting the PDF');
expect(utils.files.downloadReceiptPDF('1')).rejects.toThrowError('Error getting the PDF');
});
});
6 changes: 5 additions & 1 deletion src/utils/files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import utils from 'utils';
/**
* Downloads pdf for a transaction
*/
export const downloadReceiptPDF = async (transactionId: string) => {
const downloadReceiptPDF = async (transactionId: string) => {
const response = await utils.loaders.getReceiptPDF(transactionId);
if (!response) {
throw new Error('Error getting the PDF');
Expand All @@ -28,3 +28,7 @@ export const downloadReceiptPDF = async (transactionId: string) => {
document.body.removeChild(a);
URL.revokeObjectURL(url);
};

export default {
downloadReceiptPDF
};
4 changes: 4 additions & 0 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ import converters from './converters';
import { datetools } from './datetools';
import loaders from './loaders';
import modal from './modal';
import notify from './notify';
import sidemenu from './sidemenu';
import storage from './storage';
import style from './style';
import files from './files';

export default {
apiClient: new Api({ baseURL: config.baseURL, timeout: config.apiTimeout }),
Expand All @@ -21,6 +23,8 @@ export default {
datetools,
loaders,
modal,
notify,
files,
sidemenu,
storage,
style,
Expand Down
31 changes: 31 additions & 0 deletions src/utils/notify.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { signal } from '@preact/signals-react';
import { AlertProps } from '@mui/material';

/** emits a notification */
const emit = (text: string, severity: AlertProps['severity'] = 'error') => {
isVisible.value = true;
payload.value.text = text;
payload.value.severity = severity;
};

/** dismiss a notification */
const dismiss = () => {
isVisible.value = false;
};

interface notificationPayload {
text?: string;
severity?: AlertProps['severity'];
}

const isVisible = signal<boolean>(false);
const payload = signal<notificationPayload>({});

export default {
emit,
dismiss,
status: {
isVisible,
payload
}
};

0 comments on commit 2e829ea

Please sign in to comment.