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 @@ + + + + + diff --git a/src/components/Modals/MultiplePlans/CopyPlanModal.vue b/src/components/Modals/MultiplePlans/CopyPlanModal.vue new file mode 100644 index 000000000..0169e1d2f --- /dev/null +++ b/src/components/Modals/MultiplePlans/CopyPlanModal.vue @@ -0,0 +1,124 @@ + + + + + diff --git a/src/components/Modals/MultiplePlans/EditPlanModal.vue b/src/components/Modals/MultiplePlans/EditPlanModal.vue new file mode 100644 index 000000000..47925c6df --- /dev/null +++ b/src/components/Modals/MultiplePlans/EditPlanModal.vue @@ -0,0 +1,119 @@ + + + + + diff --git a/src/components/Modals/MultiplePlans/NamePlanModal.vue b/src/components/Modals/MultiplePlans/NamePlanModal.vue new file mode 100644 index 000000000..c85d0e7ca --- /dev/null +++ b/src/components/Modals/MultiplePlans/NamePlanModal.vue @@ -0,0 +1,99 @@ + + + + + diff --git a/src/components/Modals/MultiplePlans/PlanModalDropdown.scss b/src/components/Modals/MultiplePlans/PlanModalDropdown.scss new file mode 100644 index 000000000..73fff3b5a --- /dev/null +++ b/src/components/Modals/MultiplePlans/PlanModalDropdown.scss @@ -0,0 +1,74 @@ +@import '@/assets/scss/_variables.scss'; + +.multiplePlansModal-dropdown { + &-placeholder { + width: 100%; + height: 100%; + font-size: 14px; + font-weight: bold; + display: flex; + align-items: center; + + color: $darkPlaceholderGray; + + background: transparent; + + &.wrapper { + position: relative; + height: 2rem; + border-radius: 2px; + border: 1px solid $darkPlaceholderGray; + cursor: pointer; + } + + &.plan { + width: 100%; + padding-left: 10px; + } + + &.down-arrow { + position: relative; + right: 7%; + width: 6.24px; + height: 6.24px; + border-left: 6.24px solid transparent; + border-right: 6.24px solid transparent; + border-top: 6.24px solid $darkPlaceholderGray; + } + + &.up-arrow { + position: relative; + right: 7%; + width: 6.24px; + height: 6.24px; + border-left: 6.24px solid transparent; + border-right: 6.24px solid transparent; + border-top: 6.24px solid $darkPlaceholderGray; + transform: rotate(180deg); + } + } + + &-content { + width: 100%; + z-index: 100; + max-height: 8rem; + border-radius: 2px; + &.item { + border: 1px solid $darkPlaceholderGray; + z-index: 150; + background-color: white; + height: 2rem; + font-size: 14px; + line-height: 16px; + display: flex; + align-items: center; + color: $darkPlaceholderGray; + padding-left: 10px; + } + &.item:hover { + background-color: $searchBoxHoverGray; + opacity: 1; + cursor: pointer; + } + } +} diff --git a/src/components/Modals/MultiplePlans/TextInputModal.vue b/src/components/Modals/MultiplePlans/TextInputModal.vue new file mode 100644 index 000000000..90f46a237 --- /dev/null +++ b/src/components/Modals/MultiplePlans/TextInputModal.vue @@ -0,0 +1,89 @@ + + + + + diff --git a/src/components/Modals/NewSemesterModal.vue b/src/components/Modals/NewSemesterModal.vue index cd956e6f8..f493e5aea 100644 --- a/src/components/Modals/NewSemesterModal.vue +++ b/src/components/Modals/NewSemesterModal.vue @@ -37,7 +37,7 @@ export default defineComponent({ }, computed: { semesters(): readonly FirestoreSemester[] { - return store.state.semesters; + return store.getters.getCurrentPlanSemesters; }, }, methods: { diff --git a/src/components/Modals/Onboarding/Onboarding.vue b/src/components/Modals/Onboarding/Onboarding.vue index aea70a69d..8ac3ed32d 100644 --- a/src/components/Modals/Onboarding/Onboarding.vue +++ b/src/components/Modals/Onboarding/Onboarding.vue @@ -124,6 +124,7 @@ import { import timeline1Text from '@/assets/images/timeline1text.svg'; import timeline2Text from '@/assets/images/timeline2text.svg'; import timeline3Text from '@/assets/images/timeline3text.svg'; +import store from '@/store'; const timelineTexts = [timeline1Text, timeline2Text, timeline3Text]; @@ -251,7 +252,7 @@ export default defineComponent({ this.clearTransferCreditIfGraduate(); setAppOnboardingData(this.name, revised); // indicates first time user onboarding - if (!this.isEditingProfile) populateSemesters(revised); + if (!this.isEditingProfile) populateSemesters(store.state.currentPlan, revised); this.$emit('onboard'); }, goBack() { diff --git a/src/components/Modals/TeleportModal.vue b/src/components/Modals/TeleportModal.vue index 0d64dff06..b5a0ff04a 100644 --- a/src/components/Modals/TeleportModal.vue +++ b/src/components/Modals/TeleportModal.vue @@ -23,7 +23,12 @@