Skip to content

Commit 177eff6

Browse files
authored
Merge pull request #599 from pennlabs/alert-plan-integration
Alert plan integration
2 parents 13ab45f + 20992a4 commit 177eff6

24 files changed

+2907
-2178
lines changed

backend/Pipfile.lock

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

backend/docker-compose.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ version: "3"
33
services:
44
db:
55
image: postgres
6-
command: postgres
76
environment:
87
- POSTGRES_DB=postgres
98
- POSTGRES_USER=penn-courses

frontend/plan/.eslintignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
/**/old/*
2+
node_modules/
23
next.config.js
34
_document.js
45
**/*.json

frontend/plan/actions/index.js

Lines changed: 219 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import fetch from "cross-fetch";
22
import AwesomeDebouncePromise from "awesome-debounce-promise";
33
import { batch } from "react-redux";
4+
import { parsePhoneNumberFromString } from "libphonenumber-js";
45
import getCsrf from "../components/csrf";
56
import { MIN_FETCH_INTERVAL } from "../constants/sync_constants";
67
import { PATH_REGISTRATION_SCHEDULE_NAME } from "../constants/constants";
@@ -42,6 +43,14 @@ export const ADD_CART_ITEM = "ADD_CART_ITEM";
4243
export const REMOVE_CART_ITEM = "REMOVE_CART_ITEM";
4344
export const CHANGE_SORT_TYPE = "CHANGE_SORT_TYPE";
4445

46+
export const REGISTER_ALERT_ITEM = "REGISTER_ALERT_ITEM";
47+
export const REACTIVATE_ALERT_ITEM = "REACTIVATE_ALERT_ITEM";
48+
export const DEACTIVATE_ALERT_ITEM = "DEACTIVATE_ALERT_ITEM";
49+
export const DELETE_ALERT_ITEM = "DELETE_ALERT_ITEM";
50+
export const UPDATE_CONTACT_INFO = "UPDATE_CONTACT_INFO";
51+
52+
export const MARK_ALERTS_SYNCED = "MARK_ALERTS_SYNCED";
53+
4554
export const TOGGLE_CHECK = "TOGGLE_CHECK";
4655
export const REMOVE_SCHED_ITEM = "REMOVE_SCHED_ITEM";
4756

@@ -493,6 +502,35 @@ export const removeCartItem = (sectionId) => ({
493502
sectionId,
494503
});
495504

505+
export const registerAlertFrontend = (alert) => ({
506+
type: REGISTER_ALERT_ITEM,
507+
alert,
508+
});
509+
510+
export const reactivateAlertFrontend = (sectionId) => ({
511+
type: REACTIVATE_ALERT_ITEM,
512+
sectionId,
513+
});
514+
515+
export const deactivateAlertFrontend = (sectionId) => ({
516+
type: DEACTIVATE_ALERT_ITEM,
517+
sectionId,
518+
});
519+
520+
export const deleteAlertFrontend = (sectionId) => ({
521+
type: DELETE_ALERT_ITEM,
522+
sectionId,
523+
});
524+
525+
export const updateContactInfoFrontend = (contactInfo) => ({
526+
type: UPDATE_CONTACT_INFO,
527+
contactInfo,
528+
});
529+
530+
export const markAlertsSynced = () => ({
531+
type: MARK_ALERTS_SYNCED,
532+
});
533+
496534
export const changeSortType = (sortMode) => ({
497535
type: CHANGE_SORT_TYPE,
498536
sortMode,
@@ -632,9 +670,7 @@ export const createScheduleOnBackend = (name, sections = []) => (dispatch) => {
632670
.then(({ id }) => {
633671
dispatch(createScheduleOnFrontend(name, id, sections));
634672
})
635-
.catch((error) => {
636-
console.log(error);
637-
});
673+
.catch((error) => console.log(error));
638674
};
639675

640676
export const deleteScheduleOnBackend = (user, scheduleName, scheduleId) => (
@@ -667,9 +703,7 @@ export const deleteScheduleOnBackend = (user, scheduleName, scheduleId) => (
667703
})
668704
);
669705
})
670-
.catch((error) => {
671-
console.log(error);
672-
});
706+
.catch((error) => console.log(error));
673707
};
674708

675709
export const findOwnPrimarySchedule = (user) => (dispatch) => {
@@ -686,9 +720,7 @@ export const findOwnPrimarySchedule = (user) => (dispatch) => {
686720
setPrimaryScheduleIdOnFrontend(foundSched?.schedule.id)
687721
);
688722
})
689-
.catch((error) => {
690-
console.log(error);
691-
})
723+
.catch((error) => console.log(error))
692724
);
693725
};
694726

@@ -716,3 +748,181 @@ export const setCurrentUserPrimarySchedule = (user, scheduleId) => (
716748
})
717749
.catch((error) => console.log(error));
718750
};
751+
752+
export const registerAlertItem = (sectionId) => (dispatch) => {
753+
const registrationObj = {
754+
section: sectionId,
755+
auto_resubscribe: true,
756+
close_notification: false,
757+
};
758+
const init = {
759+
method: "POST",
760+
credentials: "include",
761+
mode: "same-origin",
762+
headers: {
763+
Accept: "application/json",
764+
"Content-Type": "application/json",
765+
"X-CSRFToken": getCsrf(),
766+
},
767+
body: JSON.stringify(registrationObj),
768+
};
769+
doAPIRequest("/alert/registrations/", init)
770+
.then((res) => res.json())
771+
.then((data) => {
772+
dispatch(
773+
registerAlertFrontend({
774+
...registrationObj,
775+
id: data.id,
776+
cancelled: false,
777+
status: "C",
778+
})
779+
);
780+
});
781+
};
782+
783+
export const reactivateAlertItem = (sectionId, alertId) => (dispatch) => {
784+
const updateObj = {
785+
resubscribe: true,
786+
};
787+
const init = {
788+
method: "PUT",
789+
credentials: "include",
790+
mode: "same-origin",
791+
headers: {
792+
Accept: "application/json",
793+
"Content-Type": "application/json",
794+
"X-CSRFToken": getCsrf(),
795+
},
796+
body: JSON.stringify(updateObj),
797+
};
798+
doAPIRequest(`/alert/registrations/${alertId}/`, init).then((res) => {
799+
if (res.ok) {
800+
dispatch(reactivateAlertFrontend(sectionId));
801+
}
802+
});
803+
};
804+
805+
export const deactivateAlertItem = (sectionId, alertId) => (dispatch) => {
806+
const updateObj = {
807+
cancelled: true,
808+
};
809+
const init = {
810+
method: "PUT",
811+
credentials: "include",
812+
mode: "same-origin",
813+
headers: {
814+
Accept: "application/json",
815+
"Content-Type": "application/json",
816+
"X-CSRFToken": getCsrf(),
817+
},
818+
body: JSON.stringify(updateObj),
819+
};
820+
doAPIRequest(`/alert/registrations/${alertId}/`, init).then((res) => {
821+
if (res.ok) {
822+
dispatch(deactivateAlertFrontend(sectionId));
823+
}
824+
});
825+
};
826+
827+
export const deleteAlertItem = (sectionId, alertId) => (dispatch) => {
828+
const updateObj = {
829+
deleted: true,
830+
};
831+
const init = {
832+
method: "PUT",
833+
credentials: "include",
834+
mode: "same-origin",
835+
headers: {
836+
Accept: "application/json",
837+
"Content-Type": "application/json",
838+
"X-CSRFToken": getCsrf(),
839+
},
840+
body: JSON.stringify(updateObj),
841+
};
842+
doAPIRequest(`/alert/registrations/${alertId}/`, init).then((res) => {
843+
if (res.ok) {
844+
dispatch(deleteAlertFrontend(sectionId));
845+
}
846+
});
847+
};
848+
849+
export const fetchAlerts = () => (dispatch) => {
850+
const init = {
851+
method: "GET",
852+
credentials: "include",
853+
mode: "same-origin",
854+
headers: {
855+
Accept: "application/json",
856+
"Content-Type": "application/json",
857+
"X-CSRFToken": getCsrf(),
858+
},
859+
};
860+
doAPIRequest("/alert/registrations/", init)
861+
.then((res) => res.json())
862+
.then((alerts) => {
863+
alerts.forEach((alert) => {
864+
dispatch(
865+
registerAlertFrontend({
866+
id: alert.id,
867+
section: alert.section,
868+
cancelled: alert.cancelled,
869+
auto_resubscribe: alert.auto_resubscribe,
870+
close_notification: alert.close_notification,
871+
status: alert.section_status,
872+
})
873+
);
874+
});
875+
})
876+
.catch((error) => console.log(error));
877+
};
878+
879+
export const fetchContactInfo = () => (dispatch) => {
880+
fetch("/accounts/me/", {
881+
method: "GET",
882+
credentials: "include",
883+
mode: "same-origin",
884+
headers: {
885+
Accept: "application/json",
886+
"Content-Type": "application/json",
887+
"X-CSRFToken": getCsrf(),
888+
},
889+
})
890+
.then((res) => res.json())
891+
.then((data) => {
892+
dispatch(
893+
updateContactInfoFrontend({
894+
email: data.profile.email,
895+
phone: data.profile.phone,
896+
})
897+
);
898+
})
899+
// eslint-disable-next-line no-console
900+
.catch((error) => console.log(error));
901+
};
902+
903+
export const updateContactInfo = (contactInfo) => (dispatch) => {
904+
const profile = {
905+
email: contactInfo.email,
906+
phone:
907+
parsePhoneNumberFromString(contactInfo.phone, "US")?.number ?? "",
908+
};
909+
fetch("/accounts/me/", {
910+
method: "PATCH",
911+
credentials: "include",
912+
mode: "same-origin",
913+
headers: {
914+
Accept: "application/json",
915+
"Content-Type": "application/json",
916+
"X-CSRFToken": getCsrf(),
917+
},
918+
body: JSON.stringify({
919+
profile,
920+
}),
921+
}).then((res) => {
922+
if (!res.ok) {
923+
throw new Error(JSON.stringify(res));
924+
} else {
925+
dispatch(updateContactInfoFrontend(profile));
926+
}
927+
});
928+
};

frontend/plan/components/CartSection.tsx

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ const CourseDetailsContainer = styled.div`
1515
flex-grow: 0;
1616
display: flex;
1717
flex-direction: column;
18-
max-width: 70%;
1918
text-align: left;
2019
align-items: left;
2120
`;
@@ -133,14 +132,11 @@ const CourseCartItem = styled.div<{ $lastAdded: boolean; $isMobile: boolean }>`
133132
cursor: pointer;
134133
user-select: none;
135134
136-
display: ${(props) => (props.$isMobile ? "grid" : "flex")};
135+
display: grid;
137136
flex-direction: row;
138-
justify-content: space-around;
139137
padding: 0.8rem;
140138
border-bottom: 1px solid #e5e8eb;
141-
grid-template-columns: ${(props) =>
142-
props.$isMobile ? "20% 50% 15% 15%" : ""};
143-
139+
grid-template-columns: 20% 50% 15% 15%;
144140
* {
145141
user-select: none;
146142
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import styled from "styled-components";
2+
3+
interface AlertButtonProps {
4+
alerts?: {
5+
add: () => void;
6+
remove: () => void;
7+
}
8+
inAlerts: boolean;
9+
}
10+
11+
const Button = styled.button`
12+
color: gray;
13+
padding: 0;
14+
border: none;
15+
background: none;
16+
&:hover {
17+
cursor: pointer;
18+
color: #669afb;
19+
}
20+
`;
21+
22+
const AlertButton: React.FC<AlertButtonProps> = ({ alerts, inAlerts }) => {
23+
return(
24+
<Button
25+
role="button"
26+
onClick={(event) => {
27+
event.stopPropagation();
28+
if(inAlerts) {
29+
alerts?.remove();
30+
} else {
31+
alerts?.add();
32+
}
33+
}}
34+
>
35+
<i
36+
style={{ fontSize: "1rem" }}
37+
className={inAlerts ? "fas fa-bell": "far fa-bell"}
38+
/>
39+
</Button>
40+
)
41+
}
42+
43+
export default AlertButton;

0 commit comments

Comments
 (0)