Skip to content

Commit

Permalink
Coffee Chat Archive/Unarchive Button (#872)
Browse files Browse the repository at this point in the history
### Summary <!-- Required -->

<!-- Provide a general summary of your changes in the Title above -->
<!-- Itemize bug fixes, new features, and other changes -->
<!-- Feel free to break this into sub-sections, i.e. features, fixes,
etc. -->
<!-- Some examples are shown below. -->

This PR adds the functionality for Admins to be able to both Archive and
Unarchive all coffee chats to give a clean slate for every semester.

- [x] Added `archiveCoffeeChats` and `unarchiveCoffeeChats` to
`CoffeeChatDao.ts`
- [x] Added endpoints for both archive/unarchive in `api.ts` and
`loginCheckedPatch` for patch REST
- [x] Added functions for both the backend/frontend `coffeeChatAPI.ts`
and `CoffeeChatAPI.ts`
- [x] Created patch Request in the `APIWrapper.ts`
- [x] Added Archive/Unarchive buttons to `CoffeeChats.tsx` (Gives
success alert once finished!)

<!-- If the changes have associated Notion pages/Figma design(s), please
include the links here.-->
[Notion
Link](https://www.notion.so/Coffee-Chat-Add-Ability-for-Admin-to-Archive-Coffee-Chats-1890ad723ce181319b34feaebe6c782d?pvs=4)

### Test Plan <!-- Required -->


https://github.com/user-attachments/assets/36a6885f-0343-45bd-aa20-2ed93cfaaf22

<!-- Provide screenshots or point out the additional unit tests -->
  • Loading branch information
JasonMun7 authored Mar 9, 2025
1 parent baf5496 commit 870f697
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 1 deletion.
16 changes: 16 additions & 0 deletions backend/src/API/coffeeChatAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,22 @@ export const clearAllCoffeeChats = async (user: IdolMember): Promise<void> => {
await CoffeeChatDao.clearAllCoffeeChats();
};

/**
* Archives all coffee chats by setting the isArchived field to true.
* @param user - The user making the request.
* @throws PermissionError if the user does not have permissions.
*/
export const archiveCoffeeChats = async (user: IdolMember): Promise<void> => {
const canArchive = await PermissionsManager.isLeadOrAdmin(user);
if (!canArchive) {
throw new PermissionError(
`User with email ${user.email} does not have permission to archive coffee chats.`
);
}

await CoffeeChatDao.archiveCoffeeChats();
};

/**
* Gets the coffee chat bingo board
*/
Expand Down
13 changes: 12 additions & 1 deletion backend/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ import {
checkMemberMeetsCategory,
runAutoChecker,
notifyMemberCoffeeChat,
getCoffeeChatSuggestions
getCoffeeChatSuggestions,
archiveCoffeeChats
} from './API/coffeeChatAPI';
import {
allSignInForms,
Expand Down Expand Up @@ -204,6 +205,11 @@ const loginCheckedPut = (
handler: (req: Request, user: IdolMember) => Promise<Record<string, unknown>>
) => router.put(path, loginCheckedHandler(handler));

const loginCheckedPatch = (
path: string,
handler: (req: Request, user: IdolMember) => Promise<Record<string, unknown>>
) => router.patch(path, loginCheckedHandler(handler));

// Members
router.get('/member', async (req, res) => {
const type = req.query.type as string | undefined;
Expand Down Expand Up @@ -341,6 +347,11 @@ loginCheckedPut('/coffee-chat', async (req, user) => ({
coffeeChat: await updateCoffeeChat(req.body, user)
}));

loginCheckedPatch('/coffee-chat/archive', async (_, user) => {
await archiveCoffeeChats(user);
return { message: 'All coffee chats archived successfully.' };
});

loginCheckedGet('/coffee-chat-bingo-board', async () => {
const board = await getCoffeeChatBingoBoard();
return { board };
Expand Down
17 changes: 17 additions & 0 deletions backend/src/dao/CoffeeChatDao.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,23 @@ export default class CoffeeChatDao extends BaseDao<CoffeeChat, DBCoffeeChat> {
await deleteCollection(db, 'coffee-chats', 500);
}

/**
* Archives all coffee chats by setting the isArchived field to true.
*/
static async archiveCoffeeChats(): Promise<void> {
const coffeeChatsToDelete = await coffeeChatsCollection.where('isArchived', '==', true).get();
const deletePromises = coffeeChatsToDelete.docs.map((doc) => doc.ref.delete());

const coffeeChatsToArchive = await coffeeChatsCollection.where('isArchived', '==', false).get();
const batch = db.batch();

coffeeChatsToArchive.docs.forEach((doc) => {
batch.update(doc.ref, { isArchived: true });
});

await Promise.all([Promise.all(deletePromises), batch.commit()]);
}

/**
* Gets the coffee chat bingo board
*/
Expand Down
12 changes: 12 additions & 0 deletions frontend/src/API/APIWrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,18 @@ export default class APIWrapper {
.then((resOrErr) => this.responseMiddleware(resOrErr));
}

public static async patch(
url: string,
body: unknown = {},
config = {}
): Promise<APIProcessedResponse> {
const idToken = await getUserIDTokenNonNull();
return axios
.patch(url, body, { headers: { ...config, 'auth-token': idToken } })
.catch((err: AxiosError) => err)
.then((resOrErr) => this.responseMiddleware(resOrErr));
}

private static responseMiddleware(
resOrErr: AxiosResponse<unknown> | AxiosError
): APIProcessedResponse {
Expand Down
11 changes: 11 additions & 0 deletions frontend/src/API/CoffeeChatAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,17 @@ export default class CoffeeChatAPI {
await APIWrapper.delete(`${backendURL}/coffee-chat/${uuid}`);
}

public static async archiveCoffeeChats(): Promise<void> {
return APIWrapper.patch(`${backendURL}/coffee-chat/archive`).then((res) => {
if (res.data.error) {
Emitters.generalError.emit({
headerMsg: "Couldn't archive coffee chats",
contentMsg: `Error: ${res.data.error}`
});
}
});
}

public static async getCoffeeChatBingoBoard(): Promise<string[][]> {
const res = await APIWrapper.get(`${backendURL}/coffee-chat-bingo-board`).then(
(res) => res.data
Expand Down
28 changes: 28 additions & 0 deletions frontend/src/components/Admin/CoffeeChats/CoffeeChats.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import styles from './CoffeeChats.module.css';
import CoffeeChatAPI from '../../../API/CoffeeChatAPI';
import { useMembers } from '../../Common/FirestoreDataProvider';
import CoffeeChatsBingoBoard from '../../Forms/CoffeeChatsForm/CoffeeChatsBingoBoard';
import { Emitters } from '../../../utils';

const CoffeeChats: React.FC = () => {
const [bingoBoard, setBingoBoard] = useState<string[][]>([[]]);
Expand Down Expand Up @@ -62,6 +63,30 @@ const CoffeeChats: React.FC = () => {
value: member.netid
}));

const archiveAllCoffeeChats = async () => {
if (
!window.confirm(
'Are you sure you want to archive all coffee chats? This action cannot be undone.'
)
) {
return;
}

try {
await CoffeeChatAPI.archiveCoffeeChats();
Emitters.generalSuccess.emit({
headerMsg: 'Success',
contentMsg: 'All coffee chats have been archived successfully!! :)'
});
setIsLoading(true);
} catch (error) {
Emitters.generalError.emit({
headerMsg: 'Error',
contentMsg: 'Failed to archive the coffee chats'
});
}
};

return (
<div className={styles.flexContainer}>
<div>
Expand Down Expand Up @@ -124,6 +149,9 @@ const CoffeeChats: React.FC = () => {
<Button onClick={() => setSelectedMember(null)} disabled={selectedMember == null}>
Review All Coffee Chats
</Button>
<Button onClick={archiveAllCoffeeChats} disabled={selectedMember !== null}>
Archive All Coffee Chats
</Button>
<div className={styles.dropdownButton}>
<Dropdown
placeholder={DEAFAULT_MEMBER_DROPDOWN_TEXT}
Expand Down

0 comments on commit 870f697

Please sign in to comment.