Skip to content

Commit

Permalink
Merge pull request #599 from pennlabs/alert-plan-integration
Browse files Browse the repository at this point in the history
Alert plan integration
  • Loading branch information
esinx authored Apr 14, 2024
2 parents 13ab45f + 20992a4 commit 177eff6
Show file tree
Hide file tree
Showing 24 changed files with 2,907 additions and 2,178 deletions.
8 changes: 4 additions & 4 deletions backend/Pipfile.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion backend/docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ version: "3"
services:
db:
image: postgres
command: postgres
environment:
- POSTGRES_DB=postgres
- POSTGRES_USER=penn-courses
Expand Down
1 change: 1 addition & 0 deletions frontend/plan/.eslintignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/**/old/*
node_modules/
next.config.js
_document.js
**/*.json
Expand Down
228 changes: 219 additions & 9 deletions frontend/plan/actions/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import fetch from "cross-fetch";
import AwesomeDebouncePromise from "awesome-debounce-promise";
import { batch } from "react-redux";
import { parsePhoneNumberFromString } from "libphonenumber-js";
import getCsrf from "../components/csrf";
import { MIN_FETCH_INTERVAL } from "../constants/sync_constants";
import { PATH_REGISTRATION_SCHEDULE_NAME } from "../constants/constants";
Expand Down Expand Up @@ -42,6 +43,14 @@ export const ADD_CART_ITEM = "ADD_CART_ITEM";
export const REMOVE_CART_ITEM = "REMOVE_CART_ITEM";
export const CHANGE_SORT_TYPE = "CHANGE_SORT_TYPE";

export const REGISTER_ALERT_ITEM = "REGISTER_ALERT_ITEM";
export const REACTIVATE_ALERT_ITEM = "REACTIVATE_ALERT_ITEM";
export const DEACTIVATE_ALERT_ITEM = "DEACTIVATE_ALERT_ITEM";
export const DELETE_ALERT_ITEM = "DELETE_ALERT_ITEM";
export const UPDATE_CONTACT_INFO = "UPDATE_CONTACT_INFO";

export const MARK_ALERTS_SYNCED = "MARK_ALERTS_SYNCED";

export const TOGGLE_CHECK = "TOGGLE_CHECK";
export const REMOVE_SCHED_ITEM = "REMOVE_SCHED_ITEM";

Expand Down Expand Up @@ -493,6 +502,35 @@ export const removeCartItem = (sectionId) => ({
sectionId,
});

export const registerAlertFrontend = (alert) => ({
type: REGISTER_ALERT_ITEM,
alert,
});

export const reactivateAlertFrontend = (sectionId) => ({
type: REACTIVATE_ALERT_ITEM,
sectionId,
});

export const deactivateAlertFrontend = (sectionId) => ({
type: DEACTIVATE_ALERT_ITEM,
sectionId,
});

export const deleteAlertFrontend = (sectionId) => ({
type: DELETE_ALERT_ITEM,
sectionId,
});

export const updateContactInfoFrontend = (contactInfo) => ({
type: UPDATE_CONTACT_INFO,
contactInfo,
});

export const markAlertsSynced = () => ({
type: MARK_ALERTS_SYNCED,
});

export const changeSortType = (sortMode) => ({
type: CHANGE_SORT_TYPE,
sortMode,
Expand Down Expand Up @@ -632,9 +670,7 @@ export const createScheduleOnBackend = (name, sections = []) => (dispatch) => {
.then(({ id }) => {
dispatch(createScheduleOnFrontend(name, id, sections));
})
.catch((error) => {
console.log(error);
});
.catch((error) => console.log(error));
};

export const deleteScheduleOnBackend = (user, scheduleName, scheduleId) => (
Expand Down Expand Up @@ -667,9 +703,7 @@ export const deleteScheduleOnBackend = (user, scheduleName, scheduleId) => (
})
);
})
.catch((error) => {
console.log(error);
});
.catch((error) => console.log(error));
};

export const findOwnPrimarySchedule = (user) => (dispatch) => {
Expand All @@ -686,9 +720,7 @@ export const findOwnPrimarySchedule = (user) => (dispatch) => {
setPrimaryScheduleIdOnFrontend(foundSched?.schedule.id)
);
})
.catch((error) => {
console.log(error);
})
.catch((error) => console.log(error))
);
};

Expand Down Expand Up @@ -716,3 +748,181 @@ export const setCurrentUserPrimarySchedule = (user, scheduleId) => (
})
.catch((error) => console.log(error));
};

export const registerAlertItem = (sectionId) => (dispatch) => {
const registrationObj = {
section: sectionId,
auto_resubscribe: true,
close_notification: false,
};
const init = {
method: "POST",
credentials: "include",
mode: "same-origin",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
"X-CSRFToken": getCsrf(),
},
body: JSON.stringify(registrationObj),
};
doAPIRequest("/alert/registrations/", init)
.then((res) => res.json())
.then((data) => {
dispatch(
registerAlertFrontend({
...registrationObj,
id: data.id,
cancelled: false,
status: "C",
})
);
});
};

export const reactivateAlertItem = (sectionId, alertId) => (dispatch) => {
const updateObj = {
resubscribe: true,
};
const init = {
method: "PUT",
credentials: "include",
mode: "same-origin",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
"X-CSRFToken": getCsrf(),
},
body: JSON.stringify(updateObj),
};
doAPIRequest(`/alert/registrations/${alertId}/`, init).then((res) => {
if (res.ok) {
dispatch(reactivateAlertFrontend(sectionId));
}
});
};

export const deactivateAlertItem = (sectionId, alertId) => (dispatch) => {
const updateObj = {
cancelled: true,
};
const init = {
method: "PUT",
credentials: "include",
mode: "same-origin",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
"X-CSRFToken": getCsrf(),
},
body: JSON.stringify(updateObj),
};
doAPIRequest(`/alert/registrations/${alertId}/`, init).then((res) => {
if (res.ok) {
dispatch(deactivateAlertFrontend(sectionId));
}
});
};

export const deleteAlertItem = (sectionId, alertId) => (dispatch) => {
const updateObj = {
deleted: true,
};
const init = {
method: "PUT",
credentials: "include",
mode: "same-origin",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
"X-CSRFToken": getCsrf(),
},
body: JSON.stringify(updateObj),
};
doAPIRequest(`/alert/registrations/${alertId}/`, init).then((res) => {
if (res.ok) {
dispatch(deleteAlertFrontend(sectionId));
}
});
};

export const fetchAlerts = () => (dispatch) => {
const init = {
method: "GET",
credentials: "include",
mode: "same-origin",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
"X-CSRFToken": getCsrf(),
},
};
doAPIRequest("/alert/registrations/", init)
.then((res) => res.json())
.then((alerts) => {
alerts.forEach((alert) => {
dispatch(
registerAlertFrontend({
id: alert.id,
section: alert.section,
cancelled: alert.cancelled,
auto_resubscribe: alert.auto_resubscribe,
close_notification: alert.close_notification,
status: alert.section_status,
})
);
});
})
.catch((error) => console.log(error));
};

export const fetchContactInfo = () => (dispatch) => {
fetch("/accounts/me/", {
method: "GET",
credentials: "include",
mode: "same-origin",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
"X-CSRFToken": getCsrf(),
},
})
.then((res) => res.json())
.then((data) => {
dispatch(
updateContactInfoFrontend({
email: data.profile.email,
phone: data.profile.phone,
})
);
})
// eslint-disable-next-line no-console
.catch((error) => console.log(error));
};

export const updateContactInfo = (contactInfo) => (dispatch) => {
const profile = {
email: contactInfo.email,
phone:
parsePhoneNumberFromString(contactInfo.phone, "US")?.number ?? "",
};
fetch("/accounts/me/", {
method: "PATCH",
credentials: "include",
mode: "same-origin",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
"X-CSRFToken": getCsrf(),
},
body: JSON.stringify({
profile,
}),
}).then((res) => {
if (!res.ok) {
throw new Error(JSON.stringify(res));
} else {
dispatch(updateContactInfoFrontend(profile));
}
});
};
8 changes: 2 additions & 6 deletions frontend/plan/components/CartSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ const CourseDetailsContainer = styled.div`
flex-grow: 0;
display: flex;
flex-direction: column;
max-width: 70%;
text-align: left;
align-items: left;
`;
Expand Down Expand Up @@ -133,14 +132,11 @@ const CourseCartItem = styled.div<{ $lastAdded: boolean; $isMobile: boolean }>`
cursor: pointer;
user-select: none;
display: ${(props) => (props.$isMobile ? "grid" : "flex")};
display: grid;
flex-direction: row;
justify-content: space-around;
padding: 0.8rem;
border-bottom: 1px solid #e5e8eb;
grid-template-columns: ${(props) =>
props.$isMobile ? "20% 50% 15% 15%" : ""};
grid-template-columns: 20% 50% 15% 15%;
* {
user-select: none;
}
Expand Down
43 changes: 43 additions & 0 deletions frontend/plan/components/alert/AlertButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import styled from "styled-components";

interface AlertButtonProps {
alerts?: {
add: () => void;
remove: () => void;
}
inAlerts: boolean;
}

const Button = styled.button`
color: gray;
padding: 0;
border: none;
background: none;
&:hover {
cursor: pointer;
color: #669afb;
}
`;

const AlertButton: React.FC<AlertButtonProps> = ({ alerts, inAlerts }) => {
return(
<Button
role="button"
onClick={(event) => {
event.stopPropagation();
if(inAlerts) {
alerts?.remove();
} else {
alerts?.add();
}
}}
>
<i
style={{ fontSize: "1rem" }}
className={inAlerts ? "fas fa-bell": "far fa-bell"}
/>
</Button>
)
}

export default AlertButton;
Loading

0 comments on commit 177eff6

Please sign in to comment.