Skip to content

Commit bcc4f6b

Browse files
feat #602: implement dashboard toasts (#604)
1 parent 50afb8d commit bcc4f6b

File tree

11 files changed

+124
-62
lines changed

11 files changed

+124
-62
lines changed

app/dashboard/page.tsx

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,15 @@ import GroupCard, { GroupCardSkeleton } from '@/components/GroupCard/GroupCard';
55
import Link from 'next/link';
66
import { useEffect, useState } from 'react';
77
import { GiftExchangeWithMemberCount } from '../types/giftExchange';
8+
import { useToast } from '@/hooks/use-toast';
9+
import { ToastVariants } from '@/components/Toast/Toast.enum';
810

911
export default function Dashboard() {
1012
const [giftExchanges, setGiftExchanges] = useState<
1113
GiftExchangeWithMemberCount[]
1214
>([]);
1315
const [isLoading, setIsLoading] = useState(true);
16+
const { toast } = useToast();
1417

1518
useEffect(() => {
1619
async function fetchGiftExchanges() {
@@ -28,6 +31,39 @@ export default function Dashboard() {
2831

2932
const data = await response.json();
3033
setGiftExchanges(data);
34+
35+
const today = new Date();
36+
for (const exchange of data) {
37+
const drawingDate = new Date(exchange.drawing_date);
38+
const timeDifference = drawingDate.getTime() - today.getTime();
39+
const dayDifference = Math.ceil(
40+
timeDifference / (1000 * 60 * 60 * 24),
41+
);
42+
43+
if (dayDifference > 0 && dayDifference <= 3) {
44+
toast({
45+
variant: ToastVariants.Warning,
46+
title: `Upcoming Draw - ${exchange.name}`,
47+
description: `The draw is in ${dayDifference} day${dayDifference < 2 ? '' : 's'}!`,
48+
group: exchange.gift_exchange_id,
49+
});
50+
} else if (dayDifference === 0) {
51+
toast({
52+
variant: ToastVariants.Success,
53+
title: `Draw Today - ${exchange.name}`,
54+
description: `Go to your group to initiate the gift exchange draw.`,
55+
group: exchange.gift_exchange_id,
56+
});
57+
} else if (dayDifference < 0) {
58+
toast({
59+
variant: ToastVariants.Error,
60+
title: `Draw date has passed - ${exchange.name}`,
61+
description:
62+
'Your Secret Santas are still secret! Please draw now or reschedule drawing date.',
63+
group: exchange.gift_exchange_id,
64+
});
65+
}
66+
}
3167
} catch (error) {
3268
console.error('Failed to fetch gift exchanges:', error);
3369
} finally {

app/layout.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import GlobalHeader from '@/components/GlobalHeader/GlobalHeader';
99
import { SnowOverlayProvider } from '@/providers/SnowOverlayProvider';
1010
import SnowOverlayWrapper from '@/components/SnowOverlayWrapper/SnowOverlayWrapper';
1111
import AuthContextProvider from '@/context/AuthContextProvider';
12+
import Toaster from '@/components/Toaster/Toaster';
1213

1314
const geistSans = localFont({
1415
src: './fonts/GeistVF.woff',
@@ -48,6 +49,7 @@ const RootLayout = ({
4849
{children}
4950
</SnowOverlayProvider>
5051
</AuthContextProvider>
52+
<Toaster />
5153
</body>
5254
</html>
5355
);
Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,23 @@
1-
import { ToastAction } from "./ToastAction";
1+
import { ToastAction } from './ToastAction';
22
import { render, screen, fireEvent } from '@testing-library/react';
33

44
describe('ToastAction', () => {
5-
it('renders the children content within ToastAction', () => {
6-
render(<ToastAction altText="undo">Undo</ToastAction>)
5+
it('renders the children content within ToastAction', () => {
6+
render(<ToastAction altText="undo">Undo</ToastAction>);
77

8-
const toastAction = screen.getByTestId('toastAction');
9-
expect(toastAction).toHaveTextContent('Undo');
10-
});
8+
const toastAction = screen.getByTestId('toastAction');
9+
expect(toastAction).toHaveTextContent('Undo');
10+
});
1111

12-
it('calls onClick when clicked', () => {
13-
const handleClick = jest.fn();
14-
render(<ToastAction altText="undo" onClick={handleClick}>Undo</ToastAction>)
12+
it('calls onClick when clicked', () => {
13+
const handleClick = jest.fn();
14+
render(
15+
<ToastAction altText="undo" onClick={handleClick}>
16+
Undo
17+
</ToastAction>,
18+
);
1519

16-
fireEvent.click(screen.getByText('Undo'));
17-
expect(handleClick).toHaveBeenCalled()
18-
})
19-
})
20+
fireEvent.click(screen.getByText('Undo'));
21+
expect(handleClick).toHaveBeenCalled();
22+
});
23+
});

components/ToastAction/ToastAction.tsx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
// Licensed under the MIT License.
33

44
import React from 'react';
5-
import * as ToastPrimitives from "@radix-ui/react-toast"
6-
import { cn } from "@/lib/utils"
5+
import * as ToastPrimitives from '@radix-ui/react-toast';
6+
import { cn } from '@/lib/utils';
77

88
const ToastAction = React.forwardRef<
99
React.ElementRef<typeof ToastPrimitives.Action>,
@@ -13,14 +13,14 @@ const ToastAction = React.forwardRef<
1313
data-testid="toastAction"
1414
ref={ref}
1515
className={cn(
16-
"inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium transition-colors hover:bg-secondary focus:outline-none focus:ring-1 focus:ring-ring disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive",
17-
className
16+
'inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium transition-colors hover:bg-secondary focus:outline-none focus:ring-1 focus:ring-ring disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive',
17+
className,
1818
)}
1919
{...props}
2020
/>
21-
))
22-
ToastAction.displayName = ToastPrimitives.Action.displayName
21+
));
22+
ToastAction.displayName = ToastPrimitives.Action.displayName;
2323

24-
type ToastActionElement = React.ReactElement<typeof ToastAction>
24+
type ToastActionElement = React.ReactElement<typeof ToastAction>;
2525

26-
export { ToastAction, type ToastActionElement }
26+
export { ToastAction, type ToastActionElement };

components/ToastDescription/ToastDescription.test.tsx

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,31 @@ import { ToastDescription } from './ToastDescription';
44

55
describe('ToastDescription', () => {
66
it('renders the description text', () => {
7-
const { getByText } = render(<ToastDescription>Test Description</ToastDescription>);
7+
const { getByText } = render(
8+
<ToastDescription>Test Description</ToastDescription>,
9+
);
810
expect(getByText('Test Description')).toBeInTheDocument();
911
});
1012

1113
it('applies custom classNames', () => {
12-
render(<ToastDescription className="custom-class">Test Description</ToastDescription>)
14+
render(
15+
<ToastDescription className="custom-class">
16+
Test Description
17+
</ToastDescription>,
18+
);
1319

1420
const toastDescription = screen.getByTestId('toastDescription');
15-
expect(toastDescription).toHaveClass('custom-class')
16-
})
21+
expect(toastDescription).toHaveClass('custom-class');
22+
});
1723

1824
it('passes custom data attributes', () => {
19-
render(<ToastDescription data-custom-attribute="testValue"></ToastDescription>);
25+
render(
26+
<ToastDescription data-custom-attribute="testValue"></ToastDescription>,
27+
);
2028
const toastDescription = screen.getByTestId('toastDescription');
21-
expect(toastDescription).toHaveAttribute('data-custom-attribute', 'testValue')
22-
})
23-
});
29+
expect(toastDescription).toHaveAttribute(
30+
'data-custom-attribute',
31+
'testValue',
32+
);
33+
});
34+
});

components/ToastDescription/ToastDescription.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,20 @@
22
// Licensed under the MIT License.
33

44
import React from 'react';
5-
import * as ToastPrimitives from "@radix-ui/react-toast"
6-
import { cn } from "@/lib/utils"
5+
import * as ToastPrimitives from '@radix-ui/react-toast';
6+
import { cn } from '@/lib/utils';
77

88
const ToastDescription = React.forwardRef<
99
React.ElementRef<typeof ToastPrimitives.Description>,
1010
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description>
1111
>(({ className, ...props }, ref) => (
1212
<ToastPrimitives.Description
1313
ref={ref}
14-
className={cn("text-sm opacity-90", className)}
14+
className={cn('text-sm opacity-90', className)}
1515
data-testid="toastDescription"
1616
{...props}
1717
/>
18-
))
19-
ToastDescription.displayName = ToastPrimitives.Description.displayName
18+
));
19+
ToastDescription.displayName = ToastPrimitives.Description.displayName;
2020

21-
export { ToastDescription }
21+
export { ToastDescription };
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
// Copyright (c) Gridiron Survivor.
22
// Licensed under the MIT License.
33

4-
"use client"
4+
'use client';
55

6-
import * as ToastPrimitives from "@radix-ui/react-toast"
6+
import * as ToastPrimitives from '@radix-ui/react-toast';
77

8-
const ToastProvider = ToastPrimitives.Provider
8+
const ToastProvider = ToastPrimitives.Provider;
99

10-
export { ToastProvider }
10+
export { ToastProvider };

components/ToastTitle/ToastTitle.test.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@ describe('ToastTitle', () => {
1212
render(<ToastTitle className="custom-class"></ToastTitle>);
1313

1414
const toastTitle = screen.getByTestId('toastTitle');
15-
expect(toastTitle).toHaveClass('custom-class')
16-
})
15+
expect(toastTitle).toHaveClass('custom-class');
16+
});
1717

1818
it('passes custom data attributes', () => {
1919
render(<ToastTitle data-custom-attribute="testValue"></ToastTitle>);
2020
const toastTitle = screen.getByTestId('toastTitle');
21-
expect(toastTitle).toHaveAttribute('data-custom-attribute', 'testValue')
22-
})
23-
});
21+
expect(toastTitle).toHaveAttribute('data-custom-attribute', 'testValue');
22+
});
23+
});
Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
// Copyright (c) Gridiron Survivor.
22
// Licensed under the MIT License.
33

4-
import React from "react"
5-
import * as ToastPrimitives from "@radix-ui/react-toast"
6-
import { cn } from "@/lib/utils"
4+
import React from 'react';
5+
import * as ToastPrimitives from '@radix-ui/react-toast';
6+
import { cn } from '@/lib/utils';
77

88
const ToastTitle = React.forwardRef<
99
React.ElementRef<typeof ToastPrimitives.Title>,
@@ -12,10 +12,10 @@ const ToastTitle = React.forwardRef<
1212
<ToastPrimitives.Title
1313
data-testid="toastTitle"
1414
ref={ref}
15-
className={cn("text-sm font-semibold [&+div]:text-xs", className)}
15+
className={cn('text-sm font-semibold [&+div]:text-xs', className)}
1616
{...props}
1717
/>
18-
))
19-
ToastTitle.displayName = ToastPrimitives.Title.displayName
18+
));
19+
ToastTitle.displayName = ToastPrimitives.Title.displayName;
2020

21-
export { ToastTitle }
21+
export { ToastTitle };

hooks/use-toast.test.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@ import type { Action } from './use-toast';
1010

1111
import React from 'react';
1212

13-
// methods to test: addToRemoveQueue, dispatch, toast, useToast
14-
1513
const MOCK_TOAST_1 = { id: '1', title: 'Toast 1', open: true };
1614
const MOCK_TOAST_2 = { id: '2', title: 'Toast 2', open: true };
1715
const TOAST_LIMIT = 1;

0 commit comments

Comments
 (0)