diff --git a/README.md b/README.md
index fbc6aae71..431dafd48 100644
--- a/README.md
+++ b/README.md
@@ -22,6 +22,17 @@ Then access http://localhost:8080/
## Contributors
+## SP24
+- **Hannah Zhou** - Developer
+- **Nidhi Mylavarapu** - Developer
+- **Simon Ilincev** - Developer
+- **Zak Kent** - Dev Advisor
+- **Joanna Chen** - Designer
+- **Elizabeth Tang** - TPM
+- **Andrew Xu** - TPM
+- **Kaylin Chan** - PM
+- **Ilyssa Yan** - APM
+
### FA23
- **Nidhi Mylavarapu** - Developer
- **Simon Ilincev** - Developer
@@ -29,6 +40,7 @@ Then access http://localhost:8080/
- **Miranda Yu** - Developer
- **Elizabeth Tang** - Developer
- **Pablo Raigoza** - Developer
+- **Andrew Xu** - Developer
- **Michelle Dai** - Designer
- **Joanna Chen** - Designer
- **Jonathan Mak** - PMM
diff --git a/cypress/integration/test.spec.ts b/cypress/integration/test.spec.ts
index 83c4e3b46..a915c7257 100644
--- a/cypress/integration/test.spec.ts
+++ b/cypress/integration/test.spec.ts
@@ -17,6 +17,12 @@ before('Visit site logged in', () => {
cy.wait(5000); // ensure the page has time to load
});
+// Test to confirm that the new user walkthrough works as expected
+// Click through the initial explanation, then the 4 following steps, and finally the finishing page
+it('Click through new feature tour', () => {
+ cy.get('.introjs-nextbutton').click();
+});
+
// Delete existing semesters to ensure existing data does not mess with tests
it('Delete all existing semesters, if any exist', () => {
const semesterMenus = '[data-cyId=semesterMenu]';
@@ -237,3 +243,14 @@ it('Minimize a semester', () => {
cy.get('[data-cyId=semester-course]').should('be.visible');
cy.get('[data-cyId=semester-addCourse]').should('be.visible');
});
+
+it('Multiple plans dropdown open/close', () => {
+ // dropdown initially closed
+ cy.get('[data-cyId=multiplePlans-dropdown-content]').should('not.exist');
+ cy.get('[data-cyId=multiplePlans-dropdown-open]').click();
+ // dropdown opens
+ cy.get('[data-cyId=multiplePlans-dropdown-content]').should('be.visible');
+ cy.get('[data-cyId=multiplePlans-dropdown-close]').click();
+ // dropdown closed again
+ cy.get('[data-cyId=multiplePlans-dropdown-content]').should('not.exist');
+});
diff --git a/package.json b/package.json
index dd1bcdf91..616fa202d 100644
--- a/package.json
+++ b/package.json
@@ -100,4 +100,4 @@
"not dead"
],
"license": "AGPL-3.0"
-}
\ No newline at end of file
+}
diff --git a/scripts/migration/new-feature-migration.ts b/scripts/migration/new-feature-migration.ts
new file mode 100644
index 000000000..2c2642cbb
--- /dev/null
+++ b/scripts/migration/new-feature-migration.ts
@@ -0,0 +1,28 @@
+/* eslint-disable no-console */
+
+import { usernameCollection, onboardingDataCollection } from '../firebase-config';
+
+/**
+ * Perform migration of semester to plans with a list of semesters
+ */
+async function runOnUser(userEmail: string) {
+ await onboardingDataCollection.doc(userEmail).update({ sawNewFeature: false });
+}
+
+async function main() {
+ const userEmail = process.argv[2];
+ if (userEmail != null) {
+ await runOnUser(userEmail);
+ return;
+ }
+ const collection = await usernameCollection.get();
+ for (const { id } of collection.docs) {
+ console.group(`Running on ${id}...`);
+ // Intentionally await in a loop to have no interleaved console logs.
+ // eslint-disable-next-line no-await-in-loop
+ await runOnUser(id);
+ console.groupEnd();
+ }
+}
+
+main();
diff --git a/scripts/migration/plans-migration.ts b/scripts/migration/plans-migration.ts
new file mode 100644
index 000000000..8ed979f64
--- /dev/null
+++ b/scripts/migration/plans-migration.ts
@@ -0,0 +1,32 @@
+/* eslint-disable no-console */
+
+import { usernameCollection, semestersCollection } from '../firebase-config';
+
+/**
+ * Perform migration of semester to plans with a list of semesters
+ */
+async function runOnUser(userEmail: string) {
+ const semestersDoc = await semestersCollection.doc(userEmail).get();
+ const semesters = semestersDoc.data()?.semesters ?? [];
+ await semestersCollection.doc(userEmail).update({
+ plans: [{ name: 'Plan 1', semesters }],
+ });
+}
+
+async function main() {
+ const userEmail = process.argv[2];
+ if (userEmail != null) {
+ await runOnUser(userEmail);
+ return;
+ }
+ const collection = await usernameCollection.get();
+ for (const { id } of collection.docs) {
+ console.group(`Running on ${id}...`);
+ // Intentionally await in a loop to have no interleaved console logs.
+ // eslint-disable-next-line no-await-in-loop
+ await runOnUser(id);
+ console.groupEnd();
+ }
+}
+
+main();
diff --git a/scripts/track-users.ts b/scripts/track-users.ts
index dc956c277..999568a62 100644
--- a/scripts/track-users.ts
+++ b/scripts/track-users.ts
@@ -135,11 +135,11 @@ async function trackUsers() {
return;
}
- const semesters = getFirstPlan(doc.data());
+ const plan = getFirstPlan(doc.data());
let oldSemesterCount = 0;
let newSemesterCount = 0;
- semesters.forEach((semester: { year: number; season: string }) => {
+ plan.semesters.forEach((semester: { year: number; season: string }) => {
if (isOld(semester)) {
oldSemesterCount += 1;
} else {
@@ -152,7 +152,7 @@ async function trackUsers() {
activeUsersCount += 1;
}
- numSemestersPerUser.push(semesters.length);
+ numSemestersPerUser.push(plan.semesters.length);
oldSemesters.push(oldSemesterCount);
newSemesters.push(newSemesterCount);
});
diff --git a/src/assets/images/editplan.svg b/src/assets/images/editplan.svg
new file mode 100644
index 000000000..470eb4545
--- /dev/null
+++ b/src/assets/images/editplan.svg
@@ -0,0 +1,4 @@
+
diff --git a/src/components/Course/CourseCaution.vue b/src/components/Course/CourseCaution.vue
index 0ef5f2efa..0a978eaf6 100644
--- a/src/components/Course/CourseCaution.vue
+++ b/src/components/Course/CourseCaution.vue
@@ -98,7 +98,8 @@ const getCourseCautions = (
isPlaceholderCourse(course) &&
((!store.state.orderByNewest && semesterIndex !== course.startingSemester) ||
(store.state.orderByNewest &&
- store.state.semesters.length - semesterIndex + 1 !== course.startingSemester));
+ store.getters.getCurrentPlanSemesters.length - semesterIndex + 1 !==
+ course.startingSemester));
return {
noMatchedRequirement,
typicallyOfferedWarning,
diff --git a/src/components/Modals/EditSemester.vue b/src/components/Modals/EditSemester.vue
index 8703e37e9..e1bc36508 100644
--- a/src/components/Modals/EditSemester.vue
+++ b/src/components/Modals/EditSemester.vue
@@ -46,7 +46,7 @@ export default defineComponent({
},
computed: {
semesters(): readonly FirestoreSemester[] {
- return store.state.semesters;
+ return store.getters.getCurrentPlanSemesters;
},
},
methods: {
diff --git a/src/components/Modals/MultiplePlans/AddPlanModal.vue b/src/components/Modals/MultiplePlans/AddPlanModal.vue
new file mode 100644
index 000000000..8736b395d
--- /dev/null
+++ b/src/components/Modals/MultiplePlans/AddPlanModal.vue
@@ -0,0 +1,71 @@
+
+