diff --git a/.gitignore b/.gitignore index 44c040641..3525c32fe 100644 --- a/.gitignore +++ b/.gitignore @@ -41,6 +41,7 @@ Thumbs.db # mongodb data /mongodb-data/* !/mongodb-data/.gitkeep +/mongodb-data2/* # environment variables diff --git a/apps/api/scripts/seed-database.js b/apps/api/scripts/seed-database.js index c33f99a5f..8e59efffa 100644 --- a/apps/api/scripts/seed-database.js +++ b/apps/api/scripts/seed-database.js @@ -11,6 +11,7 @@ const { tap, toArray, delay, + mergeMap, } = require('rxjs/operators') const moment = require('moment') @@ -24,6 +25,19 @@ const { RoleMapping, } = app.models +function pickRandomArrayElement(array) { + return array[Math.floor(Math.random() * array.length)] +} +function pickRandomArrayElements(array, maxCount) { + const count = maxCount + ? maxCount + : randomNumberInRange(1, Math.min(array.length)) + return _.sampleSize(array, count) +} +function randomNumberInRange(min, max) { + return Math.floor(Math.random() * (max - min + 1)) + min +} + const personsRaw = require('./random-names.json') const persons = [] personsRaw.forEach((person) => { @@ -37,6 +51,403 @@ personsRaw.forEach((person) => { } }) +const MENTORING_GOALS = { + buildingAProfessionalNetwork: 'Building a professional network', + jobSearchAndApplicationProcess: 'Job search and application process', + entrepreneurshipAndFreelancing: 'Entrepreneurship and freelancing', + tutoringInAParticularSkillTool: 'Tutoring in a particular skill / tool', + careerOrientatioPlanning: 'Career orientation & planning', + preparationForACertificationInterview: + 'Preparation for a certification / interview', +} +const mentoringGoalsKeys = Object.keys(MENTORING_GOALS) + +const DESIRED_ROLES = { + marketingSocialMediaAndSales: 'Marketing, social media and sales', + productAndProjectManagement: 'Product and project management', + requirementsAnalysisAndResearch: 'Requirements analysis and research', + softwareDevelopment: 'Software development', + uxAndUiDesign: 'UX and UI design', + testingAndQualityAssurance: 'Testing and Quality Assurance', + // other: 'Other', // specifically commented out as there are no + // mentoring topics in the group 'other'. It causes issues when seeding +} +const desiredRolesKeys = Object.keys(DESIRED_ROLES) + +const FIELDS_OF_EXPERTISE = { + humanResourcesAndRecruiting: 'Human resources and recruiting', + ...DESIRED_ROLES, +} +const fieldsOfExpertiseKeys = Object.keys(FIELDS_OF_EXPERTISE) + +const MENTORING_TOPICS = [ + { + id: 'Application process and portfolio', + label: 'Application process and portfolio', + group: 'overarchingTopics', + }, + { id: 'Communication', label: 'Communication', group: 'overarchingTopics' }, + { + id: 'Cross-cultural teams', + label: 'Cross-cultural teams', + group: 'overarchingTopics', + }, + { + id: 'Cross-functional work', + label: 'Cross-functional work', + group: 'overarchingTopics', + }, + { id: 'Facilitation', label: 'Facilitation', group: 'overarchingTopics' }, + { + id: 'Giving / receiving feedback', + label: 'Giving / receiving feedback', + group: 'overarchingTopics', + }, + { + id: 'Self-organisation', + label: 'Self-organisation', + group: 'overarchingTopics', + }, + { + id: 'Social network profile tuning', + label: 'Social network profile tuning', + group: 'overarchingTopics', + }, + { + id: 'Storytelling and presentation', + label: 'Storytelling and presentation', + group: 'overarchingTopics', + }, + { + id: 'Team leadership', + label: 'Team leadership', + group: 'overarchingTopics', + }, + { + id: 'Time management', + label: 'Time management', + group: 'overarchingTopics', + }, + { + id: 'Marketing and social media', + label: 'Marketing and social media', + group: 'marketingSocialMediaAndSales', + }, + { id: 'Sales', label: 'Sales', group: 'marketingSocialMediaAndSales' }, + { + id: 'Data analysis', + label: 'Data analysis', + group: 'requirementsAnalysisAndResearch', + }, + { + id: 'Design research', + label: 'Design research', + group: 'requirementsAnalysisAndResearch', + }, + { + id: 'Design Thinking', + label: 'Design Thinking', + group: 'requirementsAnalysisAndResearch', + }, + { + id: 'Domain driven design', + label: 'Domain driven design', + group: 'requirementsAnalysisAndResearch', + }, + { + id: 'Mapping customer experience', + label: 'Mapping customer experience', + group: 'requirementsAnalysisAndResearch', + }, + { + id: 'Prioritisation metrics', + label: 'Prioritisation metrics', + group: 'requirementsAnalysisAndResearch', + }, + { + id: 'Process modelling', + label: 'Process modelling', + group: 'requirementsAnalysisAndResearch', + }, + { + id: 'Software Development Life Cycle', + label: 'Software Development Life Cycle', + group: 'requirementsAnalysisAndResearch', + }, + { + id: 'Technical writing', + label: 'Technical writing', + group: 'requirementsAnalysisAndResearch', + }, + { + id: 'User story mapping', + label: 'User story mapping', + group: 'requirementsAnalysisAndResearch', + }, + { + id: 'Qualitative User Research', + label: 'Qualitative User Research', + group: 'requirementsAnalysisAndResearch', + }, + { + id: 'Quantitative User Research', + label: 'Quantitative User Research', + group: 'requirementsAnalysisAndResearch', + }, + { id: 'Animation', label: 'Animation', group: 'uxAndUiDesign' }, + { id: 'Branding', label: 'Branding', group: 'uxAndUiDesign' }, + { + id: 'General user experience', + label: 'General user experience', + group: 'uxAndUiDesign', + }, + { id: 'Graphic Design', label: 'Graphic Design', group: 'uxAndUiDesign' }, + { + id: 'Information architecture', + label: 'Information architecture', + group: 'uxAndUiDesign', + }, + { + id: 'Interaction design', + label: 'Interaction design', + group: 'uxAndUiDesign', + }, + { + id: 'Prototyping and wireframing', + label: 'Prototyping and wireframing', + group: 'uxAndUiDesign', + }, + { + id: 'Responsive design', + label: 'Responsive design', + group: 'uxAndUiDesign', + }, + { + id: 'Typography / color theory', + label: 'Typography / color theory', + group: 'uxAndUiDesign', + }, + { + id: 'Visual communication', + label: 'Visual communication', + group: 'uxAndUiDesign', + }, + { + id: 'Automated software testing', + label: 'Automated software testing', + group: 'testingAndQualityAssurance', + }, + { + id: 'Behavior-driven development', + label: 'Behavior-driven development', + group: 'testingAndQualityAssurance', + }, + { + id: 'Cross-browser testing', + label: 'Cross-browser testing', + group: 'testingAndQualityAssurance', + }, + { + id: 'Functional Testing', + label: 'Functional Testing', + group: 'testingAndQualityAssurance', + }, + { + id: 'Software testing methodologies', + label: 'Software testing methodologies', + group: 'testingAndQualityAssurance', + }, + { + id: 'Test-driven development', + label: 'Test-driven development', + group: 'testingAndQualityAssurance', + }, + { + id: 'Test planning', + label: 'Test planning', + group: 'testingAndQualityAssurance', + }, + { + id: 'Test management', + label: 'Test management', + group: 'testingAndQualityAssurance', + }, + { + id: 'Usability testing', + label: 'Usability testing', + group: 'testingAndQualityAssurance', + }, + { + id: 'Agile frameworks', + label: 'Agile frameworks', + group: 'productAndProjectManagement', + }, + { + id: 'Budget calculation', + label: 'Budget calculation', + group: 'productAndProjectManagement', + }, + { + id: 'Business development', + label: 'Business development', + group: 'productAndProjectManagement', + }, + { + id: 'Change management', + label: 'Change management', + group: 'productAndProjectManagement', + }, + { + id: 'Enterprise architecture management', + label: 'Enterprise architecture management', + group: 'productAndProjectManagement', + }, + { + id: 'Market research', + label: 'Market research', + group: 'productAndProjectManagement', + }, + { + id: 'Planning and roadmaps', + label: 'Planning and roadmaps', + group: 'productAndProjectManagement', + }, + { + id: 'Prioritisation metrics', + label: 'Prioritisation metrics', + group: 'productAndProjectManagement', + }, + { + id: 'Product Backlog management', + label: 'Product Backlog management', + group: 'productAndProjectManagement', + }, + { + id: 'Product management', + label: 'Product management', + group: 'productAndProjectManagement', + }, + { + id: 'Project management', + label: 'Project management', + group: 'productAndProjectManagement', + }, + { + id: 'Project management office', + label: 'Project management office', + group: 'productAndProjectManagement', + }, + { + id: 'Value-driven product development', + label: 'Value-driven product development', + group: 'productAndProjectManagement', + }, + { + id: 'Value metrics', + label: 'Value metrics', + group: 'productAndProjectManagement', + }, + { + id: 'Android Mobile Development', + label: 'Android Mobile Development', + group: 'softwareDevelopment', + }, + { + id: 'Basic programming skills', + label: 'Basic programming skills', + group: 'softwareDevelopment', + }, + { + id: 'Computer networking', + label: 'Computer networking', + group: 'softwareDevelopment', + }, + { + id: 'Cross-browser development', + label: 'Cross-browser development', + group: 'softwareDevelopment', + }, + { + id: 'Data analytics', + label: 'Data analytics', + group: 'softwareDevelopment', + }, + { + id: 'Database management', + label: 'Database management', + group: 'softwareDevelopment', + }, + { + id: 'DevOps and Cloud', + label: 'DevOps and Cloud', + group: 'softwareDevelopment', + }, + { + id: 'Hybrid App Development', + label: 'Hybrid App Development', + group: 'softwareDevelopment', + }, + { + id: 'Hardware and networks', + label: 'Hardware and networks', + group: 'softwareDevelopment', + }, + { id: 'IoT', label: 'IoT', group: 'softwareDevelopment' }, + { + id: 'iOS Mobile Development', + label: 'iOS Mobile Development', + group: 'softwareDevelopment', + }, + { + id: 'Machine learning', + label: 'Machine learning', + group: 'softwareDevelopment', + }, + { id: 'Security', label: 'Security', group: 'softwareDevelopment' }, + { + id: 'Atlassian Jira', + label: 'Atlassian Jira', + group: 'toolsAndFrameworks', + }, + { id: 'AWS', label: 'AWS', group: 'toolsAndFrameworks' }, + { id: 'Azure', label: 'Azure', group: 'toolsAndFrameworks' }, + { + id: 'Balsamiq Mockup', + label: 'Balsamiq Mockup', + group: 'toolsAndFrameworks', + }, + { id: 'Blockchain', label: 'Blockchain', group: 'toolsAndFrameworks' }, + { id: 'Cucumber', label: 'Cucumber', group: 'toolsAndFrameworks' }, + { id: 'Cypress', label: 'Cypress', group: 'toolsAndFrameworks' }, + { id: 'Docker', label: 'Docker', group: 'toolsAndFrameworks' }, + { id: 'Figma', label: 'Figma', group: 'toolsAndFrameworks' }, + { id: 'Flutter', label: 'Flutter', group: 'toolsAndFrameworks' }, + { id: 'GCP', label: 'GCP', group: 'toolsAndFrameworks' }, + { id: 'Git', label: 'Git', group: 'toolsAndFrameworks' }, + { id: 'Gherkin', label: 'Gherkin', group: 'toolsAndFrameworks' }, + { id: 'HTML & CSS', label: 'HTML & CSS', group: 'toolsAndFrameworks' }, + { id: 'Java', label: 'Java', group: 'toolsAndFrameworks' }, + { id: 'JavaScript', label: 'JavaScript', group: 'toolsAndFrameworks' }, + { id: 'Jest', label: 'Jest', group: 'toolsAndFrameworks' }, + { id: 'MongoDB', label: 'MongoDB', group: 'toolsAndFrameworks' }, + { id: 'MySQL', label: 'MySQL', group: 'toolsAndFrameworks' }, + { id: 'NodeJS', label: 'NodeJS', group: 'toolsAndFrameworks' }, + { id: 'Python', label: 'Python', group: 'toolsAndFrameworks' }, + { id: 'React', label: 'React', group: 'toolsAndFrameworks' }, + { id: 'React Native', label: 'React Native', group: 'toolsAndFrameworks' }, + { id: "REST API's", label: "REST API's", group: 'toolsAndFrameworks' }, + { id: 'Salesforce', label: 'Salesforce', group: 'toolsAndFrameworks' }, + { id: 'Selenium', label: 'Selenium', group: 'toolsAndFrameworks' }, + { id: 'Sketch', label: 'Sketch', group: 'toolsAndFrameworks' }, + { id: 'SQL', label: 'SQL', group: 'toolsAndFrameworks' }, + { id: 'Xray', label: 'Xray', group: 'toolsAndFrameworks' }, + { id: 'Zephyr', label: 'Zephyr', group: 'toolsAndFrameworks' }, + { id: 'VMWare', label: 'VMWare', group: 'toolsAndFrameworks' }, + { id: 'Virtual Box', label: 'Virtual Box', group: 'toolsAndFrameworks' }, +] +const mentoringTopicsKeys = MENTORING_TOPICS.map((topic) => topic.id) +const mentoringTopicsByDesiredRole = _.groupBy(MENTORING_TOPICS, 'group') + const categories = [ { id: 'basicProgrammingSkills', @@ -170,7 +581,7 @@ const categories = [ }, { id: 'entrepreneurship', label: 'Entrepreneurship', group: 'careerSupport' }, { id: 'freelancing', label: 'Freelancing', group: 'careerSupport' }, -] +].map((p) => p.id) const Languages = ['German', 'Arabic', 'Farsi', 'Tigrinya'] @@ -235,6 +646,67 @@ const pickRandomUserType = () => { return possibleUserTypes[randomIndex] } +const addMentorSpecificProperties = (profile) => { + profile.mentor_mentoringTopics = pickRandomArrayElements( + mentoringTopicsKeys, + 6 + ) + profile.mentor_mentoringGoals = pickRandomArrayElements(mentoringGoalsKeys, 2) + profile.mentor_professionalExperienceFields = pickRandomArrayElements( + fieldsOfExpertiseKeys, + 2 + ) + return profile +} + +const addMenteeSpecificProperties = (profile) => { + profile.mentee_mentoringGoal = pickRandomArrayElement(mentoringGoalsKeys) + profile.mentee_overarchingMentoringTopics = pickRandomArrayElements( + mentoringTopicsByDesiredRole['overarchingTopics'].map((p) => p.id), + 3 + ) + profile.mentee_primaryRole_fieldOfExpertise = + pickRandomArrayElement(desiredRolesKeys) + if ( + !mentoringTopicsByDesiredRole[profile.mentee_primaryRole_fieldOfExpertise] + ) { + console.log(profile.mentee_primaryRole_fieldOfExpertise) + console.log( + mentoringTopicsByDesiredRole[profile.mentee_primaryRole_fieldOfExpertise] + ) + } + profile.mentee_primaryRole_mentoringTopics = pickRandomArrayElements( + mentoringTopicsByDesiredRole[ + profile.mentee_primaryRole_fieldOfExpertise + ].map((p) => p.id), + 3 + ) + profile.mentee_secondaryRole_fieldOfExpertise = + pickRandomArrayElement(desiredRolesKeys) + if ( + !mentoringTopicsByDesiredRole[profile.mentee_secondaryRole_fieldOfExpertise] + ) { + console.log(profile.mentee_secondaryRole_fieldOfExpertise) + console.log( + mentoringTopicsByDesiredRole[ + profile.mentee_secondaryRole_fieldOfExpertise + ] + ) + } + profile.mentee_secondaryRole_mentoringTopics = pickRandomArrayElements( + mentoringTopicsByDesiredRole[ + profile.mentee_secondaryRole_fieldOfExpertise + ].map((p) => p.id), + 3 + ) + profile.mentee_toolsAndFrameworks_mentoringTopics = pickRandomArrayElements( + mentoringTopicsByDesiredRole['toolsAndFrameworks'].map((p) => p.id), + 6 + ) + + return profile +} + const users = fp.compose( fp.take(1000), fp.map(({ name, surname, gender }) => { @@ -242,7 +714,7 @@ const users = fp.compose( Math.random() > 0.5 ? 'berlin' : Math.random() > 0.5 ? 'munich' : 'nrw' const email = randomString() + '@' + randomString() + '.com' const password = email - return { + const profile = { redUser: { email, password, @@ -272,8 +744,8 @@ const users = fp.compose( mentee_occupationOther_description: randomString(), profileAvatarImageS3Key: 'c1774822-9495-4bd6-866a-bf4d28aaddc8_ScreenShot2019-03-12at22.22.20.png', - languages: Languages.filter(() => Math.random() > 0.5).concat( - 'English' + languages: ['English'].concat( + Languages.filter(() => Math.random() > 0.5) ), otherLanguages: randomString(), personalDescription: randomString(undefined, 300), @@ -292,6 +764,12 @@ const users = fp.compose( courses[Math.floor(Math.random() * courses.length)].id, }, } + if (profile.redProfile.userType.indexOf('mentee') !== -1) { + addMenteeSpecificProperties(profile.redProfile) + } else { + addMentorSpecificProperties(profile.redProfile) + } + return profile }) )(persons) @@ -363,6 +841,7 @@ const ericMenteeRedProfile = { mentee_currentlyEnrolledInCourse: 'salesforceFundamentals', username: 'career+testmentee@redi-school.org', } +addMenteeSpecificProperties(ericMenteeRedProfile) const ericMentorRedUser = { password: 'career+testmentor@redi-school.org', @@ -404,6 +883,7 @@ const ericMentorRedProfile = { menteeCountCapacity: 2, username: 'career+testmentor@redi-school.org', } +addMentorSpecificProperties(ericMentorRedProfile) const ericAdminUser = { email: 'cloud-accounts@redi-school.org', @@ -442,7 +922,7 @@ const ericAdminRedProfile = { githubProfileUrl: '', telephoneNumber: '', categories: categories.map((c) => c.id).filter(() => Math.random() < 0.4), - menteeCountCapacity: 2, + menteeCountCapacity: 0, username: 'cloud-accounts@redi-school.org', } @@ -455,7 +935,7 @@ Rx.of({}) switchMap(redUserDestroyAll), switchMap(redProfileDestroyAll), tap(() => console.log('destroyed')), - delay(10000), + delay(2000), // switchMap(redMentoringSessionDestroyAll), switchMap(() => redUserCreate(ericAdminUser)), switchMap((redUser) => @@ -471,14 +951,16 @@ Rx.of({}) redProfileCreateOnRedUser(redUser)(ericMentorRedProfile) ), switchMapTo(users), - concatMap( + mergeMap( (userData) => redUserCreate(userData.redUser), - (userData, redUserInst) => ({ ...userData, redUserInst }) + (userData, redUserInst) => ({ ...userData, redUserInst }), + 10 ), - concatMap( + mergeMap( (userData) => redProfileCreateOnRedUser(userData.redUserInst)(userData.redProfile), - (userData, redProfileInst) => ({ ...userData, redProfileInst }) + (userData, redProfileInst) => ({ ...userData, redProfileInst }), + 10 ), toArray(), // Pick X mentor-mentee pairs, create match diff --git a/apps/redi-connect/src/assets/locales/en/translation.json b/apps/redi-connect/src/assets/locales/en/translation.json index 0192236d3..4e3d48dd8 100644 --- a/apps/redi-connect/src/assets/locales/en/translation.json +++ b/apps/redi-connect/src/assets/locales/en/translation.json @@ -186,7 +186,8 @@ "profile": { "notification": { "pendingMentor": "Thanks for signing up! We are reviewing your profile and will send you an email once we're done. Students will be able to apply to become your mentee once your account is active.", - "pendingMentee": "Thanks for signing up! Please schedule a time for an onboarding session with us. You find the link in the email you received. Youโ€™ll be able to find a mentor once your account is active.", + "pendingMentee": "Thanks for signing up! Please schedule a time for an onboarding session with us. You find the link in the email you received. Meanwhile, we encourage you to fill in your profile.", + "missingMentoringGoal": "Welcome to ReDI Connect! Please fill in your profile and select a mentoring goal and a primary role you would like to be mentored on. You will be able to find a mentor once your profile is complete.", "deactivatedMentee": "Dear {{ name }}, your ReDI Connect profile has been deactivated by the Career Support Team. This could be for a number of reasons. If you think this has been done by mistake, please contact Paulina at {{ email }}. Thank you!", "deactivatedMentor": "Dear {{ name }}, your ReDI Connect profile has been deactivated by the Career Support Team. Likely you have not been active for a while. This means you are not visible to prospective mentees. If you want to become active as a mentor again, please contact Miriam at {{ email }}. Speak soon!" } diff --git a/apps/redi-connect/src/components/molecules/ReadMentoringGoals.tsx b/apps/redi-connect/src/components/molecules/ReadMentoringGoals.tsx new file mode 100644 index 000000000..35ce4f58d --- /dev/null +++ b/apps/redi-connect/src/components/molecules/ReadMentoringGoals.tsx @@ -0,0 +1,56 @@ +import { + Caption, + CardTags, + CardTagsProps, + Placeholder, +} from '@talent-connect/shared-atomic-design-components' +import { MENTORING_GOALS } from '@talent-connect/shared-config' +import { RedProfile } from '@talent-connect/shared-types' +import React from 'react' +import { connect } from 'react-redux' +import { RootState } from '../../redux/types' + +interface ReadMentoringProps { + profile: RedProfile + caption?: boolean +} + +export const ProfileTags = ({ items, shortList }: CardTagsProps) => ( + MENTORING_GOALS[item]} + /> +) + +const ReadMentoringGoals = ({ profile, caption }: ReadMentoringProps) => { + const { mentor_mentoringGoals: mentoringGoals } = profile + + if (!mentoringGoals?.length && !caption) + return ( + + Select at least one goal you would like to support mentees with + + ) + + return ( + <> + {caption && Mentoring goals} + + + ) +} + +const mapStateToProps = (state: RootState) => ({ + profile: state.user.profile as RedProfile, +}) + +export default { + Me: connect(mapStateToProps, {})(ReadMentoringGoals), + Some: ({ profile }: ReadMentoringProps) => ( + + ), + Tags: ({ items, shortList }: CardTagsProps) => ( + + ), +} diff --git a/apps/redi-connect/src/components/molecules/ReadMentoringTopicsNew2022.tsx b/apps/redi-connect/src/components/molecules/ReadMentoringTopicsNew2022.tsx new file mode 100644 index 000000000..5ed9a378c --- /dev/null +++ b/apps/redi-connect/src/components/molecules/ReadMentoringTopicsNew2022.tsx @@ -0,0 +1,55 @@ +import { + Caption, + CardTags, + CardTagsProps, + Placeholder, +} from '@talent-connect/shared-atomic-design-components' +import { MENTORING_TOPICS_MAP } from '@talent-connect/shared-config' +import { RedProfile } from '@talent-connect/shared-types' +import React from 'react' +import { connect } from 'react-redux' +import { RootState } from '../../redux/types' + +interface ReadMentoringProps { + profile: RedProfile + caption?: boolean +} + +export const ProfileTags = ({ items, shortList }: CardTagsProps) => ( + MENTORING_TOPICS_MAP[item]} + /> +) + +const ReadMentoringTopicsNew2022 = ({ + profile, + caption, +}: ReadMentoringProps) => { + const { mentor_mentoringTopics } = profile + + if (!mentor_mentoringTopics?.length && !caption) + return Please pick mentoring topics. + + return ( + <> + {caption && {'Mentoring Topics'}} + + + ) +} + +const mapStateToProps = (state: RootState) => ({ + profile: state.user.profile as RedProfile, +}) + +export default { + Me: connect(mapStateToProps, {})(ReadMentoringTopicsNew2022), + Some: ({ profile }: ReadMentoringProps) => ( + + ), + Tags: ({ items, shortList }: CardTagsProps) => ( + + ), +} diff --git a/apps/redi-connect/src/components/molecules/ReadProfessionalExperienceFields.tsx b/apps/redi-connect/src/components/molecules/ReadProfessionalExperienceFields.tsx new file mode 100644 index 000000000..f13a804a9 --- /dev/null +++ b/apps/redi-connect/src/components/molecules/ReadProfessionalExperienceFields.tsx @@ -0,0 +1,56 @@ +import { + Caption, + CardTags, + CardTagsProps, + Placeholder, +} from '@talent-connect/shared-atomic-design-components' +import { FIELDS_OF_EXPERTISE } from '@talent-connect/shared-config' +import { RedProfile } from '@talent-connect/shared-types' +import React from 'react' +import { connect } from 'react-redux' +import { RootState } from '../../redux/types' + +interface ReadMentoringProps { + profile: RedProfile + caption?: boolean +} + +export const ProfileTags = ({ items, shortList }: CardTagsProps) => ( + FIELDS_OF_EXPERTISE[item]} + /> +) + +const ReadProfessionalExperienceFields = ({ + profile, + caption, +}: ReadMentoringProps) => { + const { mentor_professionalExperienceFields: professionalExperienceFields } = + profile + + if (!professionalExperienceFields?.length && !caption) + return Select your fields of expertise + + return ( + <> + {caption && Professional experience} + + + ) +} + +const mapStateToProps = (state: RootState) => ({ + profile: state.user.profile as RedProfile, +}) + +export default { + Me: connect(mapStateToProps, {})(ReadProfessionalExperienceFields), + Some: ({ profile }: ReadMentoringProps) => ( + + ), + Tags: ({ items, shortList }: CardTagsProps) => ( + + ), +} diff --git a/apps/redi-connect/src/components/organisms/EditableAbout.tsx b/apps/redi-connect/src/components/organisms/EditableAbout.tsx index 1692471a0..e6397df05 100644 --- a/apps/redi-connect/src/components/organisms/EditableAbout.tsx +++ b/apps/redi-connect/src/components/organisms/EditableAbout.tsx @@ -58,6 +58,7 @@ const EditableAbout = ({ profile, profileSaveStart }: any) => { return ( formik.handleSubmit()} onClose={() => formik.resetForm()} savePossible={formik.dirty && formik.isValid} diff --git a/apps/redi-connect/src/components/organisms/EditableMentoringGoals.tsx b/apps/redi-connect/src/components/organisms/EditableMentoringGoals.tsx new file mode 100644 index 000000000..70dc75f0f --- /dev/null +++ b/apps/redi-connect/src/components/organisms/EditableMentoringGoals.tsx @@ -0,0 +1,138 @@ +import { + Checkbox, + Editable, +} from '@talent-connect/shared-atomic-design-components' +import { MENTORING_GOALS } from '@talent-connect/shared-config' +import { RedProfile } from '@talent-connect/shared-types' +import { objectEntries } from '@talent-connect/typescript-utilities' +import { FormikValues, useFormik } from 'formik' +import React from 'react' +import { Content, Element } from 'react-bulma-components' +import { connect } from 'react-redux' +import * as Yup from 'yup' +import { RootState } from '../../redux/types' +import { profileSaveStart } from '../../redux/user/actions' +import ReadMentoringGoals from '../molecules/ReadMentoringGoals' + +export interface FormValues { + isMentor: boolean + mentor_mentoringGoals: string[] +} + +const formMentoringGoals = objectEntries(MENTORING_GOALS) + +interface Props { + profile: RedProfile | undefined + profileSaveStart: (arg0: Partial) => void +} + +const validationSchema = Yup.object({ + mentoringGoals: Yup.array().min(1), +}) + +const EditableMentoringGoals = ({ profile, profileSaveStart }: Props) => { + const { + id, + userType, + mentor_mentoringGoals: mentoringGoals, + } = profile as RedProfile + + const submitForm = async (values: FormikValues) => { + const profileMentoring = values as Partial + profileSaveStart({ ...profileMentoring, id }) + } + + const isMentor = + userType === 'mentor' || userType === 'public-sign-up-mentor-pending-review' + + const initialValues: FormValues = { + isMentor, + mentor_mentoringGoals: mentoringGoals || [], + } + + const formik = useFormik({ + initialValues, + enableReinitialize: true, + validationSchema, + onSubmit: submitForm, + }) + + const { mentor_mentoringGoals: selectedMentoringGoals } = formik.values + + const mentoringGoalsChange = (e: any) => { + e.persist() + const value = e.target.value + let newMentoringGoals + if (e.target.checked) { + newMentoringGoals = selectedMentoringGoals.concat(value) + } else { + newMentoringGoals = selectedMentoringGoals.filter( + (cat: any) => cat !== value + ) + } + formik.setFieldValue('mentor_mentoringGoals', newMentoringGoals) + formik.setFieldTouched('mentor_mentoringGoals', true, false) + } + + return ( + formik.handleSubmit()} + onClose={() => formik.resetForm()} + savePossible={formik.dirty && formik.isValid} + read={} + className="mentoring" + > + + Select at least one goal you would like to support mentees with + + + {formMentoringGoals.map(([fieldId, fieldLabel]) => ( + + ))} + + + ) +} + +const MentoringGoal = ({ + id, + label, + selectedMentoringGoals, + onChange, + formik, +}: any) => { + return ( + + {label} + + ) +} + +const mapStateToProps = (state: RootState) => ({ + profile: state.user.profile, +}) + +const mapDispatchToProps = (dispatch: any) => ({ + profileSaveStart: (profile: Partial) => + dispatch(profileSaveStart(profile)), +}) + +export default connect( + mapStateToProps, + mapDispatchToProps +)(EditableMentoringGoals) diff --git a/apps/redi-connect/src/components/organisms/EditableMentoringTopics.tsx b/apps/redi-connect/src/components/organisms/EditableMentoringTopics.tsx index ae08f56c3..fb1b45e6a 100644 --- a/apps/redi-connect/src/components/organisms/EditableMentoringTopics.tsx +++ b/apps/redi-connect/src/components/organisms/EditableMentoringTopics.tsx @@ -121,9 +121,6 @@ const CategoryGroup = ({ onChange, formik, }: any) => { - // The current REDI_LOCATION might not use the current CategoryGroup (e.g. - // Munich doesnt, at the time or writing, use 'coding' or 'other'. If it's the case, return null - if (!categoriesByGroup[id]) return null return ( diff --git a/apps/redi-connect/src/components/organisms/EditableMentoringTopicsNew2022.tsx b/apps/redi-connect/src/components/organisms/EditableMentoringTopicsNew2022.tsx new file mode 100644 index 000000000..fb2f1bb3a --- /dev/null +++ b/apps/redi-connect/src/components/organisms/EditableMentoringTopicsNew2022.tsx @@ -0,0 +1,152 @@ +import { + Checkbox, + Editable, +} from '@talent-connect/shared-atomic-design-components' +import { + MentoringTopicKey, + MENTORING_TOPICS, + MENTORING_TOPIC_GROUPS, +} from '@talent-connect/shared-config' +import { RedProfile } from '@talent-connect/shared-types' +import { objectEntries } from '@talent-connect/typescript-utilities' +import { FormikValues, useFormik } from 'formik' +import groupBy from 'lodash/groupBy' +import React from 'react' +import { Columns, Content, Element, Heading } from 'react-bulma-components' +import { connect } from 'react-redux' +import { RootState } from '../../redux/types' +import { profileSaveStart } from '../../redux/user/actions' +import ReadMentoringTopicsNew2022 from '../molecules/ReadMentoringTopicsNew2022' + +export type UserType = + | 'mentor' + | 'mentee' + | 'public-sign-up-mentor-pending-review' + | 'public-sign-up-mentee-pending-review' + +export interface FormValues { + mentor_mentoringTopics: MentoringTopicKey[] +} + +const mentoringTopicsByGroup = groupBy(MENTORING_TOPICS, (topic) => topic.group) + +const formMentoringTopicGroups = objectEntries(MENTORING_TOPIC_GROUPS) + +interface Props { + profile: RedProfile | undefined + profileSaveStart: Function +} + +const EditableMentoringTopics = ({ profile, profileSaveStart }: Props) => { + const { id, userType, mentor_mentoringTopics } = profile as RedProfile + + const submitForm = async (values: FormikValues) => { + const profileMentoring = values as Partial + profileSaveStart({ ...profileMentoring, id }) + } + + const initialValues: FormValues = { + mentor_mentoringTopics: mentor_mentoringTopics || [], + } + + const formik = useFormik({ + initialValues, + enableReinitialize: true, + onSubmit: submitForm, + }) + + const { mentor_mentoringTopics: selectedMentoringTopics } = formik.values + + const mentoringTopicsChange = (e: any) => { + e.persist() + const value = e.target.value + let newMentoringTopics + if (e.target.checked) { + newMentoringTopics = selectedMentoringTopics.concat(value) + } else { + newMentoringTopics = selectedMentoringTopics.filter( + (cat: any) => cat !== value + ) + } + formik.setFieldValue('mentor_mentoringTopics', newMentoringTopics) + formik.setFieldTouched('mentor_mentoringTopics', true, false) + } + + return ( + formik.handleSubmit()} + onClose={() => formik.resetForm()} + savePossible={formik.dirty && formik.isValid} + read={} + className="mentoring" + > + + Select at least one topic where you would like to support mentees. + + + {formMentoringTopicGroups.map(([groupId, groupLabel]) => ( + + ))} + + + ) +} + +const MentoringTopicGroup = ({ + id, + label, + selectedMentoringTopics, + onChange, + formik, +}: any) => { + if (!mentoringTopicsByGroup[id]) return null + return ( + + + {label} + + + {mentoringTopicsByGroup[id].map((groupItem) => ( + + {groupItem.label} + + ))} + + + ) +} + +const mapStateToProps = (state: RootState) => ({ + profile: state.user.profile, +}) + +const mapDispatchToProps = (dispatch: any) => ({ + profileSaveStart: (profile: Partial) => + dispatch(profileSaveStart(profile)), +}) + +export default connect( + mapStateToProps, + mapDispatchToProps +)(EditableMentoringTopics) diff --git a/apps/redi-connect/src/components/organisms/EditableProfessionalExperienceFields.tsx b/apps/redi-connect/src/components/organisms/EditableProfessionalExperienceFields.tsx new file mode 100644 index 000000000..e90ccd1d2 --- /dev/null +++ b/apps/redi-connect/src/components/organisms/EditableProfessionalExperienceFields.tsx @@ -0,0 +1,140 @@ +import { + Checkbox, + Editable, +} from '@talent-connect/shared-atomic-design-components' +import { FIELDS_OF_EXPERTISE } from '@talent-connect/shared-config' +import { RedProfile } from '@talent-connect/shared-types' +import { objectEntries } from '@talent-connect/typescript-utilities' +import { FormikValues, useFormik } from 'formik' +import React from 'react' +import { Content, Element } from 'react-bulma-components' +import { connect } from 'react-redux' +import { RootState } from '../../redux/types' +import { profileSaveStart } from '../../redux/user/actions' +import { ReadMentoringTopics } from '../molecules' +import ReadProfessionalExperienceFields from '../molecules/ReadProfessionalExperienceFields' + +export interface FormValues { + isMentor: boolean + mentor_professionalExperienceFields: string[] +} + +const formProfessionalExperienceFields = objectEntries(FIELDS_OF_EXPERTISE) + +interface Props { + profile: RedProfile | undefined + profileSaveStart: Function +} + +const EditableProfessionalExperienceFields = ({ + profile, + profileSaveStart, +}: Props) => { + const { + id, + userType, + mentor_professionalExperienceFields: professionalExperienceFields, + } = profile as RedProfile + + const submitForm = async (values: FormikValues) => { + const profileMentoring = values as Partial + profileSaveStart({ ...profileMentoring, id }) + } + + const isMentor = + userType === 'mentor' || userType === 'public-sign-up-mentor-pending-review' + + const initialValues: FormValues = { + isMentor, + mentor_professionalExperienceFields: professionalExperienceFields || [], + } + + const formik = useFormik({ + initialValues, + enableReinitialize: true, + onSubmit: submitForm, + }) + + const { mentor_professionalExperienceFields: selectedProfessionalExperienceFields } = + formik.values + + const professionalExperienceFieldsChange = (e: any) => { + e.persist() + const value = e.target.value + let newProfessionalExperienceFields + if (e.target.checked) { + newProfessionalExperienceFields = + selectedProfessionalExperienceFields.concat(value) + } else { + newProfessionalExperienceFields = + selectedProfessionalExperienceFields.filter((cat: any) => cat !== value) + } + formik.setFieldValue( + 'mentor_professionalExperienceFields', + newProfessionalExperienceFields + ) + formik.setFieldTouched('mentor_professionalExperienceFields', true, false) + } + + return ( + formik.handleSubmit()} + onClose={() => formik.resetForm()} + savePossible={formik.dirty && formik.isValid} + read={} + className="mentoring" + > + Select your fields of expertise + + {formProfessionalExperienceFields.map(([fieldId, fieldLabel]) => ( + + ))} + + + ) +} + +const ProfessionalExperienceField = ({ + id, + label, + selectedProfessionalExperienceFields, + onChange, + formik, +}: any) => { + return ( + + {label} + + ) +} + +const mapStateToProps = (state: RootState) => ({ + profile: state.user.profile, +}) + +const mapDispatchToProps = (dispatch: any) => ({ + profileSaveStart: (profile: Partial) => + dispatch(profileSaveStart(profile)), +}) + +export default connect( + mapStateToProps, + mapDispatchToProps +)(EditableProfessionalExperienceFields) diff --git a/apps/redi-connect/src/components/organisms/ProfileCard.tsx b/apps/redi-connect/src/components/organisms/ProfileCard.tsx index b615b6788..869d70055 100644 --- a/apps/redi-connect/src/components/organisms/ProfileCard.tsx +++ b/apps/redi-connect/src/components/organisms/ProfileCard.tsx @@ -7,6 +7,8 @@ import { useHistory } from 'react-router-dom' import { AWS_PROFILE_AVATARS_BUCKET_BASE_URL, REDI_LOCATION_NAMES, + FIELDS_OF_EXPERTISE, + MENTORING_TOPICS_MAP, } from '@talent-connect/shared-config' import placeholderImage from '../../assets/images/img-placeholder.png' @@ -30,15 +32,23 @@ const ProfileCard = ({ }: ProfileCardProps) => { const history = useHistory() - const { + let { firstName, lastName, languages, - categories, + mentor_mentoringTopics, + mentor_professionalExperienceFields, rediLocation, profileAvatarImageS3Key, } = profile + const mentoringTopics = + mentor_mentoringTopics?.map((topic) => MENTORING_TOPICS_MAP[topic]) ?? [] + const professionalExperienceFields = + mentor_professionalExperienceFields?.map( + (field) => FIELDS_OF_EXPERTISE[field] + ) ?? [] + const handleFavorite = (e: React.MouseEvent) => { e.stopPropagation() toggleFavorite && toggleFavorite(profile.id) @@ -82,9 +92,19 @@ const ProfileCard = ({ {REDI_LOCATION_NAMES[rediLocation]} {languages && } - {categories && ( - - )} +

+ + Mentors in these overarching topics, role-related skills and + tools/frameworks: + + {mentoringTopics?.length > 0 ? mentoringTopics.join(' | ') : null} +

+

+ Has professional experience in these fields: + {professionalExperienceFields?.length > 0 + ? professionalExperienceFields.join(' | ') + : null} +

) diff --git a/apps/redi-connect/src/components/templates/LoggedIn.tsx b/apps/redi-connect/src/components/templates/LoggedIn.tsx index 353104285..1499981c2 100644 --- a/apps/redi-connect/src/components/templates/LoggedIn.tsx +++ b/apps/redi-connect/src/components/templates/LoggedIn.tsx @@ -24,6 +24,7 @@ import { import { useTranslation } from 'react-i18next' import Footer from '../organisms/Footer' import { RedMatch } from '@talent-connect/shared-types' +import { ensureMenteeProfileIsComplete } from '../../../../redi-connect/src/pages/app/find-a-mentor/utils' interface Props { loading: boolean @@ -72,6 +73,8 @@ const LoggedIn = ({ history.push(`/app/mentorships/${redMatchId}`) } + const isMenteeProfileComplete = ensureMenteeProfileIsComplete(profile) + return ( <> @@ -96,6 +99,11 @@ const LoggedIn = ({ {t('loggedInArea.profile.notification.pendingMentor')} )} + {profile.userType === 'mentee' && !isMenteeProfileComplete && ( + + {t('loggedInArea.profile.notification.missingMentoringGoal')} + + )} {profile.userType === 'mentee' && !profile.userActivated && ( {t('loggedInArea.profile.notification.deactivatedMentee', { diff --git a/apps/redi-connect/src/pages/app/applications/Applications.tsx b/apps/redi-connect/src/pages/app/applications/Applications.tsx index ea98e8bee..df3385b06 100644 --- a/apps/redi-connect/src/pages/app/applications/Applications.tsx +++ b/apps/redi-connect/src/pages/app/applications/Applications.tsx @@ -9,6 +9,7 @@ import { connect } from 'react-redux' import { RedMatch } from '@talent-connect/shared-types' import { useHistory } from 'react-router-dom' import { getRedProfileFromLocalStorage } from '../../../services/auth/auth' +import { ensureMenteeProfileIsComplete } from '../find-a-mentor/utils' interface Props { applicants: RedMatch[] @@ -18,7 +19,13 @@ function Applications({ applicants }: Props) { const history = useHistory() const profile = getRedProfileFromLocalStorage() - if (profile.userActivated !== true) return + const isMenteeProfileComplete = ensureMenteeProfileIsComplete(profile) + + if ( + profile.userActivated !== true || + (profile.userType === 'mentee' && !isMenteeProfileComplete) + ) + return return ( diff --git a/apps/redi-connect/src/pages/app/find-a-mentor/FindAMentor.tsx b/apps/redi-connect/src/pages/app/find-a-mentor/FindAMentor.tsx index 33480800c..9152bdee7 100644 --- a/apps/redi-connect/src/pages/app/find-a-mentor/FindAMentor.tsx +++ b/apps/redi-connect/src/pages/app/find-a-mentor/FindAMentor.tsx @@ -1,6 +1,6 @@ import React, { useCallback, useEffect, useState } from 'react' import { Content, Columns, Tag, Form } from 'react-bulma-components' -import { debounce } from 'lodash' +import { debounce, values } from 'lodash' import { Heading, Icon, @@ -16,9 +16,20 @@ import { connect } from 'react-redux' import { RootState } from '../../../redux/types' import { LoggedIn } from '../../../components/templates' -import { CATEGORIES, REDI_LOCATION_NAMES } from '@talent-connect/shared-config' +import { + CATEGORIES, + Language, + MentoringGoalKey, + DesiredRolesKey, + MentoringTopicKey, + REDI_LOCATION_NAMES, + MENTORING_TOPICS, + MENTORING_TOPICS_MAP, + FIELDS_OF_EXPERTISE, + FieldOfExperienceKey, +} from '@talent-connect/shared-config' import './FindAMentor.scss' -import { toggleValueInArray } from './utils' +import { toggleValueInArray, ensureMenteeProfileIsComplete } from './utils' import { StringParam, useQueryParams, @@ -28,63 +39,45 @@ import { } from 'use-query-params' import { objectKeys } from '@talent-connect/typescript-utilities' -const filterCategories = CATEGORIES.map((category) => ({ - value: category.id, - label: category.label, -})) - -interface FilterTagProps { - id: string - label: string - onClickHandler: (item: string) => void -} - -const FilterTag = ({ id, label, onClickHandler }: FilterTagProps) => ( - - {label} - { - onClickHandler(id) - }} - className="active-filters__remove" - /> - -) - interface FindAMentorProps { profile: RedProfile profileSaveStart: (profile: Partial) => void } -const FindAMentor = ({ profile, profileSaveStart }: FindAMentorProps) => { +function FindAMentor({ profile, profileSaveStart }: FindAMentorProps) { + profile = ensureNoUndefinedArrayPropertiesInProfile(profile) const { Loading, isLoading, setLoading } = useLoading() - const { - id, - categories: categoriesFromProfile, - favouritedRedProfileIds, - rediLocation, - } = profile - const [showFavorites, setShowFavorites] = useState(false) - const [mentors, setMentors] = useState([]) - const [query, setQuery] = useQueryParams({ - name: withDefault(StringParam, undefined), - topics: withDefault(ArrayParam, []), - languages: withDefault(ArrayParam, []), - locations: withDefault(ArrayParam, []), + const [allFetchedMentors, setAllFetchedMentors] = useState([]) + const [queryParams, setQuery] = useQueryParams({ + nameQuery: withDefault(StringParam, undefined), onlyFavorites: withDefault(BooleanParam, undefined), + mentoringTopics: withDefault(ArrayParam, []), + mentoringTopicsToolsAndFrameworks: withDefault(ArrayParam, []), + locations: withDefault(ArrayParam, []), + roles: withDefault(ArrayParam, []), }) - const { topics, name, languages, locations, onlyFavorites } = query + + const nameQuery = queryParams.nameQuery + const onlyFavorites = queryParams.onlyFavorites + const mentoringTopics = + queryParams.mentoringTopics as Array + const mentoringTopicsToolsAndFrameworks = + queryParams.mentoringTopicsToolsAndFrameworks as Array + const locations = queryParams.locations + const roles = queryParams.roles useEffect(() => { - const hasQuery = - topics.length > 0 || - languages.length > 0 || - locations.length > 0 || - Boolean(name) || - onlyFavorites - setQuery(hasQuery ? query : { ...query, topics: categoriesFromProfile }) - }, []) + setLoading(true) + getMentors({ + nameQuery: nameQuery ?? '', + }).then((mentors) => { + setAllFetchedMentors(ensureNoUndefinedArrayPropertiesInProfiles(mentors)) + setLoading(false) + }) + }, [nameQuery]) + + const { id, favouritedRedProfileIds, rediLocation } = profile + const [showFavorites, setShowFavorites] = useState(false) const toggleFilters = (filtersArr, filterName, item) => { const newFilters = toggleValueInArray(filtersArr, item) @@ -92,7 +85,10 @@ const FindAMentor = ({ profile, profileSaveStart }: FindAMentorProps) => { } const setName = (value) => { - setQuery((latestQuery) => ({ ...latestQuery, name: value || undefined })) + setQuery((latestQuery) => ({ + ...latestQuery, + nameQuery: value || undefined, + })) } const toggleFavorites = (favoritesArr, value) => { @@ -113,141 +109,173 @@ const FindAMentor = ({ profile, profileSaveStart }: FindAMentorProps) => { const clearFilters = () => { setQuery((latestQuery) => ({ ...latestQuery, - topics: [], - languages: [], + mentoringTopics: [], + mentoringTopicsToolsAndFrameworks: [], locations: [], + roles: [], })) } - const filterLanguages = Array.from( - new Set( - mentors - .map((mentor) => mentor.languages || []) - .flat() - .sort() + const isMenteeProfileComplete = ensureMenteeProfileIsComplete(profile) + + if (profile.userActivated !== true || !isMenteeProfileComplete) + return + + const eligibleMentors = allFetchedMentors + .filter((mentor) => mentor.currentFreeMenteeSpots > 0) + .filter( + (mentor) => + !mentor.optOutOfMenteesFromOtherRediLocation || + mentor.rediLocation === rediLocation ) - ).map((language) => ({ - value: language, - label: language, - })) - - const filterRediLocations = objectKeys(REDI_LOCATION_NAMES).map( - (location) => ({ - value: location, - label: REDI_LOCATION_NAMES[location as RediLocation] as string, - }) - ) - useEffect(() => { - setLoading(true) - getMentors({ - categories: topics, - languages, - nameQuery: name || '', - locations, - }).then((mentors) => { - setMentors( - mentors - .filter((mentor) => mentor.currentFreeMenteeSpots > 0) - .filter( - (mentor) => - !mentor.optOutOfMenteesFromOtherRediLocation || - mentor.rediLocation === rediLocation - ) - ) - setLoading(false) - }) - }, [topics, languages, locations, name]) + const mentorGroups = filterGroupRankMentors(eligibleMentors, { + menteeDesiredRoles: [ + profile.mentee_primaryRole_fieldOfExpertise, + profile.mentee_secondaryRole_fieldOfExpertise, + ], + menteeLanguages: profile.languages, + menteeMentoringGoal: profile.mentee_mentoringGoal, + menteePrimaryRoleMentoringTopics: + profile.mentee_primaryRole_mentoringTopics, + menteeSecondaryRoleMentoringTopics: + profile.mentee_secondaryRole_mentoringTopics, + menteeToolsAndFrameworksTopics: + profile.mentee_toolsAndFrameworks_mentoringTopics, - if (profile.userActivated !== true) return + chosenMentoringTopics: mentoringTopics, + chosenToolsAndFrameworks: mentoringTopicsToolsAndFrameworks, + chosenLocations: locations as Array, + chosedDesiredRoles: roles as Array, + + favoritedMentorsEnabled: showFavorites, + favoritedMentors: favouritedRedProfileIds, + }) + + const mentorCount = + mentorGroups.bestMatchMentors.length + mentorGroups.otherMentors.length return ( - Available mentors ({mentors.length}) + Available mentors ({mentorCount})
- +
+ +
+ +
+ + Only Favorites +
toggleFilters(topics, 'topics', item)} + selected={mentoringTopics} + onChange={(item) => + toggleFilters(mentoringTopics, 'mentoringTopics', item) + } />
-
+
toggleFilters(languages, 'languages', item)} + label="Tools and Frameworks" + selected={mentoringTopicsToolsAndFrameworks} + onChange={(item) => + toggleFilters( + mentoringTopicsToolsAndFrameworks, + 'mentoringTopicsToolsAndFrameworks', + item + ) + } /> -
- - Only Favorites -
-
-
-
+
toggleFilters(locations, 'locations', item)} />
- - {(topics.length !== 0 || - languages.length !== 0 || - locations.length !== 0) && ( +
+
+
+ toggleFilters(roles, 'roles', item)} + /> +
+
+
+ {(mentoringTopics.length !== 0 || + mentoringTopicsToolsAndFrameworks.length !== 0 || + locations.length !== 0 || + roles.length !== 0) && ( <> - {(topics as string[]).map((catId) => ( + {(mentoringTopics as string[]).map((id) => ( + + toggleFilters(mentoringTopics, 'mentoringTopics', item) + } + /> + ))} + {(mentoringTopicsToolsAndFrameworks as string[]).map((id) => ( item.id === catId).label} - onClickHandler={(item) => toggleFilters(topics, 'topics', item)} + key={id} + id={id} + label={MENTORING_TOPICS_MAP[id]} + onClickHandler={(item) => + toggleFilters( + mentoringTopicsToolsAndFrameworks, + 'mentoringTopicsToolsAndFrameworks', + item + ) + } /> ))} - {(languages as string[]).map((langId) => ( + {(locations as string[]).map((id) => ( - toggleFilters(languages, 'languages', item) + toggleFilters(locations, 'locations', item) } /> ))} - {(locations as RediLocation[]).map( - (locId) => - locId && ( - - toggleFilters(locations, 'locations', item) - } - /> - ) - )} + {(roles as string[]).map((id) => ( + toggleFilters(roles, 'roles', item)} + /> + ))} + Delete all filters @@ -256,28 +284,7 @@ const FindAMentor = ({ profile, profileSaveStart }: FindAMentorProps) => { )}
- - {mentors.map((mentor: RedProfile) => { - const isFavorite = favouritedRedProfileIds.includes(mentor.id) - - if (!isFavorite && showFavorites) return - - return ( - - - toggleFavorites(favouritedRedProfileIds, item) - } - isFavorite={isFavorite} - /> - - ) - })} - - - {mentors.length === 0 && !isLoading && ( + {mentorCount === 0 && !isLoading && ( <> Unfortunately could not find any mentors matching @@ -285,6 +292,44 @@ const FindAMentor = ({ profile, profileSaveStart }: FindAMentorProps) => { )} + {mentorCount > 0 && ( + <> +

+ Best Matches ({mentorGroups.bestMatchMentors.length}) +

+ + {mentorGroups.bestMatchMentors.map((mentor: RedProfile) => ( + + + toggleFavorites(favouritedRedProfileIds, item) + } + isFavorite={favouritedRedProfileIds.includes(mentor.id)} + /> + + ))} + +

+ All mentors ({mentorGroups.otherMentors.length}) +

+ + {mentorGroups.otherMentors.map((mentor: RedProfile) => ( + + + toggleFavorites(favouritedRedProfileIds, item) + } + isFavorite={favouritedRedProfileIds.includes(mentor.id)} + /> + + ))} + + + )} ) } @@ -297,3 +342,226 @@ const mapDispatchToProps = (dispatch: any) => ({ dispatch(profileSaveStart(profile)), }) export default connect(mapStateToProps, mapDispatchToProps)(FindAMentor) + +const filterItemsMentoringTopics = MENTORING_TOPICS.filter( + ({ group }) => group !== 'toolsAndFrameworks' +).map((category) => ({ + value: category.id, + label: category.label, +})) +const filterItemsToolsAndFrameworks = MENTORING_TOPICS.filter( + ({ group }) => group === 'toolsAndFrameworks' +).map((category) => ({ + value: category.id, + label: category.label, +})) +const filterItemsLocations = Object.entries(REDI_LOCATION_NAMES).map( + ([value, label]) => ({ value, label }) +) +const filterItemsRoles = Object.entries(FIELDS_OF_EXPERTISE).map( + ([value, label]) => ({ value, label }) +) + +interface FilterTagProps { + id: string + label: string + onClickHandler: (item: string) => void +} + +const FilterTag = ({ id, label, onClickHandler }: FilterTagProps) => ( + + {label} + { + onClickHandler(id) + }} + className="active-filters__remove" + /> + +) + +interface FilteredMentors { + bestMatchMentors: RedProfile[] + otherMentors: RedProfile[] +} +interface FiltersValues { + menteeLanguages: Array + menteeMentoringGoal: MentoringGoalKey + menteeDesiredRoles: Array + menteePrimaryRoleMentoringTopics: Array + menteeSecondaryRoleMentoringTopics: Array + menteeToolsAndFrameworksTopics: Array + + chosenMentoringTopics: Array + chosenToolsAndFrameworks: Array + chosenLocations: Array + chosedDesiredRoles: Array + + favoritedMentorsEnabled: boolean + favoritedMentors: string[] +} + +const filterGroupRankMentors = ( + mentors: RedProfile[], + filters: FiltersValues +): FilteredMentors => { + const filterFns = curriedFilterFunctions(filters) + const displayableMentors = mentors + .filter(filterFns.favorites) + .filter(filterFns.chosenMentoringTopics) + .filter(filterFns.chosenToolsAndFrameworks) + .filter(filterFns.chosenLocations) + .filter(filterFns.chosenDesiredRoles) + .filter(filterFns.mentorSharesLanguageWithMentee) + .filter(filterFns.menteeMentoringGoalCompatibleWithMentor) + + const groupedMentors = { + bestMatchMentors: [] as Array, + otherMentors: [] as Array, + } + + for (const mentor of displayableMentors) { + if (filterFns.isBestMatchMentor(mentor)) + groupedMentors.bestMatchMentors.push(mentor) + else groupedMentors.otherMentors.push(mentor) + } + + return groupedMentors +} + +const curriedFilterFunctions = (filters: FiltersValues) => { + const allMenteeRoleMentoringTopics = [ + ...filters.menteePrimaryRoleMentoringTopics, + ...filters.menteeSecondaryRoleMentoringTopics, + ] + + return { + favorites(mentor: RedProfile) { + if (!filters.favoritedMentorsEnabled) return true + return filters.favoritedMentors.includes(mentor.id) + }, + chosenMentoringTopics(mentor: RedProfile) { + if (filters.chosenMentoringTopics.length === 0) return true + return mentor.mentor_mentoringTopics.some((topic) => + filters.chosenMentoringTopics.includes(topic) + ) + }, + chosenToolsAndFrameworks(mentor: RedProfile) { + if (filters.chosenToolsAndFrameworks.length === 0) return true + return mentor.mentor_mentoringTopics.some((topic) => + filters.chosenToolsAndFrameworks.includes(topic) + ) + }, + chosenLocations(mentor: RedProfile) { + if (filters.chosenLocations.length === 0) return true + return filters.chosenLocations.includes(mentor.rediLocation) + }, + chosenDesiredRoles(mentor: RedProfile) { + if (filters.chosedDesiredRoles.length === 0) return true + return mentor.mentor_professionalExperienceFields.some((field) => + filters.chosedDesiredRoles.includes(field) + ) + }, + mentorSharesLanguageWithMentee(mentor: RedProfile) { + return ( + mentor.languages.some((lang) => + filters.menteeLanguages.includes(lang) + ) || [] + ) + }, + menteeMentoringGoalCompatibleWithMentor(mentor: RedProfile) { + switch (filters.menteeMentoringGoal) { + /** + * When mentee has one of the following goals from a mentorship, + * we filter the mentors with relevant professional experience with mentee's + * desired roles + */ + case 'tutoringInAParticularSkillTool': + case 'preparationForACertificationInterview': + case 'careerOrientatioPlanning': + if ( + !mentor.mentor_professionalExperienceFields.some((field) => + filters.menteeDesiredRoles.includes(field) + ) + ) + return false + + /** + * If the mentee has Primary or Secondary Skills selected, we match those + * with the mentor's mentoringTopics to filter mentors. If they are not selected + * we don't filter. + */ + case 'tutoringInAParticularSkillTool': + case 'preparationForACertificationInterview': + if ( + allMenteeRoleMentoringTopics.length > 0 && + !allMenteeRoleMentoringTopics.some((topic) => + mentor.mentor_mentoringTopics.includes(topic) + ) + ) + return false + + default: + return true + } + }, + isBestMatchMentor(mentor: RedProfile) { + switch (filters.menteeMentoringGoal) { + case 'tutoringInAParticularSkillTool': + case 'preparationForACertificationInterview': + return filters.menteeToolsAndFrameworksTopics.some((topic) => + mentor.mentor_mentoringTopics.includes(topic) + ) + + case 'careerOrientatioPlanning': + return allMenteeRoleMentoringTopics.some((topic) => + mentor.mentor_mentoringTopics.includes(topic) + ) + + case 'jobSearchAndApplicationProcess': + case 'buildingAProfessionalNetwork': + case 'entrepreneurshipAndFreelancing': + return allMenteeRoleMentoringTopics.some((topic) => + mentor.mentor_mentoringTopics.includes(topic) + ) + + default: + // Exhaustiveness check: we should never hit this clause + throw new Error( + `Unexpected mentee mentoring goal: ${filters.menteeMentoringGoal}` + ) + } + }, + } +} + +function ensureNoUndefinedArrayPropertiesInProfile(mentor: RedProfile) { + const keys = [ + 'languages', + 'categories', + 'favouritedRedProfileIds', + 'mentor_mentoringTopics', + 'mentor_mentoringGoals', + 'mentor_professionalExperienceFields', + 'mentee_mentoringGoal', + 'mentee_overarchingMentoringTopics', + 'mentee_primaryRole_mentoringTopics', + 'mentee_secondaryRole_mentoringTopics', + 'mentee_toolsAndFrameworks_mentoringTopics', + ] + + keys.forEach((key) => { + if (mentor[key] === undefined) { + mentor[key] = [] + } + }) + + return mentor +} + +function ensureNoUndefinedArrayPropertiesInProfiles(mentors: RedProfile[]) { + const fixedMentors = mentors.map(ensureNoUndefinedArrayPropertiesInProfile) + + return fixedMentors +} diff --git a/apps/redi-connect/src/pages/app/find-a-mentor/utils.ts b/apps/redi-connect/src/pages/app/find-a-mentor/utils.ts index 5c2d5363c..0455a27b3 100644 --- a/apps/redi-connect/src/pages/app/find-a-mentor/utils.ts +++ b/apps/redi-connect/src/pages/app/find-a-mentor/utils.ts @@ -1,4 +1,19 @@ +import { RedProfile } from '@talent-connect/shared-types' + export function toggleValueInArray(array: Array, value: T) { if (array.includes(value)) return array.filter((val) => val !== value) else return [...array, value] } + +// Fix for newly registered mentee users: +// Ensuring that a mentee filled their profile with required information before displaying list of mentors +const isString = (value: any) => typeof value === 'string' +const isFulfilledString = (string: any) => isString(string) && string.length > 0 + +const arrayIsNotEmpty = (array: any) => Array.isArray(array) && array.length > 0 +const isObjectValueFulfilled = (objectValue: any) => arrayIsNotEmpty(objectValue) || isFulfilledString(objectValue) + +export const ensureMenteeProfileIsComplete = ({ mentee_mentoringGoal, mentee_primaryRole_fieldOfExpertise }: RedProfile) => { + const isMenteeProfileComplete = isObjectValueFulfilled(mentee_mentoringGoal) && isObjectValueFulfilled(mentee_primaryRole_fieldOfExpertise) + return isMenteeProfileComplete +} diff --git a/apps/redi-connect/src/pages/app/me/Me.tsx b/apps/redi-connect/src/pages/app/me/Me.tsx index 381370e07..3467ac70a 100644 --- a/apps/redi-connect/src/pages/app/me/Me.tsx +++ b/apps/redi-connect/src/pages/app/me/Me.tsx @@ -1,28 +1,10 @@ +import { Loader } from '@talent-connect/shared-atomic-design-components' import React, { useEffect } from 'react' import { connect } from 'react-redux' import { RootState } from '../../../redux/types' import { profileFetchStart } from '../../../redux/user/actions' -import { Columns, Content, Element } from 'react-bulma-components' -import { - Heading, - Loader, -} from '@talent-connect/shared-atomic-design-components' -import { - Avatar, - EditableAbout, - EditableContactDetails, - EditableEducation, - EditableLanguages, - EditableMentoringTopics, - EditableOccupation, - EditablePersonalDetail, - EditableRediClass, - EditableSocialMedia, - EditableMenteeCount, -} from '../../../components/organisms' - -import { LoggedIn } from '../../../components/templates' -// CHECK OUT THE LOADER +import MeMenteeProfile from './me-mentee-profile/MeMenteeProfile' +import MeMentorProfile from './MeMentorProfile' const Me = ({ loading, saveResult, profileFetchStart, profile }: any) => { useEffect(() => { @@ -40,104 +22,10 @@ const Me = ({ loading, saveResult, profileFetchStart, profile }: any) => { profile.userType === 'public-sign-up-mentor-pending-review' return ( - - {saveResult === 'error' && <>An error occurred, please try again.} - {saveResult === 'submitting' && } - - - - - - - Hi, {profile.firstName} - - {`Please fill out your profile. Let potential ${ - userIsMentee ? 'mentors' : 'mentees' - } know a little bit more about you, so you can find the perfect fit. If you have filled out your profile: Great! Make sure you keep it up to date.`} - - - - - - {`Please fill out your profile. Let potential ${ - userIsMentee ? 'mentors' : 'mentees' - } know a little bit more about you, so you can find the perfect fit. If you have filled out your profile: Great! Make sure you keep it up to date.`} - - - - - - - - - - - {userIsMentor && ( - - - - - - - - )} - - - - - - - - - - - - - - - - - - - - - - - - - - - - {userIsMentee && ( - - - - - - - - - - - - - )} - - - - - - - - - + <> + {userIsMentee ? : null} + {userIsMentor ? : null} + ) } diff --git a/apps/redi-connect/src/pages/app/me/MeMentorProfile.tsx b/apps/redi-connect/src/pages/app/me/MeMentorProfile.tsx new file mode 100644 index 000000000..4ee570284 --- /dev/null +++ b/apps/redi-connect/src/pages/app/me/MeMentorProfile.tsx @@ -0,0 +1,134 @@ +import { + Heading, + Loader, +} from '@talent-connect/shared-atomic-design-components' +import React from 'react' +import { Columns, Content, Element } from 'react-bulma-components' +import { connect } from 'react-redux' +import { + Avatar, + EditableAbout, + EditableContactDetails, + EditableLanguages, + EditableMenteeCount, + EditableOccupation, + EditablePersonalDetail, + EditableSocialMedia, +} from '../../../components/organisms' +import EditableMentoringGoals from '../../../components/organisms/EditableMentoringGoals' +import EditableMentoringTopicsNew2022 from '../../../components/organisms/EditableMentoringTopicsNew2022' +import EditableProfessionalExperienceFields from '../../../components/organisms/EditableProfessionalExperienceFields' +import { LoggedIn } from '../../../components/templates' +import { RootState } from '../../../redux/types' +import { profileFetchStart } from '../../../redux/user/actions' + +const MeMentorProfile = ({ loading, saveResult, profile }: any) => { + if (loading) return + + return ( + + {saveResult === 'error' && <>An error occurred, please try again.} + {saveResult === 'submitting' && } + + + + + + + Hi, {profile.firstName} + + Please fill out your profile. Let potential mentees know a little + bit more about you, so you can find the perfect fit. If you have + filled out your profile: Great! Make sure you keep it up to date. + + + + + + Please fill out your profile. Let potential mentees know a little bit + more about you, so you can find the perfect fit. If you have filled + out your profile: Great! Make sure you keep it up to date. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ) +} + +const mapStateToProps = (state: RootState) => ({ + saveResult: state.user.saveResult, + loading: state.user.loading, + profile: state.user.profile, +}) + +const mapDispatchToProps = (dispatch: any) => ({ + profileFetchStart: () => dispatch(profileFetchStart()), +}) + +export default connect(mapStateToProps, mapDispatchToProps)(MeMentorProfile) diff --git a/apps/redi-connect/src/pages/app/me/me-mentee-profile/EditableMentoringGoalTopics.tsx b/apps/redi-connect/src/pages/app/me/me-mentee-profile/EditableMentoringGoalTopics.tsx new file mode 100644 index 000000000..1d809f3f3 --- /dev/null +++ b/apps/redi-connect/src/pages/app/me/me-mentee-profile/EditableMentoringGoalTopics.tsx @@ -0,0 +1,228 @@ +import { + Caption, + Editable, + FormSelect, +} from '@talent-connect/shared-atomic-design-components' +import { + DESIRED_ROLES, + MENTORING_GOALS, + MENTORING_TOPICS, +} from '@talent-connect/shared-config' +import { RedProfile } from '@talent-connect/shared-types' +import { objectEntries } from '@talent-connect/typescript-utilities' +import { FormikValues, useFormik } from 'formik' +import React, { useEffect } from 'react' +import { Columns, Element } from 'react-bulma-components' +import { connect } from 'react-redux' +import * as Yup from 'yup' +import { RootState } from '../../../../redux/types' +import { profileSaveStart } from '../../../../redux/user/actions' +import ReadMentoringGoalTopics from './ReadMentoringGoalTopics' + +type FormValues = Pick< + RedProfile, + | 'mentee_mentoringGoal' + | 'mentee_overarchingMentoringTopics' + | 'mentee_primaryRole_fieldOfExpertise' + | 'mentee_primaryRole_mentoringTopics' + | 'mentee_secondaryRole_fieldOfExpertise' + | 'mentee_secondaryRole_mentoringTopics' +> + +interface Props { + profile: RedProfile | undefined + profileSaveStart: Function +} + +const validationSchema = Yup.object({ + mentee_mentoringGoal: Yup.string() + .nullable() + .required('Please select a mentoring goal'), + mentee_primaryRole_fieldOfExpertise: Yup.string() + .nullable() + .required('Please select a role'), + mentee_overarchingMentoringTopics: Yup.array().max( + 3, + 'You can select up to three topics' + ), + mentee_primaryRole_mentoringTopics: Yup.array().max( + 3, + 'You can select up to three skills' + ), + mentee_secondaryRole_mentoringTopics: Yup.array().max( + 3, + 'You can select up to three skills' + ), +}) + +const EditableMentoringGoalTopics = ({ profile, profileSaveStart }: Props) => { + const { + id, + mentee_mentoringGoal, + mentee_overarchingMentoringTopics = [], + mentee_primaryRole_fieldOfExpertise, + mentee_primaryRole_mentoringTopics = [], + mentee_secondaryRole_fieldOfExpertise, + mentee_secondaryRole_mentoringTopics = [], + } = profile as RedProfile + + const submitForm = async (values: FormikValues) => { + const profileMentoring = values as Partial + profileSaveStart({ ...profileMentoring, id }) + } + + const initialValues: FormValues = { + mentee_mentoringGoal, + mentee_overarchingMentoringTopics, + mentee_primaryRole_fieldOfExpertise, + mentee_primaryRole_mentoringTopics, + mentee_secondaryRole_fieldOfExpertise, + mentee_secondaryRole_mentoringTopics, + } + + const formik = useFormik({ + initialValues, + enableReinitialize: true, + validationSchema, + onSubmit: submitForm, + }) + + useEffect(() => { + if (mentee_primaryRole_fieldOfExpertise) + formik.setFieldValue('mentee_primaryRole_mentoringTopics', []) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [formik.values.mentee_primaryRole_fieldOfExpertise]) + + useEffect(() => { + if (mentee_secondaryRole_fieldOfExpertise) + formik.setFieldValue('mentee_secondaryRole_mentoringTopics', []) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [formik.values.mentee_secondaryRole_fieldOfExpertise]) + + return ( + <> + + formik.handleSubmit()} + onClose={() => formik.resetForm()} + savePossible={formik.dirty && formik.isValid} + read={} + className="mentoring" + > + + + + Goal* + + + + Topics + + + + + + + + Primary role* + + + + Skills + + + + + + + + Secondary role + + + + Skills + + + + + + + ) +} + +const formMentoringGoals = objectEntries(MENTORING_GOALS).map( + ([value, label]) => ({ value, label }) +) +const formFieldsOfExpertise = objectEntries(DESIRED_ROLES).map( + ([value, label]) => ({ value, label }) +) + +const formMentoringTopicsInGroup = (group) => + MENTORING_TOPICS.filter((topic) => topic.group === group).map( + ({ id, label }) => ({ value: id, label }) + ) + +const mapStateToProps = (state: RootState) => ({ + profile: state.user.profile, +}) +const mapDispatchToProps = (dispatch: any) => ({ + profileSaveStart: (profile: Partial) => + dispatch(profileSaveStart(profile)), +}) + +export default connect( + mapStateToProps, + mapDispatchToProps +)(EditableMentoringGoalTopics) diff --git a/apps/redi-connect/src/pages/app/me/me-mentee-profile/EditableToolsAndFrameworks.tsx b/apps/redi-connect/src/pages/app/me/me-mentee-profile/EditableToolsAndFrameworks.tsx new file mode 100644 index 000000000..5821fa8dc --- /dev/null +++ b/apps/redi-connect/src/pages/app/me/me-mentee-profile/EditableToolsAndFrameworks.tsx @@ -0,0 +1,140 @@ +import { + Caption, + Checkbox, + Editable, + Placeholder, +} from '@talent-connect/shared-atomic-design-components' +import { MENTORING_TOPICS } from '@talent-connect/shared-config' +import { RedProfile } from '@talent-connect/shared-types' +import { FormikValues, useFormik } from 'formik' +import { chunk } from 'lodash' +import React from 'react' +import { Columns } from 'react-bulma-components' +import { connect } from 'react-redux' +import * as Yup from 'yup' +import { RootState } from '../../../../redux/types' +import { profileSaveStart } from '../../../../redux/user/actions' +import ReadToolsAndFrameworks from './ReadToolsAndFrameworks' + +type FormValues = Pick + +interface Props { + profile: RedProfile | undefined + profileSaveStart: Function +} + +const validationSchema = Yup.object({ + mentee_toolsAndFrameworks_mentoringTopics: Yup.array().max( + 3, + 'You can select up to 3 tools and frameworks' + ), +}) + +const EditableToolsAndFrameworks = ({ profile, profileSaveStart }: Props) => { + const { id, mentee_toolsAndFrameworks_mentoringTopics } = + profile as RedProfile + + const submitForm = async (values: FormikValues) => { + const profileMentoring = values as Partial + profileSaveStart({ ...profileMentoring, id }) + } + + const initialValues: FormValues = { + mentee_toolsAndFrameworks_mentoringTopics: + mentee_toolsAndFrameworks_mentoringTopics ?? [], + } + + const formik = useFormik({ + initialValues, + enableReinitialize: true, + validationSchema, + onSubmit: submitForm, + }) + + const selectedValues = formik.values.mentee_toolsAndFrameworks_mentoringTopics + + const onChange = (e: any) => { + e.persist() + const value = e.target.value + let newCategories + if (e.target.checked) { + newCategories = selectedValues.concat(value) + } else { + newCategories = selectedValues.filter((cat: any) => cat !== value) + } + formik.setFieldValue( + 'mentee_toolsAndFrameworks_mentoringTopics', + newCategories + ) + formik.setFieldTouched( + 'mentee_toolsAndFrameworks_mentoringTopics', + true, + false + ) + } + + return ( + <> + + formik.handleSubmit()} + onClose={() => formik.resetForm()} + savePossible={formik.dirty && formik.isValid} + read={} + className="mentoring" + > + + Please select tools and technologies you are particularly interested in + (max 3). + + + {formToolsAndFrameworksGroups.map((group) => ( + + {group.map((item) => ( + = 3 && + !selectedValues.includes(item.value) + } + {...formik} + > + {item.label} + + ))} + + ))} + + + + ) +} + +const formToolsAndFrameworks = MENTORING_TOPICS.filter( + (topic) => topic.group === 'toolsAndFrameworks' +) + .map(({ id, label }) => ({ value: id, label })) + .sort((a, b) => (a.label > b.label ? 1 : -1)) +const formToolsAndFrameworksGroups = chunk( + formToolsAndFrameworks, + Math.ceil(formToolsAndFrameworks.length / 3) +) + +const mapStateToProps = (state: RootState) => ({ + profile: state.user.profile, +}) +const mapDispatchToProps = (dispatch: any) => ({ + profileSaveStart: (profile: Partial) => + dispatch(profileSaveStart(profile)), +}) + +export default connect( + mapStateToProps, + mapDispatchToProps +)(EditableToolsAndFrameworks) diff --git a/apps/redi-connect/src/pages/app/me/me-mentee-profile/MeMenteeProfile.tsx b/apps/redi-connect/src/pages/app/me/me-mentee-profile/MeMenteeProfile.tsx new file mode 100644 index 000000000..5ff182a16 --- /dev/null +++ b/apps/redi-connect/src/pages/app/me/me-mentee-profile/MeMenteeProfile.tsx @@ -0,0 +1,134 @@ +import { + Caption, + Heading, + Loader, +} from '@talent-connect/shared-atomic-design-components' +import React from 'react' +import { Columns, Content, Element } from 'react-bulma-components' +import { connect } from 'react-redux' +import { + Avatar, + EditableAbout, + EditableContactDetails, + EditableEducation, + EditableLanguages, + EditableOccupation, + EditablePersonalDetail, + EditableRediClass, + EditableSocialMedia, +} from '../../../../components/organisms' +import { LoggedIn } from '../../../../components/templates' +import { RootState } from '../../../../redux/types' +import { profileFetchStart } from '../../../../redux/user/actions' +import EditableMentoringGoalTopics from './EditableMentoringGoalTopics' +import EditableToolsAndFrameworks from './EditableToolsAndFrameworks' + +const MeMenteeProfile = ({ loading, saveResult, profile }: any) => { + if (loading) return + + return ( + + {saveResult === 'error' && <>An error occurred, please try again.} + {saveResult === 'submitting' && } + + + + + + + Hi, {profile.firstName} + + Please fill out your profile. Let potential mentors know a little + bit more about you, so you can find the perfect fit. If you have + filled out your profile: Great! Make sure you keep it up to date. + + + + + + Please fill out your profile. Let potential mentors know a little bit + more about you, so you can find the perfect fit. If you have filled + out your profile: Great! Make sure you keep it up to date. + + + + + + + + Professional experience and contact details + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ) +} + +const mapStateToProps = (state: RootState) => ({ + saveResult: state.user.saveResult, + loading: state.user.loading, + profile: state.user.profile, +}) + +const mapDispatchToProps = (dispatch: any) => ({ + profileFetchStart: () => dispatch(profileFetchStart()), +}) + +export default connect(mapStateToProps, mapDispatchToProps)(MeMenteeProfile) diff --git a/apps/redi-connect/src/pages/app/me/me-mentee-profile/ReadMentoringGoalTopics.tsx b/apps/redi-connect/src/pages/app/me/me-mentee-profile/ReadMentoringGoalTopics.tsx new file mode 100644 index 000000000..7dcf906c2 --- /dev/null +++ b/apps/redi-connect/src/pages/app/me/me-mentee-profile/ReadMentoringGoalTopics.tsx @@ -0,0 +1,137 @@ +import { + Caption, + CardTags, + Placeholder, +} from '@talent-connect/shared-atomic-design-components' +import { + FIELDS_OF_EXPERTISE, + MENTORING_GOALS, + MENTORING_TOPICS_MAP, +} from '@talent-connect/shared-config' +import { RedProfile } from '@talent-connect/shared-types' +import React from 'react' +import { Columns, Element } from 'react-bulma-components' +import { connect } from 'react-redux' +import { RootState } from '../../../../redux/types' + +interface ReadMentoringProps { + profile: RedProfile + caption?: boolean +} + +const ReadMentoringGoalTopics = ({ profile, caption }: ReadMentoringProps) => { + const { + mentee_mentoringGoal, + mentee_overarchingMentoringTopics = [], + mentee_primaryRole_fieldOfExpertise, + mentee_primaryRole_mentoringTopics = [], + mentee_secondaryRole_fieldOfExpertise, + mentee_secondaryRole_mentoringTopics = [], + } = profile + + return ( + <> + + + + Goal + {mentee_mentoringGoal ? ( + + ) : ( + + The most important goal to address with your + mentor. + + )} + + + Topics + {mentee_overarchingMentoringTopics.length > 0 ? ( + MENTORING_TOPICS_MAP[item]} + /> + ) : ( + + General topics you would like to be mentored on. + + )} + + + + + + + Primary role + {mentee_primaryRole_fieldOfExpertise ? ( + + ) : ( + + The primary role you would like to be mentored on. + + )} + + + Skills + {mentee_primaryRole_mentoringTopics.length > 0 ? ( + MENTORING_TOPICS_MAP[item]} + /> + ) : ( + + Role-related skills you would like to be mentored on. + + )} + + + + + + + Secondary role + {mentee_secondaryRole_fieldOfExpertise ? ( + + ) : ( + + The secondary role you would like to be mentored on. + + )} + + + Skills + {mentee_secondaryRole_mentoringTopics.length > 0 ? ( + MENTORING_TOPICS_MAP[item]} + /> + ) : ( + + Role-related skills you would like to be mentored on. + + )} + + + + + ) +} + +const mapStateToProps = (state: RootState) => ({ + profile: state.user.profile as RedProfile, +}) + +export default { + Me: connect(mapStateToProps, {})(ReadMentoringGoalTopics), + Some: ({ profile }: ReadMentoringProps) => ( + + ), +} diff --git a/apps/redi-connect/src/pages/app/me/me-mentee-profile/ReadToolsAndFrameworks.tsx b/apps/redi-connect/src/pages/app/me/me-mentee-profile/ReadToolsAndFrameworks.tsx new file mode 100644 index 000000000..98928f2cc --- /dev/null +++ b/apps/redi-connect/src/pages/app/me/me-mentee-profile/ReadToolsAndFrameworks.tsx @@ -0,0 +1,46 @@ +import { + CardTags, + Placeholder, +} from '@talent-connect/shared-atomic-design-components' +import { MENTORING_TOPICS_MAP } from '@talent-connect/shared-config' +import { RedProfile } from '@talent-connect/shared-types' +import React from 'react' +import { Element } from 'react-bulma-components' +import { connect } from 'react-redux' +import { RootState } from '../../../../redux/types' + +interface ReadMentoringProps { + profile: RedProfile + caption?: boolean +} + +const ReadToolsAndFrameworks = ({ profile, caption }: ReadMentoringProps) => { + const { mentee_toolsAndFrameworks_mentoringTopics = [] } = profile + + return ( + + {mentee_toolsAndFrameworks_mentoringTopics.length > 0 ? ( + MENTORING_TOPICS_MAP[item]} + /> + ) : ( + + Please select tools and technologies you are particularly interested in + (max 3). + + )} + + ) +} + +const mapStateToProps = (state: RootState) => ({ + profile: state.user.profile as RedProfile, +}) + +export default { + Me: connect(mapStateToProps, {})(ReadToolsAndFrameworks), + Some: ({ profile }: ReadMentoringProps) => ( + + ), +} diff --git a/apps/redi-connect/src/redux/matches/epics.ts b/apps/redi-connect/src/redux/matches/epics.ts index 3dee161dc..5c2334ddc 100644 --- a/apps/redi-connect/src/redux/matches/epics.ts +++ b/apps/redi-connect/src/redux/matches/epics.ts @@ -96,7 +96,6 @@ export const matchesAcceptMentorshipEpic = (action$: ActionsObservable) => export const matchesDeclineMentorshipEpic = (action$: ActionsObservable) => action$.pipe( - tap((p) => console.log('Hello hello', p)), ofType(MatchesActionType.MATCHES_DECLINE_MENTORSHIP_START), switchMap((action) => { const request = from( diff --git a/apps/redi-connect/src/services/api/api.tsx b/apps/redi-connect/src/services/api/api.tsx index 48853f9e1..93ddc6894 100644 --- a/apps/redi-connect/src/services/api/api.tsx +++ b/apps/redi-connect/src/services/api/api.tsx @@ -129,25 +129,11 @@ export interface RedProfileFilters { export const getProfiles = ({ userType, - categories, - languages, - locations, nameQuery, }: RedProfileFilters): Promise => { - const filterLanguages = - languages && languages.length !== 0 ? { inq: languages } : undefined - const filterCategories = - categories && categories.length !== 0 ? { inq: categories } : undefined - const filterLocations = - locations && locations.length !== 0 ? { inq: locations } : undefined - return http( `${API_URL}/redProfiles?filter=${JSON.stringify({ where: { - // loopbackComputedDoNotSetElsewhere__forAdminSearch__fullName: { - // like: 'Carlotta3', - // options: 'i', - // }, and: [ ...String(nameQuery) .split(' ') @@ -158,9 +144,6 @@ export const getProfiles = ({ }, })), { userType }, - { languages: filterLanguages }, - { categories: filterCategories }, - { rediLocation: filterLocations }, { userActivated: true }, ], }, @@ -170,17 +153,9 @@ export const getProfiles = ({ ).then((resp) => resp.data) } -export const getMentors = ({ - categories, - languages, - locations, - nameQuery, -}: Partial) => +export const getMentors = ({ nameQuery }: Partial) => getProfiles({ userType: 'mentor', - categories, - languages, - locations, nameQuery, }) diff --git a/apps/redi-connect/src/styles/_global.scss b/apps/redi-connect/src/styles/_global.scss index 8e1cdbc18..9cc13d8e1 100644 --- a/apps/redi-connect/src/styles/_global.scss +++ b/apps/redi-connect/src/styles/_global.scss @@ -16,3 +16,35 @@ border-bottom: 1px solid $grey-light; } } + +.block-thicker-separator { + .block-thicker-separator { + @include mobile() { + padding-bottom: 1.5rem; + margin-bottom: -0.5rem; + border-bottom: 2px solid $grey-light; + } + } + + &:not(:last-child) { + padding-bottom: 1.5rem; + margin-bottom: 1.5rem; + border-bottom: 2px solid $grey-light; + } +} + +.block-thicker-separator-dashed { + .block-thicker-separator-dashed { + @include mobile() { + padding-bottom: 1.5rem; + margin-bottom: -0.5rem; + border-bottom: 2px dashed $grey-light; + } + } + + &:not(:last-child) { + padding-bottom: 1.5rem; + margin-bottom: 1.5rem; + border-bottom: 2px dashed $grey-light; + } +} diff --git a/libs/shared-atomic-design-components/src/lib/atoms/Caption.tsx b/libs/shared-atomic-design-components/src/lib/atoms/Caption.tsx index 22ad04df8..8407d593e 100644 --- a/libs/shared-atomic-design-components/src/lib/atoms/Caption.tsx +++ b/libs/shared-atomic-design-components/src/lib/atoms/Caption.tsx @@ -2,10 +2,13 @@ import React from 'react' import { Heading as BulmaHeading } from 'react-bulma-components' import './Caption.scss' -const Caption: React.FunctionComponent = ({ children }) => ( +const Caption: React.FunctionComponent<{ bold?: boolean }> = ({ + bold = false, + children, +}) => ( void onClose: () => void read: React.ReactNode @@ -17,6 +18,7 @@ interface Props { function Editable(props: Props) { const { title, + titleBold, children, read, onSave, @@ -40,7 +42,7 @@ function Editable(props: Props) { return (
- {title} + {title}
{isEditing ? ( <> diff --git a/libs/shared-atomic-design-components/src/lib/styles/_global.scss b/libs/shared-atomic-design-components/src/lib/styles/_global.scss index 8e1cdbc18..9cc13d8e1 100644 --- a/libs/shared-atomic-design-components/src/lib/styles/_global.scss +++ b/libs/shared-atomic-design-components/src/lib/styles/_global.scss @@ -16,3 +16,35 @@ border-bottom: 1px solid $grey-light; } } + +.block-thicker-separator { + .block-thicker-separator { + @include mobile() { + padding-bottom: 1.5rem; + margin-bottom: -0.5rem; + border-bottom: 2px solid $grey-light; + } + } + + &:not(:last-child) { + padding-bottom: 1.5rem; + margin-bottom: 1.5rem; + border-bottom: 2px solid $grey-light; + } +} + +.block-thicker-separator-dashed { + .block-thicker-separator-dashed { + @include mobile() { + padding-bottom: 1.5rem; + margin-bottom: -0.5rem; + border-bottom: 2px dashed $grey-light; + } + } + + &:not(:last-child) { + padding-bottom: 1.5rem; + margin-bottom: 1.5rem; + border-bottom: 2px dashed $grey-light; + } +} diff --git a/libs/shared-config/src/index.ts b/libs/shared-config/src/index.ts index 92aae0c0a..814336830 100644 --- a/libs/shared-config/src/index.ts +++ b/libs/shared-config/src/index.ts @@ -1 +1,2 @@ export * from './lib/config' +export * from './lib/mentoring-data-lists' diff --git a/libs/shared-config/src/lib/config.ts b/libs/shared-config/src/lib/config.ts index 8573729ce..f4e76d0d2 100644 --- a/libs/shared-config/src/lib/config.ts +++ b/libs/shared-config/src/lib/config.ts @@ -5,152 +5,6 @@ export const REDI_LOCATION_NAMES = { nrw: 'NRW', } as const -export const CATEGORY_GROUPS = { - softwareEngineering: '๐Ÿ‘ฉโ€๐Ÿ’ป Software Engineering', - design: '๐ŸŽจ Design', - otherProfessions: '๐Ÿ„โ€โ™€๏ธ Other Professions', - careerSupport: 'โœ‹ Career Support', - language: '๐Ÿ—ฃ๏ธ Language Support', - other: '๐Ÿค— Other', -} as const - -export const CATEGORIES = [ - { - id: 'basicProgrammingSkills', - label: 'Basic programming skills', - group: 'softwareEngineering', - }, - { id: 'htmlCss', label: 'HTML & CSS', group: 'softwareEngineering' }, - { id: 'javascript', label: 'Javascript', group: 'softwareEngineering' }, - { id: 'react', label: 'React', group: 'softwareEngineering' }, - { id: 'java', label: 'Java', group: 'softwareEngineering' }, - { id: 'python', label: 'Python', group: 'softwareEngineering' }, - { - id: 'dataAnalytics', - label: 'Data Analytics', - group: 'softwareEngineering', - }, - { - id: 'machineLearning', - label: 'Machine Learning', - group: 'softwareEngineering', - }, - { - id: 'mobileDevelopmentIos', - label: 'iOS Mobile Development', - group: 'softwareEngineering', - }, - { - id: 'mobileDevelopmentAndroid', - label: 'Android Mobile Development', - group: 'softwareEngineering', - }, - { id: 'salesforce', label: 'Salesforce', group: 'softwareEngineering' }, - { - id: 'devOpsCloud', - label: 'DevOps and Cloud (e.g. Azure, AWS)', - group: 'softwareEngineering', - }, - { id: 'iot', label: 'IoT', group: 'softwareEngineering' }, - { - id: 'computerNetworking', - label: 'Computer Networking', - group: 'softwareEngineering', - }, - { id: 'blockchain', label: 'Blockchain', group: 'softwareEngineering' }, - { - id: 'productManagement', - label: 'Product Management', - group: 'otherProfessions', - }, - { - id: 'projectManagement', - label: 'Project Management', - group: 'otherProfessions', - }, - { - id: 'digitalMarketing', - label: 'Digital Marketing', - group: 'otherProfessions', - }, - { - id: 'businessDevelopment', - label: 'Business Development', - group: 'otherProfessions', - }, - { id: 'sales', label: 'Sales', group: 'otherProfessions' }, - { - id: 'qualityAssurance', - label: 'Quality Assurance', - group: 'otherProfessions', - }, - { id: 'basicGerman', label: 'Basic German ๐Ÿ‡ฉ๐Ÿ‡ช', group: 'language' }, - { id: 'businessGerman', label: 'Business German ๐Ÿ‡ฉ๐Ÿ‡ช', group: 'language' }, - { id: 'english', label: 'English ๐Ÿ‡ฌ๐Ÿ‡ง', group: 'language' }, - { id: 'graphicDesign', label: 'Graphic Design', group: 'design' }, - { - id: 'userInterfaceDesign', - label: 'User Interface Design', - group: 'design', - }, - { - id: 'userExperienceDesign', - label: 'User Experience Design', - group: 'design', - }, - { - id: 'motivationAndEncouragement', - label: 'Motivation & encouragement', - group: 'other', - }, - { id: 'friendAndHelp', label: 'Be a friend and help', group: 'other' }, - { id: 'dontKnowYet', label: "I don't know yet", group: 'other' }, - { - id: 'careerOrientationAndPlanning', - label: 'Career orientation & planning', - group: 'careerSupport', - }, - { - id: 'internshipOrWorkingStudent', - label: 'Internship / working student position search', - group: 'careerSupport', - }, - { id: 'jobSearch', label: 'Job search', group: 'careerSupport' }, - { - id: 'jobApplicationsCvPreparationEnglish', - label: 'Job applications and CV preparation in English', - group: 'careerSupport', - }, - { - id: 'jobApplicationsCvPreparationGerman', - label: 'Job applications and CV preparation in German', - group: 'careerSupport', - }, - { - id: 'interviewPreparation', - label: 'Interview preparation', - group: 'careerSupport', - }, - { - id: 'codingChallengePreparation', - label: 'Coding challenge preparation', - group: 'careerSupport', - }, - { - id: 'buildingProfessionalNetwork', - label: 'Building a professional network', - group: 'careerSupport', - }, - { id: 'entrepreneurship', label: 'Entrepreneurship', group: 'careerSupport' }, - { id: 'freelancing', label: 'Freelancing', group: 'careerSupport' }, -] as const -export type CategoryKey = typeof CATEGORIES[number]['id'] -export type CategoryLabel = typeof CATEGORIES[number]['label'] - -export const CATEGORIES_MAP = Object.fromEntries( - CATEGORIES.map((cat) => [cat.id, cat.label]) -) as Record - export const LANGUAGES = [ 'Afrikaans', 'Albanian', diff --git a/libs/shared-config/src/lib/mentoring-data-lists.ts b/libs/shared-config/src/lib/mentoring-data-lists.ts new file mode 100644 index 000000000..0db11ddbb --- /dev/null +++ b/libs/shared-config/src/lib/mentoring-data-lists.ts @@ -0,0 +1,578 @@ +export const MENTORING_GOALS = { + buildingAProfessionalNetwork: 'Building a professional network', + jobSearchAndApplicationProcess: 'Job search and application process', + entrepreneurshipAndFreelancing: 'Entrepreneurship and freelancing', + tutoringInAParticularSkillTool: 'Tutoring in a particular skill / tool', + careerOrientatioPlanning: 'Career orientation & planning', + preparationForACertificationInterview: + 'Preparation for a certification / interview', +} as const + +export type MentoringGoalKey = keyof typeof MENTORING_GOALS + +export const DESIRED_ROLES = { + marketingSocialMediaAndSales: 'Marketing, social media and sales', + productAndProjectManagement: 'Product and project management', + requirementsAnalysisAndResearch: 'Requirements analysis and research', + softwareDevelopment: 'Software development', + uxAndUiDesign: 'UX and UI design', + testingAndQualityAssurance: 'Testing and Quality Assurance', + other: 'Other', +} as const + +export type DesiredRolesKey = keyof typeof FIELDS_OF_EXPERTISE + +export const FIELDS_OF_EXPERTISE = { + humanResourcesAndRecruiting: 'Human resources and recruiting', + ...DESIRED_ROLES, +} as const + +export type FieldOfExperienceKey = keyof typeof FIELDS_OF_EXPERTISE + +export const MENTORING_TOPIC_GROUPS = { + overarchingTopics: '๐ŸŒˆ Overarching topics', + marketingSocialMediaAndSales: '๐Ÿšฆ Marketing, social media and sales', + requirementsAnalysisAndResearch: '๐Ÿงช Requirements analysis and research', + uxAndUiDesign: '๐ŸŽจ UX and UI design', + testingAndQualityAssurance: '๐ŸŽ๏ธ Testing and Quality Assurance', + productAndProjectManagement: '๐Ÿ“‹ Product and project management', + softwareDevelopment: '๐Ÿ‘ฉโ€๐Ÿ’ป Software development', + toolsAndFrameworks: '๐Ÿ› ๏ธ Tools and frameworks', +} as const + +/* +overarchingTopics +productAndProjectManagement +softwareDevelopment +*/ + +export const MENTORING_TOPICS = [ + { + id: 'Application process and portfolio', + label: 'Application process and portfolio', + group: 'overarchingTopics', + }, + { id: 'Communication', label: 'Communication', group: 'overarchingTopics' }, + { + id: 'Cross-cultural teams', + label: 'Cross-cultural teams', + group: 'overarchingTopics', + }, + { + id: 'Cross-functional work', + label: 'Cross-functional work', + group: 'overarchingTopics', + }, + { id: 'Facilitation', label: 'Facilitation', group: 'overarchingTopics' }, + { + id: 'Giving / receiving feedback', + label: 'Giving / receiving feedback', + group: 'overarchingTopics', + }, + { + id: 'Self-organisation', + label: 'Self-organisation', + group: 'overarchingTopics', + }, + { + id: 'Social network profile tuning', + label: 'Social network profile tuning', + group: 'overarchingTopics', + }, + { + id: 'Storytelling and presentation', + label: 'Storytelling and presentation', + group: 'overarchingTopics', + }, + { + id: 'Team leadership', + label: 'Team leadership', + group: 'overarchingTopics', + }, + { + id: 'Time management', + label: 'Time management', + group: 'overarchingTopics', + }, + { + id: 'Marketing and social media', + label: 'Marketing and social media', + group: 'marketingSocialMediaAndSales', + }, + { id: 'Sales', label: 'Sales', group: 'marketingSocialMediaAndSales' }, + { + id: 'Data analysis', + label: 'Data analysis', + group: 'requirementsAnalysisAndResearch', + }, + { + id: 'Design research', + label: 'Design research', + group: 'requirementsAnalysisAndResearch', + }, + { + id: 'Design Thinking', + label: 'Design Thinking', + group: 'requirementsAnalysisAndResearch', + }, + { + id: 'Domain driven design', + label: 'Domain driven design', + group: 'requirementsAnalysisAndResearch', + }, + { + id: 'Mapping customer experience', + label: 'Mapping customer experience', + group: 'requirementsAnalysisAndResearch', + }, + { + id: 'Prioritisation metrics', + label: 'Prioritisation metrics', + group: 'requirementsAnalysisAndResearch', + }, + { + id: 'Process modelling', + label: 'Process modelling', + group: 'requirementsAnalysisAndResearch', + }, + { + id: 'Software Development Life Cycle', + label: 'Software Development Life Cycle', + group: 'requirementsAnalysisAndResearch', + }, + { + id: 'Technical writing', + label: 'Technical writing', + group: 'requirementsAnalysisAndResearch', + }, + { + id: 'User story mapping', + label: 'User story mapping', + group: 'requirementsAnalysisAndResearch', + }, + { + id: 'Qualitative User Research', + label: 'Qualitative User Research', + group: 'requirementsAnalysisAndResearch', + }, + { + id: 'Quantitative User Research', + label: 'Quantitative User Research', + group: 'requirementsAnalysisAndResearch', + }, + { id: 'Animation', label: 'Animation', group: 'uxAndUiDesign' }, + { id: 'Branding', label: 'Branding', group: 'uxAndUiDesign' }, + { + id: 'General user experience', + label: 'General user experience', + group: 'uxAndUiDesign', + }, + { id: 'Graphic Design', label: 'Graphic Design', group: 'uxAndUiDesign' }, + { + id: 'Information architecture', + label: 'Information architecture', + group: 'uxAndUiDesign', + }, + { + id: 'Interaction design', + label: 'Interaction design', + group: 'uxAndUiDesign', + }, + { + id: 'Prototyping and wireframing', + label: 'Prototyping and wireframing', + group: 'uxAndUiDesign', + }, + { + id: 'Responsive design', + label: 'Responsive design', + group: 'uxAndUiDesign', + }, + { + id: 'Typography / color theory', + label: 'Typography / color theory', + group: 'uxAndUiDesign', + }, + { + id: 'Visual communication', + label: 'Visual communication', + group: 'uxAndUiDesign', + }, + { + id: 'Automated software testing', + label: 'Automated software testing', + group: 'testingAndQualityAssurance', + }, + { + id: 'Behavior-driven development', + label: 'Behavior-driven development', + group: 'testingAndQualityAssurance', + }, + { + id: 'Cross-browser testing', + label: 'Cross-browser testing', + group: 'testingAndQualityAssurance', + }, + { + id: 'Functional Testing', + label: 'Functional Testing', + group: 'testingAndQualityAssurance', + }, + { + id: 'Software testing methodologies', + label: 'Software testing methodologies', + group: 'testingAndQualityAssurance', + }, + { + id: 'Test-driven development', + label: 'Test-driven development', + group: 'testingAndQualityAssurance', + }, + { + id: 'Test planning', + label: 'Test planning', + group: 'testingAndQualityAssurance', + }, + { + id: 'Test management', + label: 'Test management', + group: 'testingAndQualityAssurance', + }, + { + id: 'Usability testing', + label: 'Usability testing', + group: 'testingAndQualityAssurance', + }, + { + id: 'Agile frameworks', + label: 'Agile frameworks', + group: 'productAndProjectManagement', + }, + { + id: 'Budget calculation', + label: 'Budget calculation', + group: 'productAndProjectManagement', + }, + { + id: 'Business development', + label: 'Business development', + group: 'productAndProjectManagement', + }, + { + id: 'Change management', + label: 'Change management', + group: 'productAndProjectManagement', + }, + { + id: 'Enterprise architecture management', + label: 'Enterprise architecture management', + group: 'productAndProjectManagement', + }, + { + id: 'Market research', + label: 'Market research', + group: 'productAndProjectManagement', + }, + { + id: 'Planning and roadmaps', + label: 'Planning and roadmaps', + group: 'productAndProjectManagement', + }, + { + id: 'Prioritisation metrics', + label: 'Prioritisation metrics', + group: 'productAndProjectManagement', + }, + { + id: 'Product Backlog management', + label: 'Product Backlog management', + group: 'productAndProjectManagement', + }, + { + id: 'Product management', + label: 'Product management', + group: 'productAndProjectManagement', + }, + { + id: 'Project management', + label: 'Project management', + group: 'productAndProjectManagement', + }, + { + id: 'Project management office', + label: 'Project management office', + group: 'productAndProjectManagement', + }, + { + id: 'Value-driven product development', + label: 'Value-driven product development', + group: 'productAndProjectManagement', + }, + { + id: 'Value metrics', + label: 'Value metrics', + group: 'productAndProjectManagement', + }, + { + id: 'Front-end development', + label: 'Front-end Development', + group: 'softwareDevelopment', + }, + { + id: 'Back-end development', + label: 'Back-end Development', + group: 'softwareDevelopment', + }, + { + id: 'Android Mobile Development', + label: 'Android Mobile Development', + group: 'softwareDevelopment', + }, + { + id: 'Basic programming skills', + label: 'Basic programming skills', + group: 'softwareDevelopment', + }, + { + id: 'Computer networking', + label: 'Computer networking', + group: 'softwareDevelopment', + }, + { + id: 'Cross-browser development', + label: 'Cross-browser development', + group: 'softwareDevelopment', + }, + { + id: 'Data analytics', + label: 'Data analytics', + group: 'softwareDevelopment', + }, + { + id: 'Database management', + label: 'Database management', + group: 'softwareDevelopment', + }, + { + id: 'DevOps and Cloud', + label: 'DevOps and Cloud', + group: 'softwareDevelopment', + }, + { + id: 'Hybrid App Development', + label: 'Hybrid App Development', + group: 'softwareDevelopment', + }, + { + id: 'Hardware and networks', + label: 'Hardware and networks', + group: 'softwareDevelopment', + }, + { id: 'IoT', label: 'IoT', group: 'softwareDevelopment' }, + { + id: 'iOS Mobile Development', + label: 'iOS Mobile Development', + group: 'softwareDevelopment', + }, + { + id: 'Machine learning', + label: 'Machine learning', + group: 'softwareDevelopment', + }, + { id: 'Security', label: 'Security', group: 'softwareDevelopment' }, + { + id: 'Atlassian Jira', + label: 'Atlassian Jira', + group: 'toolsAndFrameworks', + }, + { id: 'AWS', label: 'AWS', group: 'toolsAndFrameworks' }, + { id: 'Azure', label: 'Azure', group: 'toolsAndFrameworks' }, + { + id: 'Balsamiq Mockup', + label: 'Balsamiq Mockup', + group: 'toolsAndFrameworks', + }, + { id: 'Blockchain', label: 'Blockchain', group: 'toolsAndFrameworks' }, + { id: 'Cucumber', label: 'Cucumber', group: 'toolsAndFrameworks' }, + { id: 'Cypress', label: 'Cypress', group: 'toolsAndFrameworks' }, + { id: 'Docker', label: 'Docker', group: 'toolsAndFrameworks' }, + { id: 'Figma', label: 'Figma', group: 'toolsAndFrameworks' }, + { id: 'Flutter', label: 'Flutter', group: 'toolsAndFrameworks' }, + { id: 'GCP', label: 'GCP', group: 'toolsAndFrameworks' }, + { id: 'Git', label: 'Git', group: 'toolsAndFrameworks' }, + { id: 'Gherkin', label: 'Gherkin', group: 'toolsAndFrameworks' }, + { id: 'HTML & CSS', label: 'HTML & CSS', group: 'toolsAndFrameworks' }, + { id: 'Java', label: 'Java', group: 'toolsAndFrameworks' }, + { id: 'JavaScript', label: 'JavaScript', group: 'toolsAndFrameworks' }, + { id: 'Jest', label: 'Jest', group: 'toolsAndFrameworks' }, + { id: 'MongoDB', label: 'MongoDB', group: 'toolsAndFrameworks' }, + { id: 'MySQL', label: 'MySQL', group: 'toolsAndFrameworks' }, + { id: 'NodeJS', label: 'NodeJS', group: 'toolsAndFrameworks' }, + { id: 'Python', label: 'Python', group: 'toolsAndFrameworks' }, + { id: 'React', label: 'React', group: 'toolsAndFrameworks' }, + { id: 'React Native', label: 'React Native', group: 'toolsAndFrameworks' }, + { id: "REST API's", label: "REST API's", group: 'toolsAndFrameworks' }, + { id: 'Salesforce', label: 'Salesforce', group: 'toolsAndFrameworks' }, + { id: 'Selenium', label: 'Selenium', group: 'toolsAndFrameworks' }, + { id: 'Sketch', label: 'Sketch', group: 'toolsAndFrameworks' }, + { id: 'SQL', label: 'SQL', group: 'toolsAndFrameworks' }, + { id: 'Xray', label: 'Xray', group: 'toolsAndFrameworks' }, + { id: 'Zephyr', label: 'Zephyr', group: 'toolsAndFrameworks' }, + { id: 'VMWare', label: 'VMWare', group: 'toolsAndFrameworks' }, + { id: 'Virtual Box', label: 'Virtual Box', group: 'toolsAndFrameworks' }, +] as const + +export type MentoringTopicKey = typeof MENTORING_TOPICS[number]['id'] +export type MentoringTopicLabel = typeof MENTORING_TOPICS[number]['label'] + +export const MENTORING_TOPICS_MAP = Object.fromEntries( + MENTORING_TOPICS.map((topic) => [topic.id, topic.label]) +) as Record + +// TODO: these are the **old** 'categories' (i.e. mentoring topics). To be deleted after +// we migrate to the above! +export const CATEGORY_GROUPS = { + softwareEngineering: '๐Ÿ‘ฉโ€๐Ÿ’ป Software Engineering', + design: '๐ŸŽจ Design', + otherProfessions: '๐Ÿ„โ€โ™€๏ธ Other Professions', + careerSupport: 'โœ‹ Career Support', + language: '๐Ÿ—ฃ๏ธ Language Support', + other: '๐Ÿค— Other', +} as const + +export const CATEGORIES = [ + { + id: 'basicProgrammingSkills', + label: 'Basic programming skills', + group: 'softwareEngineering', + }, + { id: 'htmlCss', label: 'HTML & CSS', group: 'softwareEngineering' }, + { id: 'javascript', label: 'Javascript', group: 'softwareEngineering' }, + { id: 'react', label: 'React', group: 'softwareEngineering' }, + { id: 'java', label: 'Java', group: 'softwareEngineering' }, + { id: 'python', label: 'Python', group: 'softwareEngineering' }, + { + id: 'dataAnalytics', + label: 'Data Analytics', + group: 'softwareEngineering', + }, + { + id: 'machineLearning', + label: 'Machine Learning', + group: 'softwareEngineering', + }, + { + id: 'mobileDevelopmentIos', + label: 'iOS Mobile Development', + group: 'softwareEngineering', + }, + { + id: 'mobileDevelopmentAndroid', + label: 'Android Mobile Development', + group: 'softwareEngineering', + }, + { id: 'salesforce', label: 'Salesforce', group: 'softwareEngineering' }, + { + id: 'devOpsCloud', + label: 'DevOps and Cloud (e.g. Azure, AWS)', + group: 'softwareEngineering', + }, + { id: 'iot', label: 'IoT', group: 'softwareEngineering' }, + { + id: 'computerNetworking', + label: 'Computer Networking', + group: 'softwareEngineering', + }, + { id: 'blockchain', label: 'Blockchain', group: 'softwareEngineering' }, + { + id: 'productManagement', + label: 'Product Management', + group: 'otherProfessions', + }, + { + id: 'projectManagement', + label: 'Project Management', + group: 'otherProfessions', + }, + { + id: 'digitalMarketing', + label: 'Digital Marketing', + group: 'otherProfessions', + }, + { + id: 'businessDevelopment', + label: 'Business Development', + group: 'otherProfessions', + }, + { id: 'sales', label: 'Sales', group: 'otherProfessions' }, + { + id: 'qualityAssurance', + label: 'Quality Assurance', + group: 'otherProfessions', + }, + { id: 'basicGerman', label: 'Basic German ๐Ÿ‡ฉ๐Ÿ‡ช', group: 'language' }, + { id: 'businessGerman', label: 'Business German ๐Ÿ‡ฉ๐Ÿ‡ช', group: 'language' }, + { id: 'english', label: 'English ๐Ÿ‡ฌ๐Ÿ‡ง', group: 'language' }, + { id: 'graphicDesign', label: 'Graphic Design', group: 'design' }, + { + id: 'userInterfaceDesign', + label: 'User Interface Design', + group: 'design', + }, + { + id: 'userExperienceDesign', + label: 'User Experience Design', + group: 'design', + }, + { + id: 'motivationAndEncouragement', + label: 'Motivation & encouragement', + group: 'other', + }, + { id: 'friendAndHelp', label: 'Be a friend and help', group: 'other' }, + { id: 'dontKnowYet', label: "I don't know yet", group: 'other' }, + { + id: 'careerOrientationAndPlanning', + label: 'Career orientation & planning', + group: 'careerSupport', + }, + { + id: 'internshipOrWorkingStudent', + label: 'Internship / working student position search', + group: 'careerSupport', + }, + { id: 'jobSearch', label: 'Job search', group: 'careerSupport' }, + { + id: 'jobApplicationsCvPreparationEnglish', + label: 'Job applications and CV preparation in English', + group: 'careerSupport', + }, + { + id: 'jobApplicationsCvPreparationGerman', + label: 'Job applications and CV preparation in German', + group: 'careerSupport', + }, + { + id: 'interviewPreparation', + label: 'Interview preparation', + group: 'careerSupport', + }, + { + id: 'codingChallengePreparation', + label: 'Coding challenge preparation', + group: 'careerSupport', + }, + { + id: 'buildingProfessionalNetwork', + label: 'Building a professional network', + group: 'careerSupport', + }, + { id: 'entrepreneurship', label: 'Entrepreneurship', group: 'careerSupport' }, + { id: 'freelancing', label: 'Freelancing', group: 'careerSupport' }, +] as const +export type CategoryKey = typeof CATEGORIES[number]['id'] +export type CategoryLabel = typeof CATEGORIES[number]['label'] + +export const CATEGORIES_MAP = Object.fromEntries( + CATEGORIES.map((cat) => [cat.id, cat.label]) +) as Record diff --git a/libs/shared-types/src/lib/RedProfile.ts b/libs/shared-types/src/lib/RedProfile.ts index 17e327625..ed2737ac1 100644 --- a/libs/shared-types/src/lib/RedProfile.ts +++ b/libs/shared-types/src/lib/RedProfile.ts @@ -10,6 +10,9 @@ import { GenderKey, Language, MenteeOccupationCategoryKey, + MentoringGoalKey, + MentoringTopicKey, + FieldOfExperienceKey, } from '@talent-connect/shared-config' export type RedProfile = { @@ -51,6 +54,18 @@ export type RedProfile = { favouritedRedProfileIds: Array optOutOfMenteesFromOtherRediLocation: boolean + mentor_mentoringTopics: Array + mentor_mentoringGoals: Array + mentor_professionalExperienceFields: Array + + mentee_mentoringGoal: MentoringGoalKey + mentee_overarchingMentoringTopics: Array + mentee_primaryRole_fieldOfExpertise: FieldOfExperienceKey + mentee_primaryRole_mentoringTopics: Array + mentee_secondaryRole_fieldOfExpertise: FieldOfExperienceKey + mentee_secondaryRole_mentoringTopics: Array + mentee_toolsAndFrameworks_mentoringTopics: Array + createdAt: Date updatedAt: Date userActivated?: boolean