Skip to content
Draft
Show file tree
Hide file tree
Changes from 55 commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
66835c2
feat((admin)/layout.tsx): began building out admin layout
ryanfurrer Aug 2, 2024
9049bfa
create admin layout
ryanfurrer Aug 8, 2024
5f3d9e5
Merge branch 'develop' into ryan/create-admin-layout
ryanfurrer Aug 8, 2024
ac4c293
feat(admin/layout.tsx): added classnames to future components and sor…
ryanfurrer Aug 8, 2024
13ec1c6
Merge branch 'ryan/create-admin-layout' of https://github.com/LetsGet…
ryanfurrer Aug 8, 2024
d4cfc88
(admin/layout.tsx): use alias import for errorboundary rather than re…
ryanfurrer Aug 12, 2024
a0eafb5
refine admin layout and begin componetizing the layout elements
ryanfurrer Aug 12, 2024
9bc34ef
refine components and pages for admin route
ryanfurrer Aug 13, 2024
906e83f
Merge branch 'develop' into ryan/create-admin-layout
ryanfurrer Aug 13, 2024
e880373
attempt to add adminheader props to admin layout so the header can be…
ryanfurrer Aug 13, 2024
9205f10
remove props from adminheader so the layout itself can be reviewed
ryanfurrer Aug 13, 2024
f50fe0e
Merge branch 'develop' into ryan/create-admin-layout
ryanfurrer Aug 13, 2024
b7685f7
Feat: added in email notification functionality
alexappleget Aug 15, 2024
621497e
fix: removed unused optional props after users in the server action
chris-nowicki Aug 15, 2024
85e48dd
Auto stash before merge of "alex/implement-email-notification-functio…
alexappleget Aug 15, 2024
0ce258f
Fix: Added in comments to fix eslint error for sendEmailNotification()
alexappleget Aug 15, 2024
4f2709f
Fix: Added participants prop to sendEmailNotification function and cr…
alexappleget Aug 19, 2024
1cb0041
Merge branch 'develop' into alex/implement-email-notification-functio…
alexappleget Aug 20, 2024
7c7f7f2
Fix: deleted test file and changed AdminHome to AdminNotifications
alexappleget Aug 20, 2024
76ee015
Fix: Fixed testing issues for the server function on the client side.
alexappleget Aug 20, 2024
7b53303
Fix: created test file for sendEmailNotification.ts file to test the …
alexappleget Aug 20, 2024
a1ee17e
Merge remote-tracking branch 'origin/develop' into alex/implement-ema…
alexappleget Aug 21, 2024
27737c3
Fix: updated id's and name's to be consistent
alexappleget Aug 21, 2024
c0932e2
Fix: Added in group emailing functionality.
alexappleget Aug 23, 2024
415bbcc
Merge branch 'alex/add-a-league-into-email-notification' into alex/im…
alexappleget Aug 23, 2024
7ffb7fd
Fix: Alphabetized imports.
alexappleget Aug 23, 2024
32f507b
Merge remote-tracking branch 'origin/develop' into alex/implement-ema…
alexappleget Aug 26, 2024
854634c
Fix: Handled PR comments: deleted unnecessary comments in code, realp…
alexappleget Aug 28, 2024
75e0bde
Fix: Changed the wording of the error.
alexappleget Aug 28, 2024
771d6aa
Fix: Deleted param prop that no longer exists.
alexappleget Aug 28, 2024
f147c19
Fix: added the code inside the try block of the function.
alexappleget Aug 29, 2024
0c216bb
Merge remote-tracking branch 'origin/develop' into alex/implement-ema…
alexappleget Sep 5, 2024
8d41257
Merge branch 'develop' into alex/implement-email-notification-functio…
alexappleget Sep 6, 2024
3864f3a
Merge remote-tracking branch 'origin/develop' into alex/implement-ema…
alexappleget Sep 17, 2024
584de00
Merge remote-tracking branch 'origin/develop' into alex/implement-ema…
alexappleget Sep 23, 2024
af8287b
Fix: Edited league id here to my own email test league with just me.
alexappleget Sep 23, 2024
8c86b6e
Fix: Changed variable and function names for more readability.
alexappleget Sep 23, 2024
1fd8fe8
Fix: Cleaned up things per Richard's comments.
alexappleget Sep 24, 2024
1903d27
Fix: Alphabetized and edited a small change.
alexappleget Sep 24, 2024
4e44876
Fix: Explained things better in my code.
alexappleget Sep 24, 2024
17e7057
Merge branch 'develop' into alex/implement-email-notification-functio…
alexappleget Sep 26, 2024
75b5b4e
Fix: Added target IDs for bcc
alexappleget Sep 26, 2024
3624dc0
Merge remote-tracking branch 'origin/develop' into alex/implement-ema…
alexappleget Sep 26, 2024
cafd345
Fix: Fixed testing.
alexappleget Sep 26, 2024
b97e185
Fix: Fixed tests
alexappleget Sep 26, 2024
97f3ae8
Fix: Hard coded bcc target id's
alexappleget Sep 30, 2024
368f41e
Merge remote-tracking branch 'origin/develop' into alex/implement-ema…
alexappleget Sep 30, 2024
0d8d397
Merge branch 'develop' into alex/implement-email-notification-functio…
shashilo Oct 7, 2024
9605af5
Merge remote-tracking branch 'origin/develop' into alex/implement-ema…
alexappleget Oct 7, 2024
6246c4f
Merge remote-tracking branch 'origin/alex/implement-email-notificatio…
alexappleget Oct 7, 2024
6a58f10
Fix: deleted target ID's
alexappleget Oct 10, 2024
8e3148a
Fix: Fixed tests
alexappleget Oct 10, 2024
9c864d5
Merge remote-tracking branch 'origin/develop' into alex/implement-ema…
alexappleget Oct 10, 2024
a0a2c1c
Improved UX for Admin Notification Page (#490)
ryanfurrer Oct 14, 2024
e85408c
Merge branch 'develop' into alex/implement-email-notification-functio…
shashilo Oct 14, 2024
b4f7874
Merge branch 'develop' into alex/implement-email-notification-functio…
ryanfurrer Oct 14, 2024
b12ea64
Merge remote-tracking branch 'origin/develop' into alex/implement-ema…
alexappleget Oct 18, 2024
3e2aadd
Merge remote-tracking branch 'origin/develop' into alex/implement-ema…
alexappleget Oct 27, 2024
247a786
Fix: Changed leagueId to email testing league.
alexappleget Oct 28, 2024
1188d47
Merge remote-tracking branch 'origin/develop' into alex/implement-ema…
alexappleget Nov 5, 2024
e55c39f
Fix: Created an api function to convert userIDs into their targetIDs …
alexappleget Nov 6, 2024
8eb3bd6
Fix: Updated the fetch league data function to now be able to fetch t…
alexappleget Nov 6, 2024
f4e006c
Fix: Updated sendEmailNotification function test.
alexappleget Nov 6, 2024
0641f59
Merge remote-tracking branch 'origin/develop' into alex/implement-ema…
alexappleget Nov 6, 2024
aa4c65d
Fix: Changed file and folder names.
alexappleget Nov 7, 2024
5119a15
Fix: fixed testing errors and changed errors to throw error to be mor…
alexappleget Nov 7, 2024
1717433
Fix: changed mock import to fix test.
alexappleget Nov 7, 2024
d207ea0
Merge remote-tracking branch 'origin/develop' into alex/implement-ema…
alexappleget Nov 12, 2024
7bf57bb
Merge remote-tracking branch 'origin/develop' into alex/implement-ema…
alexappleget Nov 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions .env.example

This file was deleted.

1 change: 1 addition & 0 deletions api/serverConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ const client = new sdk.Client()
.setKey(API_KEY);

export const users = new sdk.Users(client);
export const messaging = new sdk.Messaging(client);
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
'use server';
import { messaging } from "@/api/serverConfig";
import { sendEmailNotifications } from "./sendEmailNotification";

jest.mock('./sendEmailNotification', () => ({
sendEmailNotifications: jest.fn(),
}))

jest.mock('@/api/serverConfig', () => ({
messaging: {
createEmail: jest.fn(),
},
}));

describe('SendEmailNotification', () => {
it('should send email with provided information', async () => {
const content = 'Test';
Comment thread
choir241 marked this conversation as resolved.
Outdated
const sendEmailUsers = ['123456', '12345', '1234'];
const subject = 'This is a test';
Comment thread
choir241 marked this conversation as resolved.
Outdated

(sendEmailNotifications as jest.Mock).mockImplementation(async ({content, sendEmailUsers, subject, testBCC}) => {
await (messaging.createEmail as jest.Mock)('1234567890', subject, content, [], sendEmailUsers, [], [], []);
})

await sendEmailNotifications({content, sendEmailUsers, subject});

expect(messaging.createEmail).toHaveBeenCalledWith(
expect.any(String),
subject,
content,
[],
Comment thread
choir241 marked this conversation as resolved.
Outdated
sendEmailUsers,
[],
[],
[],
);
});
});
38 changes: 38 additions & 0 deletions app/(admin)/admin/notifications/actions/sendEmailNotification.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright (c) Gridiron Survivor.
// Licensed under the MIT License.

'use server';
import { ID } from 'appwrite';
import { messaging } from '@/api/serverConfig';

/**
* Function to send email.
* @param props - subject, content.
* @param props.content - The actual email you are wanting to send.
* @param props.groupUsers - User id's being passed in from the notification page.
* @param props.subject - The subject of the email.
*/
export const sendEmailNotifications = async ({
content,
groupUsers,
subject,
}: {
content: string;
groupUsers: string[];
subject: string;
}): Promise<void> => {
try {
await messaging.createEmail(
ID.unique(),
subject,
content,
[],
groupUsers,
[],
[],
[],
);
Comment thread
choir241 marked this conversation as resolved.
Outdated
} catch (error) {
throw new Error('Error Sending Email');
Comment thread
choir241 marked this conversation as resolved.
Outdated
}
};
62 changes: 58 additions & 4 deletions app/(admin)/admin/notifications/page.test.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,69 @@
// /Users/ryanfurrer/Developer/GitHub/gridiron-survivor/app/(admin)/admin/notifications/page.test.tsx

import { render, screen } from '@testing-library/react';
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import { getCurrentLeague } from '@/api/apiFunctions';
import { sendEmailNotifications } from './actions/sendEmailNotification';
import AdminNotifications from './page';
import React from 'react';

let contentInput: HTMLInputElement,
selectAllUsersRadioOption: HTMLElement,
selectRecipientsRadioGroup: HTMLElement,
sendEmailButton: HTMLElement,
subjectInput: HTMLInputElement;

jest.mock('@/api/apiFunctions', () => ({
getCurrentLeague: jest.fn(),
}));

jest.mock('./actions/sendEmailNotification', () => ({
sendEmailNotifications: jest.fn(),
}));

describe('Admin notifications page', () => {
it(`should render it's content`, () => {
beforeEach(async () => {
jest.clearAllMocks();

(getCurrentLeague as jest.Mock).mockResolvedValue({
participants: ['12345', '1234', '123'],
leagueName: 'Test League',
});

render(<AdminNotifications />);

contentInput = screen.getByTestId('content-text');

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update this if you take my previous suggestions into account.

selectAllUsersRadioOption = screen.getByTestId('all-users-option');
selectRecipientsRadioGroup = screen.getByTestId('radio-group-default');
sendEmailButton = screen.getByTestId('send-email');
subjectInput = screen.getByTestId('subject-text');
});

it(`should render it's content`, () => {
Comment thread
choir241 marked this conversation as resolved.
(sendEmailNotifications as jest.Mock).mockResolvedValue({});

const adminNotificationsContent = screen.getByTestId(
'admin-notifications-content',
);
expect(adminNotificationsContent).toBeInTheDocument();
});

it('should call the sendEmailNotifications function with the provided inputs', async () => {
fireEvent.click(selectAllUsersRadioOption);
fireEvent.change(subjectInput, { target: { value: 'Test Title' } });
fireEvent.change(contentInput, {
target: { value: 'Test message section.' },
});

await waitFor(() => {
expect(sendEmailButton).toBeInTheDocument();
});

fireEvent.submit(sendEmailButton);

await waitFor(() => {
expect(sendEmailNotifications as jest.Mock).toHaveBeenCalledWith({
content: 'Test message section.',
groupUsers: ['12345', '1234', '123'],
subject: 'Test Title',
Comment thread
choir241 marked this conversation as resolved.
});
});
});
});
150 changes: 147 additions & 3 deletions app/(admin)/admin/notifications/page.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,160 @@
// Copyright (c) Gridiron Survivor.
// Licensed under the MIT License.

import { JSX } from 'react';
'use client';
import { Button } from '@/components/Button/Button';
import { getCurrentLeague } from '@/api/apiFunctions';
import { Input } from '@/components/Input/Input';
import { JSX, useState } from 'react';
import { LabelText } from '@/components/LabelText/LabelText';
import {
RadioGroupDefault,
RadioGroupDefaultItem,
} from '@/components/RadioGroupDefault/RadioGroupDefault';
import { sendEmailNotifications } from './actions/sendEmailNotification';
import { Textarea } from '@/components/Textarea/Textarea';
import React, { useEffect } from 'react';

/**
* The admin home page.
* @returns The rendered AdminHome page.
*/
const AdminNotifications = (): JSX.Element => {
const [content, setContent] = useState<string>('');
const [emailSubjects, setEmailSubjects] = useState<string>('all users');
const [groupUsers, setGroupUsers] = useState<string[]>([]);
const [leagueName, setLeagueName] = useState<string>('');
const [subject, setSubject] = useState<string>('');

/**
* To grab all users from the league.
* @returns The league data.
*/
Comment thread
choir241 marked this conversation as resolved.
const getLeagueData = async (): Promise<void> => {
try {
const leagueId = '66e1cc9000160b10bf2c'; // TEST LEAGUE (DO NOT JOIN)
const leagueData = await getCurrentLeague(leagueId);
setGroupUsers(leagueData.participants);
setLeagueName(leagueData.leagueName);
} catch (error) {
console.error('Error Sending Email:', error);
Comment thread
choir241 marked this conversation as resolved.
Outdated
throw new Error('Error Sending Email');
}
};

/**
* Handle form submission
* @param event - Prevents the default reloading.
*/
const handleSubmit = async (event: React.FormEvent): Promise<void> => {
event.preventDefault();
await sendEmailNotifications({
content,
groupUsers,
subject,
});
};

/**
* Function to handle radio selection logic.
* @param value - Value of the radio buttons.
*/
const handleRadioChange = (value: string): void => {
setGroupUsers([value]);
setEmailSubjects(value);
};

useEffect(() => {
/**
* Fetches the league data.
* @returns The league data.
*/
const fetchData = async (): Promise<void> => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this need to be in a useEfect or can it just be called when the component mounts?

await getLeagueData();
};
fetchData();
}, []);

return (
<section data-testid="admin-notifications-content">
<p>{`This is where I'd put my notifation dashboard, IF I HAD ONE!`}</p>
<section
className="flex flex-col space-y-6"
data-testid="admin-notifications-content"
>
<p>
Choose the users you would like to email in{' '}
<span className="font-bold text-orange-500">{leagueName}</span>.
</p>
<RadioGroupDefault
defaultValue="all users"
onValueChange={handleRadioChange}
required
>
<div className="flex items-center space-x-2">
<RadioGroupDefaultItem
value="all users"
id="all"
data-testid="all-users-option"
/>
<LabelText htmlFor="all">All users</LabelText>
</div>
<div className="flex items-center space-x-2">
<RadioGroupDefaultItem
value="all survivors"
id="survivors"
data-testid="only-survivors-option"
/>
<LabelText htmlFor="survivors">Only the survivors</LabelText>
</div>
<div className="flex items-center space-x-2">
<RadioGroupDefaultItem
value="all losers"
id="losers"
data-testid="only-losers-option"
/>
<LabelText htmlFor="losers">Only the losers</LabelText>
</div>
</RadioGroupDefault>
<form
onSubmit={handleSubmit}
className="flex flex-col space-y-6 max-w-[80ch]"
>
<div className="flex gap-2 flex-col">
<LabelText htmlFor="subject" className="text-lg">
Subject:
</LabelText>
<Input
data-testid="subject-text"
id="subject"
name="subject"
onChange={(e) => setSubject(e.target.value)}
type="text"
/>
</div>
<div className="flex gap-2 flex-col">
<LabelText htmlFor="content" className="text-lg">
Message:
</LabelText>
<Textarea
data-testid="content-text"
id="content"
name="content"
onChange={(e) => setContent(e.target.value)}
/>
</div>
<p>
This email will be sent to{' '}
<span className="font-bold text-orange-500">
{emailSubjects.toLowerCase()}
</span>{' '}
in <span className="font-bold text-orange-500">{leagueName}</span>
</p>
<Button
className="md:max-w-fit"
data-testid="send-email"
label="Send email"
type="submit"
/>
</form>
</section>
);
};
Expand Down
25 changes: 25 additions & 0 deletions components/LabelText/LabelText.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { LabelText } from './LabelText';
import { render } from '@testing-library/react';
import React from 'react';

describe('LabelText Component', () => {
it('renders properly', () => {
const { getByTestId } = render(<LabelText>Test Label</LabelText>);
expect(getByTestId('label-text')).toBeInTheDocument();
});

it('renders the correct text', () => {
const { getByTestId } = render(<LabelText>Test Label</LabelText>);
expect(getByTestId('label-text')).toHaveTextContent('Test Label');
});

it('applies disabled styles when peer-disabled', () => {
const { getByTestId } = render(
<LabelText className="peer-disabled">Test Label</LabelText>,
);
expect(getByTestId('label-text')).toHaveClass(
'peer-disabled:cursor-not-allowed',
);
expect(getByTestId('label-text')).toHaveClass('peer-disabled:opacity-70');
});
});
28 changes: 28 additions & 0 deletions components/LabelText/LabelText.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright (c) Gridiron Survivor.
// Licensed under the MIT License.

"use client"
import { cn } from "@/utils/utils"
import { cva, type VariantProps } from "class-variance-authority"
import * as LabelPrimitive from "@radix-ui/react-label"
import * as React from "react"

const labelTextVariants = cva(
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
)

const LabelText = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
VariantProps<typeof labelTextVariants>
>(({ className, ...props }, ref) => (
<LabelPrimitive.Root
ref={ref}
className={cn(labelTextVariants(), className)}
data-testid="label-text"
{...props}
/>
))
LabelText.displayName = LabelPrimitive.Root.displayName

export { LabelText }
5 changes: 2 additions & 3 deletions components/RadioGroup/RadioGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@

'use client';

import * as React from 'react';
import * as RadioGroupPrimitive from '@radix-ui/react-radio-group';
import { Circle } from 'lucide-react';

import { cn } from '../../utils/utils';
import * as RadioGroupPrimitive from '@radix-ui/react-radio-group';
import * as React from 'react';

const RadioGroup = React.forwardRef<
React.ElementRef<typeof RadioGroupPrimitive.Root>,
Expand Down
Loading