Skip to content

Commit fddb241

Browse files
committed
Merge branch 'develop' into alex/626-add-google-analytics
2 parents 3edeae5 + bcc4f6b commit fddb241

File tree

15 files changed

+215
-68
lines changed

15 files changed

+215
-68
lines changed

app/api/gift-exchanges/[id]/route.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,7 @@ export async function GET(
1919
.single();
2020

2121
if (error) {
22-
console.error('supabase error', error);
23-
return NextResponse.json(
24-
{ error: 'Gift exchange not found' },
25-
{ status: 404 },
26-
);
22+
throw error;
2723
}
2824

2925
return NextResponse.json(data);

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 {
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { render, screen } from '@testing-library/react';
2+
import GiftExchangePage from './page';
3+
import { useAuthContext } from '@/context/AuthContextProvider';
4+
5+
const mockGiftExchangeData = {
6+
id: '123',
7+
name: 'Test Exchange',
8+
budget: '50',
9+
status: 'pending',
10+
};
11+
12+
const mockMembers = [
13+
{
14+
id: '1',
15+
user_id: 'test-member',
16+
member: { avatar: 'https://example.com/mock-avatar.png' },
17+
},
18+
];
19+
20+
const mockGiftSuggestions = {
21+
match: null,
22+
suggestions: [],
23+
};
24+
25+
jest.mock('@/app/api/openaiConfig/config', () => ({
26+
openai: {
27+
chat: {
28+
completions: {
29+
create: jest.fn(),
30+
},
31+
},
32+
},
33+
}));
34+
35+
jest.mock('next/navigation', () => ({
36+
useParams: () => ({ id: '123' }),
37+
}));
38+
39+
jest.mock('@/context/AuthContextProvider');
40+
41+
describe('GiftExchangePage Warning Modal', () => {
42+
beforeEach(() => {
43+
jest.resetAllMocks();
44+
45+
global.fetch = jest
46+
.fn()
47+
.mockResolvedValueOnce({
48+
json: async () => mockGiftExchangeData,
49+
})
50+
.mockResolvedValueOnce({
51+
json: async () => mockMembers,
52+
})
53+
.mockResolvedValueOnce({
54+
json: async () => mockGiftSuggestions,
55+
});
56+
});
57+
58+
it('displays the WarningModal with the join button for a logged-in user who is not a member of a pending group', async () => {
59+
(useAuthContext as jest.Mock).mockReturnValue({
60+
session: {
61+
user: {
62+
id: 'not-a-member',
63+
},
64+
},
65+
});
66+
67+
render(<GiftExchangePage />);
68+
69+
const warningModal = await screen.findByTestId('warning-modal');
70+
const joinButton = screen.getByTestId('join-button');
71+
72+
expect(warningModal).toBeInTheDocument();
73+
expect(joinButton).toBeInTheDocument();
74+
});
75+
76+
it('displays the WarningModal with a Google sign-in button for users who are not signed in and not members of a pending group', async () => {
77+
(useAuthContext as jest.Mock).mockReturnValue({ session: null });
78+
79+
render(<GiftExchangePage />);
80+
81+
const warningModal = await screen.findByTestId('warning-modal');
82+
const googleButton = screen.getByTestId('google-button');
83+
84+
expect(warningModal).toBeInTheDocument();
85+
expect(googleButton).toBeInTheDocument();
86+
});
87+
});

app/gift-exchanges/[id]/page.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ export default function GiftExchangePage() {
7979
(member: GiftExchangeMember) => member.user_id === session?.user.id,
8080
),
8181
);
82+
} else {
83+
setIsUserAMember(false);
8284
}
8385
} catch (error) {
8486
console.error('Error fetching data:', error);

app/layout.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { SnowOverlayProvider } from '@/providers/SnowOverlayProvider';
1010
import SnowOverlayWrapper from '@/components/SnowOverlayWrapper/SnowOverlayWrapper';
1111
import AuthContextProvider from '@/context/AuthContextProvider';
1212
import Script from 'next/script';
13+
import Toaster from '@/components/Toaster/Toaster';
1314

1415
const geistSans = localFont({
1516
src: './fonts/GeistVF.woff',
@@ -63,6 +64,7 @@ const RootLayout = ({
6364
{children}
6465
</SnowOverlayProvider>
6566
</AuthContextProvider>
67+
<Toaster />
6668
</body>
6769
</html>
6870
);
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 };

0 commit comments

Comments
 (0)