Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Alex/implement email notification functionality #464

Open
wants to merge 69 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 67 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
ryandotfurrer Aug 2, 2024
9049bfa
create admin layout
ryandotfurrer Aug 8, 2024
5f3d9e5
Merge branch 'develop' into ryan/create-admin-layout
ryandotfurrer Aug 8, 2024
ac4c293
feat(admin/layout.tsx): added classnames to future components and sor…
ryandotfurrer Aug 8, 2024
13ec1c6
Merge branch 'ryan/create-admin-layout' of https://github.com/LetsGet…
ryandotfurrer Aug 8, 2024
d4cfc88
(admin/layout.tsx): use alias import for errorboundary rather than re…
ryandotfurrer Aug 12, 2024
a0eafb5
refine admin layout and begin componetizing the layout elements
ryandotfurrer Aug 12, 2024
9bc34ef
refine components and pages for admin route
ryandotfurrer Aug 13, 2024
906e83f
Merge branch 'develop' into ryan/create-admin-layout
ryandotfurrer Aug 13, 2024
e880373
attempt to add adminheader props to admin layout so the header can be…
ryandotfurrer Aug 13, 2024
9205f10
remove props from adminheader so the layout itself can be reviewed
ryandotfurrer Aug 13, 2024
f50fe0e
Merge branch 'develop' into ryan/create-admin-layout
ryandotfurrer 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)
ryandotfurrer 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…
ryandotfurrer 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
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);
69 changes: 65 additions & 4 deletions app/(admin)/admin/notifications/page.test.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,76 @@
// /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 { getUserTargets, sendEmailNotifications } from './serverFunctions/serverFunctionHelper';
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('./serverFunctions/serverFunctionHelper', () => ({
sendEmailNotifications: jest.fn(),
getUserTargets: 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',
});

(getUserTargets as jest.Mock).mockResolvedValue([
'target1',
'target2',
'target3',
]);

render(<AdminNotifications />);

contentInput = screen.getByTestId('content-text');
Copy link
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`, () => {
choir241 marked this conversation as resolved.
Show resolved Hide 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: ['target1', 'target2', 'target3'],
subject: 'Test Title',
});
});
});
});
153 changes: 150 additions & 3 deletions app/(admin)/admin/notifications/page.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,163 @@
// 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 './serverFunctions/serverFunctionHelper';
import { Textarea } from '@/components/Textarea/Textarea';
import React, { useEffect } from 'react';
import { getUserTargets } from './serverFunctions/serverFunctionHelper';

/**
* 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.
*/
const getLeagueData = async (): Promise<void> => {
try {
const leagueId = '66f1a8e300102bff03ff'; // EMAIL TEST LEAGUE (DO NOT CHANGE)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do we not have this dynamically working?

const leagueData = await getCurrentLeague(leagueId);
const userTargets = await getUserTargets({
userIDs: leagueData.participants,
});
setGroupUsers(userTargets);
setLeagueName(leagueData.leagueName);
} catch (error) {
throw error;
}
};

/**
* 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
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Copyright (c) Gridiron Survivor.
// Licensed under the MIT License.

'use server';
import { messaging } from '@/api/serverConfig';
import { sendEmailNotifications } from './serverFunctionHelper';

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

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

describe('SendEmailNotification', () => {
it('should send email with provided information', async () => {
const content = 'Test';
const groupUsers = ['123456', '12345', '1234'];
const subject = 'This is a test';

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

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

expect(messaging.createEmail).toHaveBeenCalledWith(
expect.any(String),
subject,
content,
[],
'1234567890',
[],
[],
groupUsers,
);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Copyright (c) Gridiron Survivor.
// Licensed under the MIT License.

'use server';
import { ID } from 'appwrite';
import { messaging, users } 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,
[],
['672a3534000502297d88'],
[],
[],
groupUsers,
);
} catch (error) {
throw error;
}
};

/**
* Function to take the userIDs and grab their targetIDs for emailing.
* @param props - userIDs
* @param props.userIDs - All the passed in userIDs.
* @returns {Promise<void>}
*/
export async function getUserTargets({
userIDs,
}: {
userIDs: string[];
}): Promise<string[]> {
try {
const userTargets = await Promise.all(
userIDs.map(async (userID) => {
const userTarget = await users.get(userID);
return userTarget.targets.map((target) => target.$id);
}),
);
return userTargets.flat();
} catch (error) {
throw error;
}
}
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');
});
});
Loading