├── .github ├── ISSUE_TEMPLATE │ ├── ---bug-report.md │ ├── --feature-request.md │ └── config.yml └── dependabot.yml ├── .gitignore ├── .nvmrc ├── .prettierrc ├── .vscode ├── launch.json └── settings.json ├── CONTRIBUTING.md ├── DEVELOPMENT.md ├── DOCUMENTATION.md ├── License.md ├── Procfile ├── README.md ├── app.json ├── auth0 ├── authzExtConfig.json ├── config.json ├── pages │ └── login.html ├── rules │ ├── Add data to idToken.js │ ├── auth0-account-link-extension.js │ ├── auth0-authorization-extension-deafult.js │ └── auth0-authorization-extension.js └── tenant.yaml ├── azure-pipelines-africa.yml ├── azure-pipelines-asia.yml ├── azure-pipelines-eu.yml ├── azure-pipelines.yml ├── backend ├── .eslintrc ├── .gitignore ├── common │ ├── errors │ │ ├── errorHandler.js │ │ └── errors.js │ ├── middleware │ │ ├── admin.js │ │ ├── events.js │ │ ├── permissions.js │ │ ├── token.js │ │ ├── votingToken.js │ │ └── webhook.js │ ├── plugins │ │ ├── allowPublish.js │ │ ├── publicFields.js │ │ └── updateAllowed.js │ ├── schemas │ │ └── Achievement.js │ ├── services │ │ ├── discord.js │ │ ├── google-calendar │ │ │ ├── credentials.json │ │ │ ├── getAccessTokenCode.js │ │ │ ├── google-calendar.js │ │ │ ├── token.json │ │ │ └── writeAccessTokenJson.js │ │ ├── linkedin.js │ │ ├── sendgrid.js │ │ └── webhook.js │ └── utils │ │ ├── dateUtils.js │ │ ├── mongoUtils.js │ │ └── userProfileUtils.js ├── index.js ├── migrations │ ├── 00-registration-questions.js │ ├── 01-remove-project-unique-index.js │ ├── 02-rename-sigma_sq-to-sigmaSq.js │ ├── 03-rename-country_code-to-countryCode.js │ ├── 04-add-finalists-to-event.js │ ├── 05-add-track-challenge-to-projectscore.js │ ├── 06-finals-active-to-event.js │ ├── 07-uniform-answers.js │ ├── 08-create-registration-objects-in-profiles.js │ ├── 09-add-challengesenabled-false │ ├── 10-add-banner-priority-and-approved-to-event.js │ ├── 11-add-emailConfig-to-event.js │ ├── 12-fix-empty-senderEmail-in-event │ ├── 13-set-eventLocation-null-where-not-exists.js │ ├── 14-add-checklist-to-registration.js │ ├── 15-add-meetingRooms-to-event.js │ ├── 16-add-faq-challenge_instructions-demoInstructions-to-event.js │ ├── 17-add-specific-hackerpacks-to-event.js │ ├── 18-add-event-newsletter-link-to-event.js │ ├── 19-add-organization-to-event.js │ ├── 20-add-new-fields-to-teams-info.js │ ├── 21-add-recruiters-to-event.js │ ├── 22-rename-team-tagline-to-subtitle.js │ ├── 23-add-timeline-to-event.js │ ├── 24-sync-registration-to-profiles.js │ ├── 25-add-emailConfig-to-event.js │ ├── 26-fix-empty-senderEmail-in-event.js │ ├── 27-add-experimental-flag-to-events.js │ ├── 28-add-gavel-login-to-registrations.js │ └── index.js ├── misc │ ├── config.js │ ├── db.js │ ├── gridfs.js │ ├── jwt.js │ ├── logger.js │ └── throng.js ├── modules │ ├── achievement │ │ └── model.js │ ├── admin │ │ └── routes.js │ ├── alert │ │ ├── graphql-controller.js │ │ ├── graphql.js │ │ └── model.js │ ├── auth │ │ ├── controller.js │ │ └── routes.js │ ├── banner │ │ ├── controller.js │ │ ├── model.js │ │ └── routes.js │ ├── cron │ │ ├── index.js │ │ └── jobs │ │ │ └── generate-event-results.js │ ├── devtools │ │ └── routes.js │ ├── email-task │ │ ├── controller.js │ │ ├── model.js │ │ ├── routes.js │ │ └── types.js │ ├── event │ │ ├── controller.js │ │ ├── graphql-controller.js │ │ ├── graphql.js │ │ ├── model.js │ │ └── routes.js │ ├── files │ │ ├── controller.js │ │ ├── model.js │ │ └── routes.js │ ├── filter-group │ │ ├── controller.js │ │ ├── model.js │ │ └── routes.js │ ├── graphql-controller-factory.js │ ├── graphql-shared-types.js │ ├── graphql.js │ ├── hackerpack │ │ ├── controller.js │ │ ├── model.js │ │ └── routes.js │ ├── meeting │ │ ├── graphql-controller.js │ │ ├── graphql.js │ │ ├── helpers.js │ │ └── model.js │ ├── message │ │ ├── graphql-controller.js │ │ ├── graphql.js │ │ └── model.js │ ├── newsletter │ │ └── routes.js │ ├── organization │ │ ├── controller.js │ │ ├── graphql-controller.js │ │ ├── graphql.js │ │ ├── model.js │ │ └── routes.js │ ├── project │ │ ├── controller.js │ │ ├── model.js │ │ └── routes.js │ ├── project_score │ │ ├── controller.js │ │ ├── model.js │ │ └── routes.js │ ├── rankings │ │ ├── controller.js │ │ ├── model.js │ │ └── routes.js │ ├── recruitment │ │ ├── controller.js │ │ ├── model.js │ │ └── routes.js │ ├── registration │ │ ├── checklists.js │ │ ├── controller.js │ │ ├── graphql-controller.js │ │ ├── graphql.js │ │ ├── helpers.js │ │ ├── model.js │ │ └── routes.js │ ├── reviewing │ │ └── gavel │ │ │ ├── Annotator.js │ │ │ ├── Decision.js │ │ │ ├── Project.js │ │ │ ├── controller.js │ │ │ ├── helper.js │ │ │ ├── maths.js │ │ │ ├── routes.js │ │ │ └── settings.js │ ├── routes.js │ ├── sandbox │ │ ├── controller.js │ │ └── routes.js │ ├── team │ │ ├── controller.js │ │ ├── model.js │ │ └── routes.js │ ├── upload │ │ ├── helper.js │ │ └── routes.js │ ├── user-profile │ │ ├── controller.js │ │ ├── graphql-controller.js │ │ ├── graphql.js │ │ ├── helpers.js │ │ ├── model.js │ │ └── routes.js │ ├── voting-token │ │ ├── controller.js │ │ ├── model.js │ │ └── routes.js │ └── winner-votes │ │ ├── controller.js │ │ ├── model.js │ │ └── routes.js ├── nodemon.json ├── package-lock.json ├── package.json └── utils │ └── permissions.js ├── docker-compose.yml ├── frontend ├── .babelrc.js ├── .eslintrc ├── .gitignore ├── README.md ├── config-overrides.js ├── jsconfig.json ├── package-lock.json ├── package.json ├── postcss.config.js ├── public │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ ├── apple-touch-icon.png │ ├── browserconfig.xml │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon.ico │ ├── index.html │ ├── locales │ │ ├── en │ │ │ └── translation.json │ │ └── zh │ │ │ └── translation.json │ ├── manifest.json │ ├── mstile-150x150.png │ ├── safari-pinned-tab.svg │ └── site.webmanifest ├── src │ ├── App.js │ ├── assets │ │ ├── images │ │ │ ├── dashboardDefault.jpg │ │ │ ├── default_cover_image.png │ │ │ ├── default_thumbnail.png │ │ │ ├── laser_2016.jpg │ │ │ ├── nawbar_waves.svg │ │ │ ├── nawbar_waves_.svg │ │ │ └── visa_signature.jpg │ │ └── logos │ │ │ ├── JO_wordmark_black.png │ │ │ ├── JO_wordmark_white.png │ │ │ ├── JunctionPlatform_Emblem_Black.svg │ │ │ ├── JunctionPlatform_Emblem_White.svg │ │ │ ├── emblem.svg │ │ │ ├── emblem_black.png │ │ │ ├── emblem_white.png │ │ │ ├── flagCN.png │ │ │ ├── flagUK.png │ │ │ ├── wordmark_black.svg │ │ │ └── wordmark_white.svg │ ├── components │ │ ├── LanguageMenu │ │ │ └── index.js │ │ ├── Participant │ │ │ ├── ParticipantPreview │ │ │ │ └── index.js │ │ │ ├── Profile │ │ │ │ └── index.js │ │ │ ├── ProfileInfo │ │ │ │ └── index.js │ │ │ ├── ProfileSide │ │ │ │ └── index.js │ │ │ ├── ProfileTop │ │ │ │ └── index.js │ │ │ └── RecruitmentFavorites │ │ │ │ └── index.js │ │ ├── PricingMenu │ │ │ └── index.js │ │ ├── Team │ │ │ ├── AdminTeamEdit │ │ │ │ └── index.js │ │ │ ├── Apply │ │ │ │ └── index.js │ │ │ ├── Filter │ │ │ │ └── index.js │ │ │ ├── JoinTeamByCode │ │ │ │ └── index.js │ │ │ ├── NoTeam │ │ │ │ └── index.js │ │ │ ├── TeamCreateEditForm │ │ │ │ └── index.js │ │ │ ├── TeamDescription │ │ │ │ └── index.js │ │ │ ├── TeamHeader │ │ │ │ └── index.js │ │ │ ├── TeamMembers │ │ │ │ └── index.js │ │ │ ├── TeamProfile │ │ │ │ └── index.js │ │ │ └── TeamRoles │ │ │ │ └── index.js │ │ ├── UserAvatar │ │ │ └── index.js │ │ ├── UserMenu │ │ │ └── index.js │ │ ├── animated │ │ │ ├── FadeInWrapper.js │ │ │ ├── StaggeredList.js │ │ │ └── StaggeredListItem.js │ │ ├── buttons │ │ │ └── StepButtons.js │ │ ├── calendar │ │ │ └── calendarView.js │ │ ├── cards │ │ │ ├── CandidateCard.js │ │ │ └── TeamCard.js │ │ ├── challenges │ │ │ ├── ChallengeDetail.js │ │ │ └── ChallengeSection.js │ │ ├── events │ │ │ ├── EventCard │ │ │ │ └── index.js │ │ │ ├── EventCardSmall │ │ │ │ └── index.js │ │ │ ├── EventHeroImage │ │ │ │ └── index.js │ │ │ ├── EventPageScript │ │ │ │ └── index.js │ │ │ ├── EventPageScriptIFrame │ │ │ │ └── index.js │ │ │ └── NewEventCard │ │ │ │ └── index.js │ │ ├── filters │ │ │ ├── FilterForm.js │ │ │ ├── FilterGroupMenu.js │ │ │ ├── FilterList.js │ │ │ ├── FilterListItem.js │ │ │ ├── FilterSaveForm.js │ │ │ └── FilterValueInput.js │ │ ├── generic │ │ │ ├── ActionMenu │ │ │ │ └── index.js │ │ │ ├── BannerCarousel │ │ │ │ └── index.js │ │ │ ├── Button │ │ │ │ └── index.js │ │ │ ├── CardTag │ │ │ │ └── index.js │ │ │ ├── ConfirmDialog │ │ │ │ └── index.js │ │ │ ├── Container │ │ │ │ └── index.js │ │ │ ├── DescriptionItem │ │ │ │ ├── DescriptionItem.module.scss │ │ │ │ └── index.js │ │ │ ├── Divider │ │ │ │ └── index.js │ │ │ ├── DragDropList │ │ │ │ └── index.js │ │ │ ├── Empty │ │ │ │ └── index.js │ │ │ ├── ErrorsBox │ │ │ │ └── index.js │ │ │ ├── EventImage │ │ │ │ └── index.js │ │ │ ├── ExternalLink │ │ │ │ └── index.js │ │ │ ├── GradientBox │ │ │ │ └── index.js │ │ │ ├── IconButton │ │ │ │ └── index.js │ │ │ ├── Image │ │ │ │ └── index.js │ │ │ ├── LineDivider │ │ │ │ └── index.js │ │ │ ├── List │ │ │ │ └── index.js │ │ │ ├── Markdown │ │ │ │ └── index.js │ │ │ ├── Modal │ │ │ │ └── index.js │ │ │ ├── NotificationBlock │ │ │ │ ├── IconHeader.js │ │ │ │ └── index.js │ │ │ ├── PageHeader │ │ │ │ └── index.js │ │ │ ├── PricingCard │ │ │ │ └── index.js │ │ │ ├── ProgressBar │ │ │ │ └── index.js │ │ │ ├── RadioScore │ │ │ │ └── index.js │ │ │ ├── SocialLinks │ │ │ │ └── index.js │ │ │ ├── Statistic │ │ │ │ └── index.js │ │ │ ├── StatusBadge │ │ │ │ └── index.js │ │ │ ├── Stepper │ │ │ │ └── index.js │ │ │ ├── Switch │ │ │ │ └── index.js │ │ │ ├── Table │ │ │ │ ├── TablePaginationActions.js │ │ │ │ ├── TableToolbar.js │ │ │ │ └── index.js │ │ │ ├── Tag │ │ │ │ ├── Variants.js │ │ │ │ └── index.js │ │ │ ├── TimelineDot │ │ │ │ └── index.js │ │ │ ├── UserListItem │ │ │ │ ├── OrganiserListItem.js │ │ │ │ └── index.js │ │ │ └── _Table │ │ │ │ ├── ActionBar.js │ │ │ │ ├── PageSelect.js │ │ │ │ ├── PageSizeSelect.js │ │ │ │ ├── Pagination.js │ │ │ │ ├── Table.js │ │ │ │ ├── filterFunctions.js │ │ │ │ ├── filterTypes.js │ │ │ │ ├── filters │ │ │ │ ├── ContainsSearch.js │ │ │ │ ├── MultipleSelectFilter.js │ │ │ │ └── SingleSelectFilter.js │ │ │ │ ├── index.js │ │ │ │ └── sortFunctions.js │ │ ├── hackerpack │ │ │ ├── CompanySection.js │ │ │ └── HackerpackDetail.js │ │ ├── inputs │ │ │ ├── BlockExitIfDirty │ │ │ │ └── index.js │ │ │ ├── BooleanInput │ │ │ │ └── index.js │ │ │ ├── BottomBar.js │ │ │ ├── Color │ │ │ │ └── index.js │ │ │ ├── DateInput │ │ │ │ └── index.js │ │ │ ├── DateTimeInput │ │ │ │ └── index.js │ │ │ ├── EducationInput │ │ │ │ └── index.js │ │ │ ├── EventNewsLetterButton │ │ │ │ └── index.js │ │ │ ├── EventTagsSelect │ │ │ │ └── index.js │ │ │ ├── FormControl │ │ │ │ └── index.js │ │ │ ├── ImageUpload │ │ │ │ └── index.js │ │ │ ├── ImageUploadMultiple │ │ │ │ └── index.js │ │ │ ├── JobRoleInput │ │ │ │ └── index.js │ │ │ ├── MarkdownInput │ │ │ │ └── index.js │ │ │ ├── NewsLetterButton │ │ │ │ └── index.js │ │ │ ├── PdfUpload │ │ │ │ └── index.js │ │ │ ├── PhoneNumberInput │ │ │ │ └── index.js │ │ │ ├── ProjectStatusInput │ │ │ │ └── index.js │ │ │ ├── RecruitmentOptionInput │ │ │ │ └── index.js │ │ │ ├── RegistrationStatusSelect │ │ │ │ └── index.js │ │ │ ├── Select │ │ │ │ └── index.js │ │ │ ├── SelectOld │ │ │ │ └── index.js │ │ │ ├── SkillsInput │ │ │ │ └── index.js │ │ │ ├── StreetAddressForm │ │ │ │ └── index.js │ │ │ ├── SubmissionFormCustomInput │ │ │ │ └── index.js │ │ │ ├── SubmitButton │ │ │ │ └── index.js │ │ │ ├── TeamOptionInput │ │ │ │ └── index.js │ │ │ ├── TextAreaInput │ │ │ │ └── index.js │ │ │ ├── TextInput │ │ │ │ └── index.js │ │ │ └── TimeInput │ │ │ │ └── index.js │ │ ├── layouts │ │ │ ├── CookieConsentBar │ │ │ │ └── index.js │ │ │ ├── EventFooter │ │ │ │ └── index.js │ │ │ ├── FixedLayout │ │ │ │ └── index.js │ │ │ ├── Footer │ │ │ │ └── index.js │ │ │ ├── MaterialTabsLayout │ │ │ │ └── index.js │ │ │ ├── PageWrapper │ │ │ │ └── index.js │ │ │ └── SidebarLayout │ │ │ │ └── index.js │ │ ├── loaders │ │ │ ├── GlitchLoader │ │ │ │ └── index.js │ │ │ └── LoadingOverlay │ │ │ │ └── index.js │ │ ├── messaging │ │ │ ├── alerts │ │ │ │ └── index.js │ │ │ └── chat │ │ │ │ └── index.js │ │ ├── modals │ │ │ ├── BulkEditRegistrationModal │ │ │ │ └── index.js │ │ │ ├── BulkEmailModal │ │ │ │ └── index.js │ │ │ ├── EditProjectModal │ │ │ │ └── index.js │ │ │ ├── EditRegistrationModal │ │ │ │ ├── EditRegistrationActions.js │ │ │ │ ├── EditRegistrationContent.js │ │ │ │ └── index.js │ │ │ ├── EditTeamModal │ │ │ │ └── index.js │ │ │ ├── OrganiserSelectModal │ │ │ │ └── index.js │ │ │ ├── ProjectReviewModal │ │ │ │ ├── ReviewElement.js │ │ │ │ └── index.js │ │ │ ├── ProjectScoreModal │ │ │ │ └── index.js │ │ │ ├── QRCodeModal │ │ │ │ └── index.js │ │ │ ├── QRCodeReaderModal │ │ │ │ └── index.js │ │ │ └── VisaInvitationDrawer │ │ │ │ ├── VisaInvitationPDF.js │ │ │ │ └── index.js │ │ ├── navbars │ │ │ ├── AccountNavBar │ │ │ │ └── index.js │ │ │ ├── BasicNavBar │ │ │ │ └── index.js │ │ │ └── GlobalNavBar │ │ │ │ └── index.js │ │ ├── organizations │ │ │ └── AdminOrganization │ │ │ │ └── index.js │ │ ├── pdfs │ │ │ └── ParticipationCertificate.js │ │ ├── plots │ │ │ ├── ApplicationsCount.js │ │ │ ├── ApplicationsLast24h.js │ │ │ ├── ApplicationsOverTime.js │ │ │ ├── RatingsSplit.js │ │ │ ├── RegistrationsByCountry.js │ │ │ ├── RegistrationsByGender.js │ │ │ ├── RegistrationsByNationality.js │ │ │ ├── RegistrationsByStatus.js │ │ │ ├── ReviewedAverage.js │ │ │ ├── ReviewedPercent.js │ │ │ └── TeamsCount.js │ │ ├── projects │ │ │ ├── ProjectDetail │ │ │ │ ├── Pagination.js │ │ │ │ ├── ProjectTeam.js │ │ │ │ ├── ShareProject.js │ │ │ │ └── index.js │ │ │ ├── ProjectSubmissionFields │ │ │ │ ├── BaseField │ │ │ │ │ └── index.js │ │ │ │ ├── ChallengesField │ │ │ │ │ └── index.js │ │ │ │ ├── DemoField │ │ │ │ │ └── index.js │ │ │ │ ├── DescriptionField │ │ │ │ │ └── index.js │ │ │ │ ├── ImagesField │ │ │ │ │ └── index.js │ │ │ │ ├── LocationField │ │ │ │ │ └── index.js │ │ │ │ ├── NameField │ │ │ │ │ └── index.js │ │ │ │ ├── PrivacyField │ │ │ │ │ └── index.js │ │ │ │ ├── PunchlineField │ │ │ │ │ └── index.js │ │ │ │ ├── SourceField │ │ │ │ │ └── index.js │ │ │ │ ├── SourcePublicField │ │ │ │ │ └── index.js │ │ │ │ ├── StatusField │ │ │ │ │ └── index.js │ │ │ │ ├── TechnologiesField │ │ │ │ │ └── index.js │ │ │ │ ├── TrackField │ │ │ │ │ └── index.js │ │ │ │ └── VideoField │ │ │ │ │ └── index.js │ │ │ ├── ProjectsGrid │ │ │ │ └── index.js │ │ │ ├── ProjectsGridItem │ │ │ │ └── index.js │ │ │ └── ScoreCriteria │ │ │ │ └── index.js │ │ └── tables │ │ │ ├── AttendeeTable │ │ │ └── index.js │ │ │ ├── ProjectsTable │ │ │ └── index.js │ │ │ └── TeamsTable │ │ │ └── index.js │ ├── constants │ │ ├── config.js │ │ ├── events.js │ │ ├── filters.js │ │ ├── projectFields.js │ │ └── timezones.json │ ├── graphql │ │ ├── client.js │ │ ├── mutations │ │ │ ├── alertOps.js │ │ │ ├── eventOps.js │ │ │ ├── meetings.js │ │ │ └── messageOps.js │ │ ├── queries │ │ │ ├── alert.js │ │ │ ├── events.js │ │ │ ├── hackerpack.js │ │ │ ├── meetings.js │ │ │ ├── messages.js │ │ │ ├── organization.js │ │ │ ├── registrations.js │ │ │ └── userProfile.js │ │ └── subscriptions │ │ │ ├── alert.js │ │ │ └── messages.js │ ├── hocs │ │ ├── EventStatusWrapper.js │ │ ├── RequiresPermission.js │ │ ├── RequiresRole.js │ │ └── ShowIfPermission.js │ ├── hooks │ │ ├── apiHooks.js │ │ ├── customHooks.js │ │ └── formHooks.js │ ├── i18n.js │ ├── index.js │ ├── junctionTheme.js │ ├── material-ui-theme.js │ ├── notifier.js │ ├── pages │ │ ├── _account │ │ │ ├── dashboard │ │ │ │ └── index.js │ │ │ ├── index.js │ │ │ └── profile │ │ │ │ └── index.js │ │ ├── _admin │ │ │ ├── banner │ │ │ │ └── index.js │ │ │ ├── default │ │ │ │ ├── BannerList.js │ │ │ │ ├── EventPriority.js │ │ │ │ ├── HackerpackList.js │ │ │ │ ├── NewBannerForm.js │ │ │ │ ├── NewHackerpackForm.js │ │ │ │ ├── NewOrganizationForm.js │ │ │ │ ├── OrganizationList.js │ │ │ │ ├── UnapprovedEvents.js │ │ │ │ └── index.js │ │ │ ├── hackerpack │ │ │ │ └── index.js │ │ │ ├── index.js │ │ │ └── organization │ │ │ │ └── index.js │ │ ├── _callback │ │ │ └── index.js │ │ ├── _contact │ │ │ └── index.js │ │ ├── _dashboard │ │ │ ├── index.js │ │ │ └── renderDashboard │ │ │ │ ├── default │ │ │ │ ├── events │ │ │ │ │ ├── CreateEventCard.js │ │ │ │ │ ├── Organizer.js │ │ │ │ │ ├── Participant.js │ │ │ │ │ ├── Partner.js │ │ │ │ │ └── index.js │ │ │ │ ├── index.js │ │ │ │ ├── logout │ │ │ │ │ └── index.js │ │ │ │ └── profile │ │ │ │ │ └── index.js │ │ │ │ ├── generalPages │ │ │ │ ├── challenges │ │ │ │ │ ├── ChallengePage.js │ │ │ │ │ └── index.js │ │ │ │ ├── default │ │ │ │ │ ├── Blocks │ │ │ │ │ │ ├── AlertBlock.js │ │ │ │ │ │ ├── CertificateBlock.js │ │ │ │ │ │ ├── EventOverBlock.js │ │ │ │ │ │ ├── GavelReviewingBlock.js │ │ │ │ │ │ ├── PartnerReviewingBlock.js │ │ │ │ │ │ ├── ProjectBlock.js │ │ │ │ │ │ ├── ProjectSubmissionsBlock.js │ │ │ │ │ │ ├── RegistrationStatusBlock.js │ │ │ │ │ │ ├── ReviewingPeriodBlock.js │ │ │ │ │ │ ├── SocialMediaBlock.js │ │ │ │ │ │ ├── TeamStatusBlock.js │ │ │ │ │ │ ├── TimeLineBlock.js │ │ │ │ │ │ ├── TravelGrantStatusBlock.js │ │ │ │ │ │ └── VisaInvitationBlock.js │ │ │ │ │ └── index.js │ │ │ │ ├── hackerpack │ │ │ │ │ ├── UNUSED_CompanySection.js │ │ │ │ │ └── index.js │ │ │ │ └── map │ │ │ │ │ └── index.js │ │ │ │ ├── index.js │ │ │ │ ├── organiser │ │ │ │ ├── alerts │ │ │ │ │ └── index.js │ │ │ │ ├── checkin │ │ │ │ │ ├── Reader.js │ │ │ │ │ └── index.js │ │ │ │ ├── default │ │ │ │ │ ├── EventsList.js │ │ │ │ │ ├── NewEventForm.js │ │ │ │ │ └── index.js │ │ │ │ ├── edit │ │ │ │ │ ├── challenges │ │ │ │ │ │ ├── ChallengesForm.js │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── configuration │ │ │ │ │ │ ├── TracksForm.js │ │ │ │ │ │ ├── TravelGrantConfig.js │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── default │ │ │ │ │ │ ├── const.js │ │ │ │ │ │ ├── index.js │ │ │ │ │ │ └── organizationInput.js │ │ │ │ │ ├── emails │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── hackerpack │ │ │ │ │ │ ├── HackerpackForm.js │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── index.js │ │ │ │ │ ├── meetingRooms │ │ │ │ │ │ ├── MeetingRoomsForm.js │ │ │ │ │ │ ├── TimeSlotsInput.js │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── other │ │ │ │ │ │ ├── CertificateForm.js │ │ │ │ │ │ ├── EventTagsForm.js │ │ │ │ │ │ ├── MetaTagsForm.js │ │ │ │ │ │ ├── PageScriptsForm.js │ │ │ │ │ │ ├── WebhooksForm.js │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── questions │ │ │ │ │ │ ├── CustomSectionList │ │ │ │ │ │ │ ├── CustomSectionListItem.js │ │ │ │ │ │ │ └── index.js │ │ │ │ │ │ ├── QuestionSelect │ │ │ │ │ │ │ └── index.js │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── schedule │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── scoreCriteria │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── submission │ │ │ │ │ │ ├── components │ │ │ │ │ │ │ ├── EditableOptions.js │ │ │ │ │ │ │ ├── TempFieldBuilder.js │ │ │ │ │ │ │ ├── inputs │ │ │ │ │ │ │ │ ├── BooleanInput.js │ │ │ │ │ │ │ │ ├── FileInput.js │ │ │ │ │ │ │ │ ├── TextInput.js │ │ │ │ │ │ │ │ └── UrlInput.js │ │ │ │ │ │ │ └── section │ │ │ │ │ │ │ │ ├── Checkbox.js │ │ │ │ │ │ │ │ ├── Dropdown.js │ │ │ │ │ │ │ │ ├── EditableText.js │ │ │ │ │ │ │ │ └── RemoveButton.js │ │ │ │ │ │ └── index.js │ │ │ │ │ └── timeline │ │ │ │ │ │ ├── TimelineForm.js │ │ │ │ │ │ └── index.js │ │ │ │ ├── index.js │ │ │ │ ├── manage │ │ │ │ │ ├── AddOrganiserDrawer.js │ │ │ │ │ ├── AddRecruiterDrawer.js │ │ │ │ │ └── index.js │ │ │ │ ├── participants │ │ │ │ │ ├── admin │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── assigned │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── default │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── index.js │ │ │ │ │ ├── teams │ │ │ │ │ │ └── index.js │ │ │ │ │ └── travel │ │ │ │ │ │ └── index.js │ │ │ │ ├── projects │ │ │ │ │ ├── annotators │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── by-challenge │ │ │ │ │ │ ├── ChallengeLink.js │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── by-track │ │ │ │ │ │ ├── TrackLink.js │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── default │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── finalist-selection │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── gavel │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── index.js │ │ │ │ │ ├── votingTokens │ │ │ │ │ │ └── index.js │ │ │ │ │ └── winners │ │ │ │ │ │ └── index.js │ │ │ │ ├── results │ │ │ │ │ ├── challenges │ │ │ │ │ │ ├── ChallengeResults.js │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── default │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── index.js │ │ │ │ │ ├── overall │ │ │ │ │ │ └── index.js │ │ │ │ │ └── tracks │ │ │ │ │ │ ├── TrackResults.js │ │ │ │ │ │ └── index.js │ │ │ │ ├── router.js │ │ │ │ ├── stats │ │ │ │ │ └── index.js │ │ │ │ └── travel-grants │ │ │ │ │ └── index.js │ │ │ │ ├── participant │ │ │ │ ├── calendar │ │ │ │ │ ├── MeetingCard.js │ │ │ │ │ ├── MeetingLocationSelection.js │ │ │ │ │ ├── ParticipantCalendarView.js │ │ │ │ │ └── index.js │ │ │ │ ├── checklist │ │ │ │ │ └── index.js │ │ │ │ ├── event-id │ │ │ │ │ └── index.js │ │ │ │ ├── finalist-voting │ │ │ │ │ └── index.js │ │ │ │ ├── index.js │ │ │ │ ├── project │ │ │ │ │ ├── ProjectImages.js │ │ │ │ │ ├── ProjectsList.js │ │ │ │ │ ├── SubmissionForm.js │ │ │ │ │ └── index.js │ │ │ │ ├── reviewing │ │ │ │ │ ├── CompareProjects.js │ │ │ │ │ ├── Complete.js │ │ │ │ │ ├── Disabled.js │ │ │ │ │ ├── FirstProject.js │ │ │ │ │ ├── Instructions.js │ │ │ │ │ ├── VoteTimer.js │ │ │ │ │ ├── index.js │ │ │ │ │ ├── instructions-online.md │ │ │ │ │ └── instructions-physical.md │ │ │ │ ├── side-challenges │ │ │ │ │ └── index.js │ │ │ │ ├── team │ │ │ │ │ ├── candidates │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── index.js │ │ │ │ │ ├── profile │ │ │ │ │ │ └── index.js │ │ │ │ │ └── teams │ │ │ │ │ │ └── index.js │ │ │ │ └── travel-grant │ │ │ │ │ ├── TravelGrantForm.js │ │ │ │ │ └── index.js │ │ │ │ └── partner │ │ │ │ ├── calendar │ │ │ │ ├── PartnerCalendarView.js │ │ │ │ ├── PartnerMeetingCard.js │ │ │ │ └── index.js │ │ │ │ ├── index.js │ │ │ │ ├── partnerrecruitment │ │ │ │ ├── admin │ │ │ │ │ ├── GrantAccessDialog.js │ │ │ │ │ ├── RecruitersList.js │ │ │ │ │ ├── RevokeAccessDialog.js │ │ │ │ │ ├── SearchBox.js │ │ │ │ │ └── index.js │ │ │ │ ├── default │ │ │ │ │ ├── Filters │ │ │ │ │ │ ├── AgeFilter.js │ │ │ │ │ │ ├── CountryOfResidenceFilter.js │ │ │ │ │ │ ├── EventsFilter.js │ │ │ │ │ │ ├── EventsFilterItem.js │ │ │ │ │ │ ├── FilterItem.js │ │ │ │ │ │ ├── FilteredBy.js │ │ │ │ │ │ ├── LanguagesFilter.js │ │ │ │ │ │ ├── RecruitmentStatusFilter.js │ │ │ │ │ │ ├── RelocationStatusFilter.js │ │ │ │ │ │ ├── RolesFilter.js │ │ │ │ │ │ ├── RolesFilterItem.js │ │ │ │ │ │ ├── SkillsFilter.js │ │ │ │ │ │ ├── SkillsFilterItem.js │ │ │ │ │ │ ├── TextSearchFilter.js │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── SearchResults │ │ │ │ │ │ ├── LoadingCard.js │ │ │ │ │ │ ├── Pagination.js │ │ │ │ │ │ ├── ResultCard.js │ │ │ │ │ │ ├── SkillRating.js │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── ToggleFavorites.js │ │ │ │ │ └── index.js │ │ │ │ ├── id │ │ │ │ │ ├── DetailSection.js │ │ │ │ │ ├── MessageHistory.js │ │ │ │ │ └── index.js │ │ │ │ └── index.js │ │ │ │ └── projects │ │ │ │ └── index.js │ │ ├── _devtools │ │ │ └── index.js │ │ ├── _error │ │ │ └── index.js │ │ ├── _events │ │ │ ├── index.js │ │ │ ├── past │ │ │ │ └── index.js │ │ │ └── slug │ │ │ │ ├── context.js │ │ │ │ ├── default │ │ │ │ ├── EventButtons │ │ │ │ │ └── index.js │ │ │ │ ├── EventInformation │ │ │ │ │ └── index.js │ │ │ │ ├── EventTimeline │ │ │ │ │ ├── TimelineDot.js │ │ │ │ │ └── index.js │ │ │ │ └── index.js │ │ │ │ ├── index.js │ │ │ │ ├── register │ │ │ │ ├── ErrorDisplay │ │ │ │ │ └── index.js │ │ │ │ ├── RegistrationBottomBar │ │ │ │ │ ├── ErrorDisplay.js │ │ │ │ │ └── index.js │ │ │ │ ├── RegistrationQuestion │ │ │ │ │ └── index.js │ │ │ │ ├── RegistrationSection │ │ │ │ │ └── index.js │ │ │ │ ├── RegistrationSectionCustom │ │ │ │ │ └── index.js │ │ │ │ ├── RegistrationSectionLabel │ │ │ │ │ └── index.js │ │ │ │ └── index.js │ │ │ │ ├── tracking │ │ │ │ └── index.js │ │ │ │ └── voteWithToken │ │ │ │ └── index.js │ │ ├── _hackerpack │ │ │ ├── UNUSED_CompanySection.js │ │ │ └── index.js │ │ ├── _home │ │ │ ├── EventHighlight │ │ │ │ └── index.js │ │ │ ├── EventsGrid │ │ │ │ └── index.js │ │ │ └── index.js │ │ ├── _index │ │ │ ├── EventHighlight │ │ │ │ └── index.js │ │ │ ├── EventsGrid │ │ │ │ └── index.js │ │ │ ├── IndexPage.js │ │ │ └── index.js │ │ ├── _login │ │ │ ├── default.js │ │ │ ├── index.js │ │ │ └── welcome │ │ │ │ ├── LightCheckbox.js │ │ │ │ ├── LightTextField.js │ │ │ │ └── index.js │ │ ├── _logout │ │ │ └── index.js │ │ ├── _pricing │ │ │ └── index.js │ │ └── _projects │ │ │ ├── index.js │ │ │ └── slug │ │ │ ├── by-challenge │ │ │ └── challenge │ │ │ │ └── index.js │ │ │ ├── by-track │ │ │ └── track │ │ │ │ └── index.js │ │ │ ├── challenge │ │ │ └── token │ │ │ │ └── index.js │ │ │ ├── default │ │ │ ├── Filters.js │ │ │ ├── ProjectsPreview.js │ │ │ └── index.js │ │ │ ├── index.js │ │ │ ├── track │ │ │ └── token │ │ │ │ └── index.js │ │ │ └── view │ │ │ └── projectId │ │ │ ├── EvaluationForm.js │ │ │ ├── ScoreForm.js │ │ │ └── index.js │ ├── redux │ │ ├── account │ │ │ ├── actionTypes.js │ │ │ ├── actions.js │ │ │ ├── reducer.js │ │ │ └── selectors.js │ │ ├── admin │ │ │ ├── actionTypes.js │ │ │ ├── actions.js │ │ │ ├── reducer.js │ │ │ └── selectors.js │ │ ├── auth │ │ │ ├── actionTypes.js │ │ │ ├── actions.js │ │ │ ├── reducer.js │ │ │ └── selectors.js │ │ ├── configureStore.js │ │ ├── dashboard │ │ │ ├── actionTypes.js │ │ │ ├── actions.js │ │ │ ├── reducer.js │ │ │ └── selectors.js │ │ ├── organiser │ │ │ ├── actionTypes.js │ │ │ ├── actions.js │ │ │ ├── reducer.js │ │ │ └── selectors.js │ │ ├── recruitment │ │ │ ├── actionTypes.js │ │ │ ├── actions.js │ │ │ ├── helpers.js │ │ │ ├── reducer.js │ │ │ └── selectors.js │ │ ├── rootReducer.js │ │ ├── snackbar │ │ │ ├── actionTypes.js │ │ │ ├── actions.js │ │ │ ├── reducer.js │ │ │ └── selectors.js │ │ ├── user │ │ │ ├── actionTypes.js │ │ │ ├── actions.js │ │ │ ├── reducer.js │ │ │ └── selectors.js │ │ └── utils.js │ ├── routes.js │ ├── serviceWorker.js │ ├── services │ │ ├── admin.js │ │ ├── analytics.js │ │ ├── api.js │ │ ├── auth0.js │ │ ├── axios.js │ │ ├── banner.js │ │ ├── csvExporter.js │ │ ├── email.js │ │ ├── events.js │ │ ├── facebookPixel.js │ │ ├── files.js │ │ ├── filterGroups.js │ │ ├── hackerpack.js │ │ ├── newsletter.js │ │ ├── organization.js │ │ ├── projectScores.js │ │ ├── projects.js │ │ ├── rankings.js │ │ ├── recruitment.js │ │ ├── registrations.js │ │ ├── reviewing │ │ │ └── gavel.js │ │ ├── teams.js │ │ ├── userProfiles.js │ │ ├── validation.js │ │ ├── votingToken.js │ │ └── winnerVote.js │ ├── styles │ │ ├── index.css │ │ └── tailwind.css │ └── utils │ │ ├── constants │ │ └── gradientList.js │ │ ├── dataModifiers.js │ │ ├── debuggingTools.js │ │ ├── events.js │ │ ├── experimental.js │ │ ├── misc.js │ │ ├── modifyPdf.js │ │ ├── styles.js │ │ └── stylingHelpers.js └── tailwind.config.js ├── npm ├── package-lock.json ├── package.json ├── shared ├── .eslintrc ├── constants │ ├── achievement-types.js │ ├── auth.js │ ├── countries.js │ ├── currencies.js │ ├── event-page-scripts.js │ ├── event-statuses.js │ ├── event-types.js │ ├── field-types.js │ ├── filter-types.js │ ├── filter-values.js │ ├── genders.js │ ├── gradient-list.js │ ├── industries.js │ ├── languages.js │ ├── misc.js │ ├── overall-reviewing-methods.js │ ├── project-default-fields.js │ ├── project-schema.js │ ├── project-statuses.js │ ├── registration-fields-custom.js │ ├── registration-fields.js │ ├── registration-statuses.js │ ├── registration-travel-grant-statuses.js │ ├── reviewing-methods.js │ ├── roles.js │ ├── select-options.js │ ├── skills.js │ ├── submission-default-fields.js │ ├── themes.js │ ├── travel-grant-details-validation-schema.js │ └── universities.js ├── data │ ├── countries.json │ ├── currencies.json │ ├── genders.json │ ├── industries.json │ ├── languages.json │ ├── roles-biz.json │ ├── roles-design.json │ ├── roles-dev.json │ ├── roles-other.json │ ├── skills-abstract.json │ ├── skills-programming-frameworks-tools.json │ ├── skills-programming-languages.json │ ├── skills-tools-and-software.json │ ├── themes.json │ ├── timezones.json │ └── universities.json ├── helpers │ ├── events.js │ ├── filterFunctions.js │ ├── filterHelpers.js │ ├── registration-fields.js │ └── utils.js ├── index.js ├── package-lock.json ├── package.json ├── schemas │ ├── Address.js │ ├── Answers.js │ ├── Candidate.js │ ├── Certificate.js │ ├── Challenge.js │ ├── Checklist.js │ ├── CloudinaryImage.js │ ├── CustomAnswer.js │ ├── Education.js │ ├── EventPageScript.js │ ├── EventTag.js │ ├── EventTheme.js │ ├── EventTimeline.js │ ├── Hackerpack.js │ ├── IBANAccount.js │ ├── LegalName.js │ ├── MeetingRoom.js │ ├── MongoFile.js │ ├── PhoneNumber.js │ ├── Recruiter.js │ ├── RecruiterEvents.js │ ├── RecruitmentOptions.js │ ├── RegistrationConfig.js │ ├── RegistrationQuestion.js │ ├── RegistrationQuestionSettings.js │ ├── RegistrationSection.js │ ├── Role.js │ ├── ScoreCriteria.js │ ├── ScoreCriteriaSettings.js │ ├── Skill.js │ ├── SubmissionDefaultFields.js │ ├── TeamOptions.js │ ├── TeamRole.js │ ├── Track.js │ ├── TravelGrantConfig.js │ ├── TravelGrantDetails.js │ ├── UserDetailsConfig.js │ ├── UserDetailsConfigItem.js │ ├── UserProfileFields.js │ ├── Webhook.js │ ├── index.js │ └── validation │ │ ├── eventSchema.js │ │ └── messageSchema.js └── test │ └── filterFunctions.test.js └── tests ├── gavelStartVoteStressTest.js └── gavelSubmitVoteStressTest.js /.github/ISSUE_TEMPLATE/--feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "✨ Feature request" 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 16 | 17 | **Is your feature request related to a problem? Please describe.** 18 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 19 | 20 | **Describe the solution you'd like** 21 | A clear and concise description of what you want to happen. 22 | 23 | **Describe alternatives you've considered** 24 | A clear and concise description of any alternative solutions or features you've considered. 25 | 26 | **Additional context** 27 | Add any other context or screenshots about the feature request here. 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true 2 | contact_links: 3 | - name: Ask a question 4 | url: https://github.com/hackjunction/JunctionApp/discussions 5 | about: Ask questions and discuss with other community members 6 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "npm" # See documentation for possible values 9 | # Root 10 | directory: "/" # Location of package manifests 11 | schedule: 12 | interval: "weekly" 13 | assignees: 14 | - "wickathou" 15 | 16 | - package-ecosystem: "npm" # See documentation for possible values 17 | # Frontend 18 | directory: "/frontend/" # Location of package manifests 19 | schedule: 20 | interval: "weekly" 21 | assignees: 22 | - "wickathou" 23 | 24 | - package-ecosystem: "npm" # See documentation for possible values 25 | # Backend 26 | directory: "/backend/" # Location of package manifests 27 | schedule: 28 | interval: "weekly" 29 | assignees: 30 | - "wickathou" 31 | 32 | - package-ecosystem: "npm" # See documentation for possible values 33 | # Shared 34 | directory: "/shared/" # Location of package manifests 35 | schedule: 36 | interval: "weekly" 37 | assignees: 38 | - "wickathou" 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | scripts/ 4 | dump/ 5 | build/ 6 | docker-compose.yml 7 | .env 8 | *.env.example 9 | azure-pipelines-1.yml 10 | azure-pipelines.yml 11 | .env dev frontend 12 | .env dev backend 13 | .env stag frontend 14 | .env stag backend 15 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 16.14.2 -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "tabWidth": 4, 4 | "semi": false, 5 | "singleQuote": true, 6 | "trailingComma": "all", 7 | "bracketSpacing": true, 8 | "jsxBracketSameLine": false, 9 | "endOfLine":"lf", 10 | "arrowParens": "avoid" 11 | } 12 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "node", 6 | "request": "attach", 7 | "name": "Node: Nodemon", 8 | "processId": "${command:PickProcess}", 9 | "restart": true, 10 | "protocol": "inspector", 11 | }, 12 | ] 13 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.workingDirectories": [ 3 | { 4 | "changeProcessCWD": true, 5 | "directory": "./frontend" 6 | }, 7 | { 8 | "changeProcessCWD": true, 9 | "directory": "./backend" 10 | }, 11 | { 12 | "changeProcessCWD": true, 13 | "directory": "./shared" 14 | } 15 | ], 16 | "editor.formatOnSave": true, 17 | "editor.codeActionsOnSave": { 18 | "source.fixAll.eslint": "explicit" 19 | }, 20 | "editor.formatOnType": true, 21 | "javascript.format.semicolons": "remove", 22 | "typescript.format.semicolons": "remove" 23 | } 24 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: npm start -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Junction App", 3 | "description": "Junction all-in-one hackathon platform", 4 | "buildpacks": [ 5 | { 6 | "url": "heroku/nodejs" 7 | } 8 | ], 9 | "env": { 10 | "SECRET_TOKEN": { 11 | "required": true, 12 | "description": "SECRET_TOKEN which is a must have" 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /auth0/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "AUTH0_DOMAIN": ".eu.auth0.com", 3 | "AUTH0_CLIENT_ID": "", 4 | "AUTH0_CLIENT_SECRET": "", 5 | "AUTH0_KEYWORD_REPLACE_MAPPINGS": { 6 | "APP_CALLBACKS": [ 7 | "http://localhost:3000/callback", 8 | "http://localhost:3000" 9 | ], 10 | "APP_LOGOUT_URLS": [ 11 | "http://localhost:3000/logout", 12 | "http://localhost:3000" 13 | ], 14 | "AUDIENCE": "https://.eu.auth0.com/api/v2/", 15 | "APP_NAME_BACKEND": "Junction App Backend", 16 | "APP_NAME_FRONTEND": "Junction SSO", 17 | "AUTHZ_URL": "https://.webtask.io/adf6e2f2b84784b57522e3b19dfc9201", 18 | "URL": "https://localhost:3000" 19 | }, 20 | "EXCLUDED_PROPS": { 21 | "clients": ["client_secret"], 22 | "connections": ["options.client_secret"] 23 | }, 24 | "AUTH0_ALLOW_DELETE": true 25 | } 26 | -------------------------------------------------------------------------------- /auth0/rules/Add data to idToken.js: -------------------------------------------------------------------------------- 1 | function (user, context, callback) { 2 | const namespace = 'https://app.hackjunction.com/'; 3 | const assignedRoles = (context.authorization || {}).roles || []; 4 | 5 | context.idToken[namespace + 'country'] = context.request.geoip.country_name; 6 | context.idToken[namespace + 'city'] = context.request.geoip.city_name; 7 | context.idToken[namespace + 'latitude'] = context.request.geoip.latitude; 8 | context.idToken[namespace + 'longitude'] = context.request.geoip.longitude; 9 | context.idToken[namespace + 'roles'] = user.roles; 10 | context.idToken[namespace + 'permissions'] = user.permissions; 11 | context.idToken[namespace + 'email'] = user.email; 12 | context.idToken[namespace + 'email_verified'] = user.email_verified; 13 | 14 | if (user.user_metadata) { 15 | context.idToken[namespace + 'recruiter_events'] = user.user_metadata.recruiterEvents || []; 16 | context.idToken[namespace + 'recruiter_organisation'] = user.user_metadata.recruiterOrganisation; 17 | } 18 | 19 | 20 | callback(null, user, context); 21 | } -------------------------------------------------------------------------------- /backend/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["airbnb-base", "plugin:prettier/recommended"], 3 | "env": { 4 | "jest": true, 5 | "browser": true 6 | }, 7 | "rules": { 8 | "import/no-extraneous-dependencies": "off", 9 | "import/prefer-default-export": "off", 10 | "no-confusing-arrow": "off", 11 | "linebreak-style": "off", 12 | "arrow-parens": ["error", "as-needed"], 13 | "comma-dangle": [ 14 | "error", 15 | { 16 | "arrays": "always-multiline", 17 | "objects": "always-multiline", 18 | "imports": "always-multiline", 19 | "exports": "always-multiline", 20 | "functions": "ignore" 21 | } 22 | ], 23 | "no-plusplus": "off", 24 | "no-underscore-dangle": "off" 25 | }, 26 | "globals": { 27 | "browser": true, 28 | "$": true, 29 | "before": true, 30 | "document": true 31 | }, 32 | } 33 | -------------------------------------------------------------------------------- /backend/.gitignore: -------------------------------------------------------------------------------- 1 | /shared 2 | /build 3 | .env 4 | .ignored 5 | /common/services/google-calendar/credentials.json 6 | /common/services/google-calendar/token.json -------------------------------------------------------------------------------- /backend/common/middleware/admin.js: -------------------------------------------------------------------------------- 1 | const { NotFoundError } = require('../errors/errors') 2 | 3 | const AdminMiddleware = { 4 | hasAdminToken: (req, res, next) => { 5 | console.log('CHECKING ADMIN TOKEN', req.query.adminToken) 6 | if (req.query.adminToken === global.gConfig.ADMIN_TOKEN) { 7 | next() 8 | } else { 9 | next(new NotFoundError()) 10 | } 11 | }, 12 | } 13 | 14 | module.exports = AdminMiddleware 15 | -------------------------------------------------------------------------------- /backend/common/middleware/token.js: -------------------------------------------------------------------------------- 1 | const { UnauthorizedError } = require('../errors/errors') 2 | 3 | /** Token parsing logic has moved to misc/jwt.js and is run for every request. 4 | * The purpose of this middleware is to throw a 401 error if the JWT does not exist 5 | * or is invalid 6 | */ 7 | 8 | /* Verify JWT from client requests */ 9 | 10 | module.exports = { 11 | hasToken: (req, res, next) => { 12 | if (!req.user) { 13 | throw new UnauthorizedError('Authentication required') 14 | } else { 15 | next() 16 | } 17 | }, 18 | } 19 | -------------------------------------------------------------------------------- /backend/common/middleware/votingToken.js: -------------------------------------------------------------------------------- 1 | const VotingTokenController = require('../../modules/voting-token/controller') 2 | const { UnauthorizedError } = require('../errors/errors') 3 | 4 | module.exports = { 5 | hasValidVotingToken: async (req, res, next) => { 6 | const token = req.query.votingToken || req.params.votingToken 7 | 8 | if (!token) { 9 | next(new UnauthorizedError('Voting token is required')) 10 | } else { 11 | try { 12 | const votingToken = await VotingTokenController.getToken(token) 13 | 14 | if (!votingToken || votingToken.isRevoked) { 15 | next( 16 | new UnauthorizedError( 17 | 'Voting token does not exist or it has been revoked', 18 | ), 19 | ) 20 | } 21 | 22 | req.votingToken = votingToken 23 | next() 24 | } catch { 25 | next( 26 | new UnauthorizedError( 27 | 'Voting token does not exist or it has been revoked', 28 | ), 29 | ) 30 | } 31 | } 32 | }, 33 | } -------------------------------------------------------------------------------- /backend/common/middleware/webhook.js: -------------------------------------------------------------------------------- 1 | const { NotFoundError } = require('../errors/errors') 2 | 3 | const WebhookMiddleware = { 4 | hasWebhookToken: (req, res, next) => { 5 | console.log('CHECKING WEBHOOK API TOKEN', req.query.webhook_api_key) 6 | if (req.query.webhook_api_key === global.gConfig.WEBHOOK_API_KEY) { 7 | next() 8 | } else { 9 | next(new NotFoundError()) 10 | } 11 | }, 12 | } 13 | 14 | module.exports = WebhookMiddleware 15 | -------------------------------------------------------------------------------- /backend/common/plugins/allowPublish.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash') 2 | /** 3 | * - Add a 'published' field to the model 4 | * - Users can add a 'requiredForPublish' option to their schema fields 5 | * - If a field has requiredForPublish: true, that field must exist if trying to set published to true. 6 | */ 7 | function allowPublishPlugin(schema, { defaultPublished = false } = {}) { 8 | schema.add({ 9 | published: { 10 | type: Boolean, 11 | default: defaultPublished, 12 | required: true, 13 | }, 14 | }) 15 | 16 | _.forOwn(schema.tree, (options, path) => { 17 | if (options.requiredForPublish) { 18 | const newOptions = { 19 | ...options, 20 | required: [ 21 | function () { 22 | return this.published === true 23 | }, 24 | `must be set before the event can be published!`, 25 | ], 26 | } 27 | //console.log("error with publishing", "options", options, "path", path)//TODO: migrate the publish button to the end and show errors 28 | delete newOptions.requiredForPublish 29 | schema.path(path, newOptions) 30 | } 31 | }) 32 | } 33 | 34 | module.exports = allowPublishPlugin 35 | -------------------------------------------------------------------------------- /backend/common/plugins/publicFields.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash') 2 | 3 | /** 4 | * Adds a new static method, getPublic, which returns only a subset of fields on the document 5 | */ 6 | 7 | function publicFieldsPlugin(schema, { fields = [] } = {}) { 8 | schema.statics.publicFields = function (docs) { 9 | if (Array.isArray(docs)) { 10 | return docs.map(doc => { 11 | return _.pick(doc.toJSON(), fields) 12 | }) 13 | } 14 | return _.pick(docs.toJSON(), fields) 15 | } 16 | } 17 | 18 | module.exports = publicFieldsPlugin 19 | -------------------------------------------------------------------------------- /backend/common/plugins/updateAllowed.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash') 2 | 3 | /** 4 | * Adds a new static method, updateAllowed, which only applies updates to fields that aren't blacklisted 5 | */ 6 | 7 | function updateAllowedPlugin(schema, { blacklisted = [] } = {}) { 8 | schema.statics.updateAllowed = function (doc, updates) { 9 | console.log('updating', updates, 'with', blacklisted) 10 | console.log('before', doc) 11 | _.forOwn(updates, (value, key) => { 12 | if (blacklisted.indexOf(key) === -1) { 13 | //console.log('Adding', key, value) 14 | // TODO FIX THE PROBLEM HERE THE CUSTOM QUESTIONS AREN'T ASSIGNED 15 | doc[key] = value 16 | // console.log('now dockey is', doc[key]) 17 | } else { 18 | console.log( 19 | 'Skipped', 20 | value, 21 | key, 22 | 'since', 23 | blacklisted.indexOf(key), 24 | ) 25 | } 26 | }) 27 | return doc.save() 28 | } 29 | } 30 | 31 | module.exports = updateAllowedPlugin 32 | -------------------------------------------------------------------------------- /backend/common/schemas/Achievement.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | 3 | const TYPES = [ 4 | 'challenge-placement', 5 | 'track-placement', 6 | 'overall-placement', 7 | 'finalist', 8 | ] 9 | 10 | const AchievementSchema = new mongoose.Schema({ 11 | type: { 12 | type: String, 13 | required: true, 14 | validate: { 15 | validator(v) { 16 | return TYPES.indexOf(v) !== -1 17 | }, 18 | message: () => `Type should be one of ${TYPES.join(', ')}`, 19 | }, 20 | }, 21 | label: { 22 | type: String, 23 | }, 24 | value: { 25 | type: String, 26 | }, 27 | rank: { 28 | type: Number, 29 | min: 1, 30 | }, 31 | }) 32 | 33 | module.exports = AchievementSchema 34 | -------------------------------------------------------------------------------- /backend/common/services/discord.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios') 2 | const Discord = require('discord.js') 3 | 4 | const client = new Discord.Client() 5 | const DiscordService = { 6 | initialize: () => { 7 | return new Promise((resolve, reject) => { 8 | client.once('ready', () => { 9 | console.log(client.users.map(user => user.email)) 10 | resolve(true) 11 | }) 12 | console.log(global.gConfig.DISCORD_BOT_TOKEN) 13 | client.login(global.gConfig.DISCORD_BOT_TOKEN) 14 | }) 15 | }, 16 | } 17 | module.exports = DiscordService 18 | -------------------------------------------------------------------------------- /backend/common/services/google-calendar/credentials.json: -------------------------------------------------------------------------------- 1 | { 2 | "installed": { 3 | "client_id": "752565208443-0g0ui7afokfi2b1t1von4qankq1vh62h.apps.googleusercontent.com", 4 | "project_id": "prod-calendar-386407", 5 | "auth_uri": "https://accounts.google.com/o/oauth2/auth", 6 | "token_uri": "https://oauth2.googleapis.com/token", 7 | "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", 8 | "client_secret": "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", 9 | "redirect_uris": [ 10 | "http://localhost" 11 | ] 12 | } 13 | } -------------------------------------------------------------------------------- /backend/common/services/google-calendar/token.json: -------------------------------------------------------------------------------- 1 | { 2 | "access_token": "ya29.a0AWY7Ckl7aCk5TvqSo3Lt-HSDwEWgolX718eVnoPFvEfdXcjLSByJ7HBK2tMxPnSgU8TItsluzhFTCCJZBjEYrff2ljdsvoKedx4UYpASxb-e8ZdTsBWWYF6_guNuJ0ivhJELS_v_I66k5UrgxsotP48gkVklaCgYKAWESARASFQG1tDrpDFQk2aE8PRheiSxruvE02Q0163", 3 | "refresh_token": "1//0cSsti3mKWoIrCgYIARAAGAwSNwF-L9IrVnM4q8qDiTfqQ0H69q1d9Y_t_4ua3SAHbZzYbi_07NKFBDQyKExuDpUAig2wNjz4kkE", 4 | "scope": "https://www.googleapis.com/auth/calendar", 5 | "token_type": "Bearer", 6 | "expiry_date": 1683801160546 7 | } 8 | -------------------------------------------------------------------------------- /backend/common/services/linkedin.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios') 2 | 3 | const CLIENT_ID = '77gyr30dlraeob' 4 | const CLIENT_SECRET = 'jvMviXjTgjluzXN8' 5 | 6 | const LinkedInService = { 7 | getAccessToken: () => { 8 | return axios 9 | .post( 10 | `https://www.linkedin.com/oauth/v2/accessToken?grant_type=client_credentials&client_id=${CLIENT_ID}&client_secret=${CLIENT_SECRET}`, 11 | {}, 12 | { 13 | headers: { 14 | Host: 'www.linkedin.com', 15 | 'Content-Type': 'application/x-www-form-urlencoded', 16 | }, 17 | } 18 | ) 19 | .then(res => { 20 | console.log('getAccessToken', res) 21 | }) 22 | .catch(err => { 23 | console.log('getAccessToken err', err) 24 | }) 25 | }, 26 | } 27 | 28 | module.exports = LinkedInService 29 | -------------------------------------------------------------------------------- /backend/common/utils/dateUtils.js: -------------------------------------------------------------------------------- 1 | const moment = require('moment-timezone') 2 | 3 | module.exports = { 4 | formatDateInterval: (start, end) => { 5 | const mom1 = moment(start) 6 | const mom2 = moment(end) 7 | 8 | if (mom1.month() === mom2.month()) { 9 | if (mom1.date() === mom2.date()) { 10 | return mom1.format('MMMM D, YYYY') 11 | } 12 | return `${mom1.format('MMMM D')}-${mom2.format('D, YYYY')}` 13 | } 14 | if (mom1.year() === mom2.year()) { 15 | return `${mom1.format('MMMM D')} - ${mom2.format('MMMM D, YYYY')}` 16 | } 17 | return `${mom1.format('MMMM D, YYYY')} - ${mom2.format('MMMM D, YYYY')}` 18 | }, 19 | } 20 | -------------------------------------------------------------------------------- /backend/migrations/01-remove-project-unique-index.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | const Promise = require('bluebird') 3 | const logger = require('../misc/logger') 4 | 5 | module.exports = { 6 | index: 1, 7 | name: '01-remove-project-unique-index', 8 | description: 'Remove unique team and event index from Project schema', 9 | run: async () => { 10 | const indexes = await mongoose.model('Project').listIndexes() 11 | const indexToDelete = indexes.find( 12 | index => index.name === 'event_1_team_1', 13 | ) 14 | if (indexToDelete) { 15 | await mongoose 16 | .model('Project') 17 | .collection.dropIndex(indexToDelete.name) 18 | logger.info(`Unique index dropped`) 19 | } else { 20 | logger.info(`-> No indexes to delete`) 21 | } 22 | return Promise.resolve() 23 | }, 24 | } 25 | -------------------------------------------------------------------------------- /backend/migrations/02-rename-sigma_sq-to-sigmaSq.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | const Promise = require('bluebird') 3 | 4 | // const GavelController = require('../modules/reviewing/gavel/controller') 5 | 6 | module.exports = { 7 | index: 2, 8 | name: '02-rename-sigma_sq-to-sigmaSq', 9 | description: 'Rename sigma_sq in GaveProject to sigmaSq', 10 | run: async () => { 11 | /* 12 | const cursor = mongoose.model('Project').find().cursor() 13 | await cursor.eachAsync(async function (doc) { 14 | GavelController.ensureGavelProject(doc) 15 | }) 16 | */ 17 | const res = await mongoose 18 | .model('GavelProject') 19 | .updateMany( 20 | { sigma_sq: { $exists: true } }, 21 | { $rename: { sigma_sq: 'sigmaSq' } }, 22 | { multi: true }, 23 | ) 24 | console.log('Done with gavelproject', res.n, res.nModified) 25 | 26 | const psres = await mongoose 27 | .model('ProjectScore') 28 | .updateMany( 29 | { max_score: { $exists: true } }, 30 | { $rename: { max_score: 'maxScore' } }, 31 | { multi: true }, 32 | ) 33 | 34 | console.log('Done with ProjectScore', psres.n, psres.nModified) 35 | return Promise.resolve() 36 | }, 37 | } 38 | -------------------------------------------------------------------------------- /backend/migrations/03-rename-country_code-to-countryCode.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | const Promise = require('bluebird') 3 | 4 | module.exports = { 5 | index: 3, 6 | name: '03-rename-country_code-to-countryCode', 7 | description: 'Rename country_code in to countryCode', 8 | run: async () => { 9 | // REMINDER!!!! 10 | // WHEN MIGRATING FIELD A INTO FIELD B, BOTH A AND B MUST EXIST IN THE SCHEMA 11 | const res = await mongoose.model('Registration').updateMany( 12 | { 'answers.phoneNumber.country_code': { $exists: true } }, 13 | { 14 | $rename: { 15 | 'answers.phoneNumber.country_code': 16 | 'answers.phoneNumber.countryCode', 17 | }, 18 | }, 19 | ) 20 | console.log('Done with countryCode', res.n, res.nModified) 21 | const psres = await mongoose.model('UserProfile').updateMany( 22 | { 'phoneNumber.country_code': { $exists: true } }, 23 | { 24 | $rename: { 25 | 'phoneNumber.country_code': 'phoneNumber.countryCode', 26 | }, 27 | }, 28 | { multi: true }, 29 | ) 30 | console.log('Done with countryCode', psres.n, psres.nModified) 31 | 32 | return Promise.resolve() 33 | }, 34 | } 35 | -------------------------------------------------------------------------------- /backend/migrations/04-add-finalists-to-event.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | const Promise = require('bluebird') 3 | 4 | module.exports = { 5 | index: 4, 6 | name: '04-add-finalists-to-event', 7 | description: 'Add finalists in to event', 8 | run: async () => { 9 | const res = await mongoose 10 | .model('Event') 11 | .updateMany( 12 | { finalists: { $exists: false } }, 13 | { $set: { finalists: [] } }, 14 | ) 15 | console.log('Done with Events', res.n, res.nModified) 16 | 17 | return Promise.resolve() 18 | }, 19 | } 20 | -------------------------------------------------------------------------------- /backend/migrations/06-finals-active-to-event.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | const Promise = require('bluebird') 3 | 4 | module.exports = { 5 | index: 6, 6 | name: '06-finals-to-event', 7 | description: 'Add finals check to event', 8 | run: async () => { 9 | const res = await mongoose 10 | .model('Event') 11 | .updateMany( 12 | { finalsActive: { $exists: false } }, 13 | { $set: { finalsActive: false } }, 14 | ) 15 | 16 | console.log('Event', res.n, res.nModified) 17 | return Promise.resolve() 18 | }, 19 | } 20 | -------------------------------------------------------------------------------- /backend/migrations/08-create-registration-objects-in-profiles.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | const Promise = require('bluebird') 3 | 4 | // const UserProfileControlller = require('../modules/user-profile/controller') 5 | 6 | module.exports = { 7 | index: 8, 8 | name: '08-create-registration-objects-in-profiles', 9 | description: 'Sync registrationa and profile data', 10 | run: async () => { 11 | /* 12 | const cursor = mongoose.model('Registration').find({}).cursor() 13 | await cursor.eachAsync(async function (registration) { 14 | UserProfileControlller.syncRegistration(registration) 15 | }) 16 | */ 17 | return Promise.resolve() 18 | }, 19 | } 20 | -------------------------------------------------------------------------------- /backend/migrations/09-add-challengesenabled-false: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | const Promise = require('bluebird') 3 | 4 | module.exports = { 5 | index: 9, 6 | name: '09-add-challengesenabled-false', 7 | description: 'if no challenges, set challengesEnabled to false', 8 | run: async () => { 9 | const nres = await mongoose 10 | .model('Event') 11 | .updateMany( 12 | { challenges: { $exists: true, $size: 0 } }, 13 | { $set: { challengesEnabled: false } }, 14 | ) 15 | console.info('Done with challengesEnabled to false', nres.n, nres.nModified) 16 | return Promise.resolve() 17 | }, 18 | } -------------------------------------------------------------------------------- /backend/migrations/10-add-banner-priority-and-approved-to-event.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | const Promise = require('bluebird') 3 | 4 | // const UserProfileControlller = require('../modules/user-profile/controller') 5 | 6 | module.exports = { 7 | index: 10, 8 | name: '10-add-banner-priority-and-approved-to-event', 9 | description: 'add fields to event', 10 | run: async () => { 11 | const res = await mongoose 12 | .model('Event') 13 | .updateMany( 14 | { approved: { $exists: false } }, 15 | { $set: { approved: true } }, 16 | ) 17 | 18 | const nres = await mongoose 19 | .model('Event') 20 | .updateMany( 21 | { frontPagePriority: { $exists: false } }, 22 | { $set: { frontPagePriority: 0 } }, 23 | ) 24 | 25 | console.log( 26 | 'Done with event priority and approved', 27 | res.n, 28 | res.nModified, 29 | nres.n, 30 | nres.nModified, 31 | ) 32 | return Promise.resolve() 33 | }, 34 | } 35 | -------------------------------------------------------------------------------- /backend/migrations/12-fix-empty-senderEmail-in-event: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | const Promise = require('bluebird') 3 | 4 | module.exports = { 5 | index: 12, 6 | name: '12-fix-empty-senderEmail-in-event', 7 | description: 'Replace empty senderEmail with noreply@hackjunction.com', 8 | run: async () => { 9 | // Update emailConfig.senderEmail field for documents with an empty senderEmail 10 | const resSenderEmail = await mongoose 11 | .model('Event') 12 | .updateMany( 13 | { 'emailConfig.senderEmail': { $in: [null, ""] } }, 14 | { 15 | $set: { 16 | 'emailConfig.senderEmail': "noreply@hackjunction.com" 17 | } 18 | } 19 | ) 20 | 21 | console.log('Done updating empty senderEmail fields', resSenderEmail.n, resSenderEmail.nModified) 22 | 23 | return Promise.resolve() 24 | }, 25 | } -------------------------------------------------------------------------------- /backend/migrations/13-set-eventLocation-null-where-not-exists.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | const Promise = require('bluebird') 3 | 4 | module.exports = { 5 | index: 13, 6 | name: '13-set-eventLocation-null-where-not-exists', 7 | description: 'set eventLocation null where not exists', 8 | run: async () => { 9 | const nres = await mongoose 10 | .model('Event') 11 | .updateMany( 12 | { eventLocation: { $exists: false } }, 13 | { $set: { eventLocation: null } }, 14 | ) 15 | console.info('Done with event timeline', nres.n, nres.nModified) 16 | return Promise.resolve() 17 | }, 18 | } 19 | -------------------------------------------------------------------------------- /backend/migrations/15-add-meetingRooms-to-event.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | const Promise = require('bluebird') 3 | 4 | module.exports = { 5 | index: 15, 6 | name: '04-add-meetingRooms-to-event', 7 | description: 'Add meeting rooms in to event', 8 | run: async () => { 9 | const res = await mongoose 10 | .model('Event') 11 | .updateMany( 12 | { meetingsEnabled: { $exists: false } }, 13 | { $set: { meetingsEnabled: true } }, 14 | ) 15 | console.log('Done with meetingsEnabled', res.n, res.nModified) 16 | 17 | return Promise.resolve() 18 | }, 19 | } 20 | -------------------------------------------------------------------------------- /backend/migrations/17-add-specific-hackerpacks-to-event.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | const Promise = require('bluebird') 3 | 4 | module.exports = { 5 | index: 17, 6 | name: '17-add-specific-hackerpacks-to-event', 7 | description: 'Add specifc hackerpacks to event', 8 | run: async () => { 9 | const res = await mongoose.model('Event').updateMany( 10 | { hackerpacksEnabled: { $exists: false } }, 11 | 12 | { 13 | $set: { 14 | hackerpacksEnabled: true, 15 | }, 16 | }, 17 | ) 18 | console.log('Done with Events', res.n, res.nModified) 19 | 20 | return Promise.resolve() 21 | }, 22 | } 23 | -------------------------------------------------------------------------------- /backend/migrations/18-add-event-newsletter-link-to-event.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | const Promise = require('bluebird') 3 | 4 | module.exports = { 5 | index: 18, 6 | name: '18-add-event-newsletter-link-to-event', 7 | description: 'Add event newsletter link to event', 8 | run: async () => { 9 | // Update newsletter field 10 | const resNewsletter = await mongoose 11 | .model('Event') 12 | .updateMany( 13 | { newsletter: { $exists: false } }, 14 | { $set: { newsletter: '' } }, 15 | ) 16 | console.log( 17 | 'Done updating newsletter field', 18 | resNewsletter.n, 19 | resNewsletter.nModified, 20 | ) 21 | 22 | return Promise.resolve() 23 | }, 24 | } 25 | -------------------------------------------------------------------------------- /backend/migrations/22-rename-team-tagline-to-subtitle.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | const Promise = require('bluebird') 3 | const shortid = require('shortid') 4 | 5 | module.exports = { 6 | index: 22, 7 | name: '22-renamed-team-tagline-to-subtitle', 8 | description: 'Rename tagline to subtitle inside of the team model', 9 | run: async () => { 10 | const renameTaglineToSubtitle = await mongoose 11 | .model('Team') 12 | .updateMany( 13 | { tagline: { $exists: true } }, 14 | { $rename: { tagline: 'subtitle' } }, 15 | ) 16 | console.log( 17 | 'Done renaming tagline to subtitle', 18 | renameTaglineToSubtitle.n, 19 | renameTaglineToSubtitle.nModified, 20 | ) 21 | 22 | console.log('Done with migration 21') 23 | return Promise.resolve() 24 | }, 25 | } 26 | -------------------------------------------------------------------------------- /backend/migrations/23-add-timeline-to-event.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | const Promise = require('bluebird') 3 | 4 | module.exports = { 5 | index: 23, 6 | name: '23-add-timeline-to-event', 7 | description: 'add timeline event', 8 | run: async () => { 9 | const nres = await mongoose 10 | .model('Event') 11 | .updateMany( 12 | { eventTimeline: { $exists: false } }, 13 | { $set: { eventTimeline: { items: [] } } }, 14 | ) 15 | const bres = await mongoose 16 | .model('Event') 17 | .updateMany( 18 | { eventTimeline: null }, 19 | { $set: { eventTimeline: { items: [] } } }, 20 | ) 21 | console.info('Done with event timeline', nres.n, nres.nModified) 22 | return Promise.resolve() 23 | }, 24 | } 25 | -------------------------------------------------------------------------------- /backend/migrations/26-fix-empty-senderEmail-in-event.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | const Promise = require('bluebird') 3 | 4 | module.exports = { 5 | index: 26, 6 | name: '26-fix-empty-senderEmail-in-event', 7 | description: 'Replace empty senderEmail with noreply@hackjunction.com', 8 | run: async () => { 9 | // Update emailConfig.senderEmail field for documents with an empty senderEmail 10 | const resSenderEmail = await mongoose 11 | .model('Event') 12 | .updateMany( 13 | { 'emailConfig.senderEmail': { $in: [null, ""] } }, 14 | { 15 | $set: { 16 | 'emailConfig.senderEmail': "noreply@hackjunction.com" 17 | } 18 | } 19 | ) 20 | 21 | console.log('Done updating empty senderEmail fields', resSenderEmail.n, resSenderEmail.nModified) 22 | 23 | return Promise.resolve() 24 | }, 25 | } 26 | -------------------------------------------------------------------------------- /backend/migrations/27-add-experimental-flag-to-events.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | const Promise = require('bluebird') 3 | 4 | module.exports = { 5 | index: 27, 6 | name: '27-add-experimental-flag-to-events', 7 | description: 8 | 'Add new experimental flag to events, to allow events to use experimental features', 9 | run: async () => { 10 | const addExperimental = await mongoose.model('Event').updateMany( 11 | { experimental: { $exists: false } }, 12 | { 13 | $set: { 14 | experimental: false, 15 | }, 16 | }, 17 | ) 18 | 19 | console.log( 20 | 'Done adding experimental flag to events', 21 | addExperimental.n, 22 | addExperimental.nModified, 23 | ) 24 | 25 | return Promise.resolve() 26 | }, 27 | } 28 | -------------------------------------------------------------------------------- /backend/migrations/28-add-gavel-login-to-registrations.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | const Promise = require('bluebird') 3 | 4 | module.exports = { 5 | index: 28, 6 | name: '28-add-gavel-login-to-registrations', 7 | description: 'Add gavel login link to registrations', 8 | run: async () => { 9 | const addGavelLogin = await mongoose.model('Registration').updateMany( 10 | { gavelLogin: { $exists: false } }, 11 | { 12 | $set: { 13 | gavelLogin: '', 14 | }, 15 | }, 16 | ) 17 | 18 | console.log( 19 | 'Done adding gavel login link to registrations', 20 | addGavelLogin.n, 21 | addGavelLogin.nModified, 22 | ) 23 | 24 | return Promise.resolve() 25 | }, 26 | } 27 | -------------------------------------------------------------------------------- /backend/misc/gridfs.js: -------------------------------------------------------------------------------- 1 | const { resolve } = require('bluebird') 2 | const { reject } = require('lodash') 3 | const mongoose = require('mongoose') 4 | const Promise = require('bluebird') 5 | const multer = require('multer') 6 | const { GridFsStorage } = require('multer-gridfs-storage') 7 | var crypto = require('crypto') 8 | 9 | mongoose.Promise = Promise 10 | 11 | //create storrage engine 12 | const storage = new GridFsStorage({ 13 | url: global.gConfig.MONGODB_URI, 14 | file: (req, file) => { 15 | return new Promise((resolve, reject) => { 16 | //encrypt filename 17 | crypto.randomBytes(16, (err, buf) => { 18 | if (err) { 19 | return reject(err) 20 | } 21 | const filename = file.originalname 22 | const fileInfo = { 23 | filename: filename, 24 | bucketName: 'uploads' 25 | } 26 | resolve(fileInfo) 27 | }) 28 | }) 29 | } 30 | }) 31 | 32 | const upload = multer({ storage }) 33 | 34 | module.exports = { 35 | storage, 36 | upload, 37 | } -------------------------------------------------------------------------------- /backend/misc/logger.js: -------------------------------------------------------------------------------- 1 | const pino = require('pino') 2 | 3 | const prod = process.env.NODE_ENV === 'production' 4 | 5 | const opts = { 6 | prettyPrint: prod ? undefined : { colorize: true }, 7 | redact: ['res.headers', 'req.headers.authorization'], 8 | } 9 | 10 | const stream = pino.destination(1) 11 | const logger = pino(opts, stream) 12 | 13 | module.exports = logger 14 | -------------------------------------------------------------------------------- /backend/modules/alert/model.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | 3 | const AlertSchema = new mongoose.Schema({ 4 | id: { 5 | type: String, 6 | required: true, 7 | unique: true, 8 | }, 9 | content: { 10 | type: String, 11 | required: true, 12 | }, 13 | eventId: { 14 | type: String, 15 | required: true, 16 | }, 17 | sender: { 18 | type: String, 19 | required: true, 20 | }, 21 | sentAt: { 22 | type: Date, 23 | required: true, 24 | }, 25 | }) 26 | 27 | const Alert = mongoose.model('Alert', AlertSchema) 28 | 29 | module.exports = { 30 | Alert, 31 | AlertSchema, 32 | } 33 | -------------------------------------------------------------------------------- /backend/modules/auth/routes.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | 3 | const router = express.Router() 4 | 5 | module.exports = router 6 | -------------------------------------------------------------------------------- /backend/modules/cron/index.js: -------------------------------------------------------------------------------- 1 | const CronJob = require('cron').CronJob 2 | 3 | const generateEventResults = require('./jobs/generate-event-results.js') 4 | 5 | const patterns = { 6 | DAILY_2AM: '0 0 2 * * *', 7 | } 8 | 9 | const jobs = { 10 | GENERATE_EVENT_RESULTS: (runImmediately = false) => 11 | new CronJob( 12 | patterns.DAILY_2AM, 13 | generateEventResults(), 14 | null, 15 | runImmediately, 16 | 'Europe/Helsinki' 17 | ), 18 | } 19 | 20 | const utils = { 21 | startAll: () => { 22 | Object.keys(jobs).forEach(job => { 23 | jobs[job](true).start() 24 | }) 25 | }, 26 | } 27 | 28 | module.exports = { 29 | utils, 30 | jobs, 31 | patterns, 32 | } 33 | -------------------------------------------------------------------------------- /backend/modules/cron/jobs/generate-event-results.js: -------------------------------------------------------------------------------- 1 | const moment = require('moment-timezone') 2 | const Promise = require('bluebird') 3 | const Event = require('../../event/model') 4 | 5 | const RankingsController = require('../../rankings/controller') 6 | 7 | const job = async () => { 8 | const allEvents = await Event.find({}) 9 | 10 | /** Find all events which have ended */ 11 | const pastEvents = allEvents.filter(event => { 12 | return moment(event.endTime) 13 | .tz(event.timezone) 14 | .isBefore() 15 | }) 16 | 17 | /** */ 18 | 19 | Promise.each(pastEvents, async event => { 20 | const results = await RankingsController.resetAllResults(event) 21 | }) 22 | } 23 | 24 | module.exports = job 25 | -------------------------------------------------------------------------------- /backend/modules/email-task/model.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | 3 | const EmailTaskSchema = new mongoose.Schema({ 4 | params: { 5 | type: mongoose.Schema.Types.Mixed, 6 | default: null, 7 | }, 8 | schedule: { 9 | type: Date, 10 | default: Date.now, 11 | }, 12 | deliveredAt: { 13 | type: Date, 14 | default: null, 15 | }, 16 | event: { 17 | type: String, 18 | required: true, 19 | }, 20 | user: { 21 | type: String, 22 | required: true, 23 | }, 24 | type: { 25 | type: String, 26 | required: true, 27 | }, 28 | }) 29 | 30 | EmailTaskSchema.set('timestamps', true) 31 | EmailTaskSchema.index( 32 | { 33 | event: 1, 34 | user: 1, 35 | type: 1, 36 | }, 37 | { 38 | unique: true, 39 | } 40 | ) 41 | 42 | const EmailTask = mongoose.model('EmailTask', EmailTaskSchema) 43 | 44 | module.exports = EmailTask 45 | -------------------------------------------------------------------------------- /backend/modules/email-task/types.js: -------------------------------------------------------------------------------- 1 | const EmailTypes = { 2 | registrationAccepted: 'registration-accepted', 3 | registrationRejected: 'registration-rejected', 4 | registrationReceived: 'registration-received', 5 | travelGrantRejected: 'travelgrant-rejected', 6 | travelGrantAccepted: 'travelgrant-accepted', 7 | travelGrantDetailsRejected: 'travelgrant-details-rejected', 8 | travelGrantDetailsAccepted: 'travelgrant-details-accepted', 9 | recruiterMessage: 'recruiter-message', 10 | } 11 | 12 | module.exports = EmailTypes 13 | -------------------------------------------------------------------------------- /backend/modules/files/model.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | const Schema = mongoose.Schema 3 | 4 | const FileSchema = new Schema({ 5 | caption: { 6 | required: true, 7 | type: String, 8 | }, 9 | filename: { 10 | required: true, 11 | type: String, 12 | }, 13 | fileId: { 14 | required: true, 15 | type: String, 16 | }, 17 | createdAt: { 18 | default: Date.now(), 19 | type: Date, 20 | }, 21 | }) 22 | 23 | const File = mongoose.model('File', FileSchema) 24 | 25 | module.exports = File -------------------------------------------------------------------------------- /backend/modules/files/routes.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const asyncHandler = require('express-async-handler') 3 | const FileController = require('./controller.js') 4 | 5 | const router = express.Router() 6 | 7 | 8 | const uploadFile = asyncHandler(async (req, res) => { 9 | console.log(req.body) 10 | const upload = await FileController.uploadOne(req.body.caption, req.file) 11 | return res.status(200).json(upload) 12 | }) 13 | 14 | const getFileByName = asyncHandler(async (req, res) => { 15 | console.log(req.body) 16 | const response = await FileController.findImageByName(req.params.filename) 17 | return response 18 | }) 19 | 20 | const deleteFileById = asyncHandler(async (req, res) => { 21 | console.log(req.body) 22 | const response = await FileController.deleteFileById(req.params.id) 23 | return res.status(200).json({ 24 | success: true, 25 | message: `File with ID: ${req.params.id} deleted`, 26 | }) 27 | }) 28 | 29 | 30 | router 31 | .route('/') 32 | .post( 33 | uploadFile 34 | ) 35 | 36 | router 37 | .route('/:filename') 38 | .get( 39 | getFileByName 40 | ) 41 | 42 | router 43 | .route('/:id') 44 | .delete( 45 | deleteFileById 46 | ) 47 | 48 | 49 | 50 | module.exports = router -------------------------------------------------------------------------------- /backend/modules/filter-group/controller.js: -------------------------------------------------------------------------------- 1 | const FilterGroup = require('./model') 2 | 3 | const controller = {} 4 | const { NotFoundError } = require('../../common/errors/errors') 5 | 6 | controller.createFilterGroup = ( 7 | label, 8 | description, 9 | createdBy, 10 | eventId, 11 | filters 12 | ) => { 13 | const filterGroup = new FilterGroup({ 14 | label, 15 | description, 16 | createdBy, 17 | filters, 18 | event: eventId, 19 | }) 20 | 21 | return filterGroup.save() 22 | } 23 | 24 | controller.editFilterGroup = (label, description, sub, eventId, filters) => { 25 | return FilterGroup.findOne({ label, event: eventId }).then(filterGroup => { 26 | if (!filterGroup) 27 | throw new NotFoundError( 28 | `Filter group with label ${label} does not exist` 29 | ) 30 | filterGroup.description = description 31 | filterGroup.filters = filters 32 | return filterGroup.save() 33 | }) 34 | } 35 | 36 | controller.deleteFilterGroup = (label, eventId) => { 37 | return FilterGroup.findOneAndRemove({ label, event: eventId }) 38 | } 39 | 40 | controller.getFilterGroupsForEvent = eventId => { 41 | return FilterGroup.find({ event: eventId }) 42 | } 43 | 44 | module.exports = controller 45 | -------------------------------------------------------------------------------- /backend/modules/filter-group/model.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | const { FilterTypes } = require('@hackjunction/shared') 3 | 4 | const FilterGroupSchema = new mongoose.Schema({ 5 | label: { 6 | type: String, 7 | required: true, 8 | }, 9 | description: { 10 | type: String, 11 | }, 12 | event: { 13 | type: mongoose.Schema.Types.ObjectId, 14 | ref: 'Event', 15 | required: true, 16 | }, 17 | createdBy: { 18 | type: String, 19 | required: true, 20 | }, 21 | filters: [ 22 | { 23 | label: { 24 | type: String, 25 | }, 26 | path: { 27 | type: String, 28 | required: true, 29 | }, 30 | type: { 31 | type: String, 32 | required: true, 33 | enum: Object.keys(FilterTypes.filterTypes), 34 | }, 35 | value: { 36 | type: mongoose.Mixed, 37 | }, 38 | }, 39 | ], 40 | }) 41 | 42 | FilterGroupSchema.set('timestamps', true) 43 | FilterGroupSchema.index({ event: 1, label: 1 }, { unique: true }) 44 | 45 | const FilterGroup = mongoose.model('FilterGroup', FilterGroupSchema) 46 | 47 | module.exports = FilterGroup 48 | -------------------------------------------------------------------------------- /backend/modules/graphql-shared-types.js: -------------------------------------------------------------------------------- 1 | const { buildSchema } = require('graphql') 2 | const { SharedGraphQLTypes } = require('@hackjunction/shared/schemas') 3 | 4 | const SharedSchema = buildSchema(SharedGraphQLTypes) 5 | 6 | module.exports = SharedSchema._typeMap 7 | -------------------------------------------------------------------------------- /backend/modules/meeting/helpers.js: -------------------------------------------------------------------------------- 1 | const Meeting = require('./model') 2 | 3 | const updateMeetingGoogleInfo = async ( 4 | meetingId, 5 | googleEventId, 6 | googleMeetLink, 7 | ) => { 8 | try { 9 | const meeting = await Meeting.findOne({ _id: meetingId }) 10 | if (!meeting) return null 11 | meeting.googleEventId = googleEventId 12 | meeting.googleMeetLink = googleMeetLink 13 | return await meeting.save() 14 | } catch (err) { 15 | console.log('Failed to update meetign google info:', err) 16 | return null 17 | } 18 | } 19 | 20 | const cancelMeeting = async meetingId => { 21 | try { 22 | const meeting = await Meeting.findOne({ _id: meetingId }) 23 | if (!meeting) return null 24 | meeting.googleEventId = null 25 | meeting.googleMeetLink = null 26 | meeting.attendees = [] 27 | return await meeting.save() 28 | } catch (err) { 29 | console.log('Failed to update meetign google info:', err) 30 | return null 31 | } 32 | } 33 | 34 | module.exports = { 35 | updateMeetingGoogleInfo, 36 | cancelMeeting, 37 | } 38 | -------------------------------------------------------------------------------- /backend/modules/message/model.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | 3 | const MessageSchema = new mongoose.Schema({ 4 | id: { 5 | type: String, 6 | required: true, 7 | unique: true, 8 | }, 9 | content: { 10 | type: String, 11 | required: true, 12 | }, 13 | recipients: { 14 | type: Array, 15 | required: true, 16 | default: [], 17 | }, 18 | sender: { 19 | type: String, 20 | required: true, 21 | }, 22 | sentAt: { 23 | type: Date, 24 | required: true, 25 | }, 26 | readAt: { 27 | type: Date, 28 | default: null, 29 | }, 30 | }) 31 | 32 | const Message = mongoose.model('Message', MessageSchema) 33 | 34 | module.exports = { 35 | Message, 36 | MessageSchema, 37 | } 38 | -------------------------------------------------------------------------------- /backend/modules/newsletter/routes.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | 3 | const router = express.Router() 4 | const asyncHandler = require('express-async-handler') 5 | const SendgridService = require('../../common/services/sendgrid') 6 | 7 | const subscribeToNewsletter = asyncHandler(async (req, res) => { 8 | await SendgridService.subscribeToMailingList( 9 | req.body.email, 10 | req.body.country, 11 | global.gConfig.SENDGRID_MAILING_LIST_ID 12 | ) 13 | return res.sendStatus(200) 14 | }) 15 | 16 | router.route('/').post(subscribeToNewsletter) 17 | 18 | module.exports = router 19 | -------------------------------------------------------------------------------- /backend/modules/rankings/model.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | 3 | /** Schema for storing event results */ 4 | 5 | const TAG_TYPES = ['overall', 'finalists'] 6 | 7 | const RankingsSchema = new mongoose.Schema({ 8 | event: { 9 | type: mongoose.Schema.Types.ObjectId, 10 | ref: 'Event', 11 | }, 12 | tag: { 13 | type: String, 14 | enum: TAG_TYPES, 15 | }, 16 | track: { 17 | type: String, 18 | }, 19 | challenge: { 20 | type: String, 21 | }, 22 | rankings: [ 23 | { 24 | type: mongoose.Schema.Types.ObjectId, 25 | ref: 'Project', 26 | required: true, 27 | }, 28 | ], 29 | }) 30 | 31 | /** Only allow a single rankings document per event/track combination */ 32 | RankingsSchema.index( 33 | { 34 | event: 1, 35 | track: 1, 36 | challenge: 1, 37 | tag: 1, 38 | }, 39 | { 40 | unique: true, 41 | sparse: true, 42 | } 43 | ) 44 | 45 | RankingsSchema.set('timestamps', true) 46 | 47 | const Rankings = mongoose.model('Rankings', RankingsSchema) 48 | 49 | module.exports = Rankings 50 | -------------------------------------------------------------------------------- /backend/modules/registration/checklists.js: -------------------------------------------------------------------------------- 1 | const checklistItemsOnline = () => [ 2 | { 3 | name: 'checkbox1', 4 | title: '1st checkboks ONLINE', 5 | checked: false, 6 | }, 7 | { 8 | name: 'checkbox2', 9 | title: '2nd checkboks', 10 | checked: false, 11 | }, 12 | { 13 | name: 'checkbox3,', 14 | title: '3rd checkbox', 15 | checked: false, 16 | }, 17 | { 18 | name: 'checkbox4', 19 | title: '4th checkbox', 20 | checked: false, 21 | }, 22 | ] 23 | 24 | const checklistItemsPhysical = () => [ 25 | { 26 | name: 'checkbox1', 27 | title: '1st checkboks PHYSICAL', 28 | checked: false, 29 | }, 30 | { 31 | name: 'checkbox2', 32 | title: '2nd checkboks', 33 | checked: false, 34 | }, 35 | { 36 | name: 'checkbox3,', 37 | title: '3rd checkbox', 38 | checked: false, 39 | }, 40 | { 41 | name: 'checkbox4', 42 | title: '4th checkbox', 43 | checked: false, 44 | }, 45 | ] 46 | 47 | module.exports = { 48 | checklistItemsOnline, 49 | checklistItemsPhysical, 50 | } 51 | -------------------------------------------------------------------------------- /backend/modules/reviewing/gavel/Decision.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | 3 | const GavelDecisionSchema = new mongoose.Schema({ 4 | annotator: { 5 | type: mongoose.Schema.Types.ObjectId, 6 | ref: 'GavelAnnotator', 7 | required: true, 8 | }, 9 | event: { 10 | type: mongoose.Schema.Types.ObjectId, 11 | ref: 'Event', 12 | required: true, 13 | }, 14 | winner: { 15 | type: mongoose.Schema.Types.ObjectId, 16 | ref: 'GavelProject', 17 | required: true, 18 | }, 19 | loser: { 20 | type: mongoose.Schema.Types.ObjectId, 21 | ref: 'GavelProject', 22 | required: true, 23 | }, 24 | }) 25 | 26 | GavelDecisionSchema.set('timestamps', true) 27 | 28 | /** TODO: Is it necessary to index these? */ 29 | GavelDecisionSchema.index({ event: 1, annotator: 1 }) 30 | 31 | const GavelDecision = mongoose.model('GavelDecision', GavelDecisionSchema) 32 | 33 | module.exports = GavelDecision 34 | -------------------------------------------------------------------------------- /backend/modules/reviewing/gavel/settings.js: -------------------------------------------------------------------------------- 1 | /** Gavel reviewing algorithm configuration options - don't change unless you really know what you're doing! */ 2 | 3 | const Settings = { 4 | /** Gavel algorithm math constants */ 5 | ALPHA_PRIOR: 10.0, 6 | BETA_PRIOR: 1.0, 7 | MU_PRIOR: 0.0, 8 | SIGMA_SQ_PRIOR: 1.0, 9 | GAMMA: 0.1, 10 | LAMBDA: 1.0, 11 | KAPPA: 0.0001, 12 | EPSILON: 0.25, 13 | /** How long must an annotator wait between votes? */ 14 | ANNOTATOR_WAIT_SECONDS: 45, 15 | /** How long after being assigned a project is an annotator considered inactive? */ 16 | ANNOTATOR_TIMEOUT_MINS: 5, 17 | /** How many views must a project have to no longer be automatically prioritised? */ 18 | ITEM_MIN_VIEWS: 5, 19 | } 20 | 21 | module.exports = Settings 22 | -------------------------------------------------------------------------------- /backend/modules/sandbox/controller.js: -------------------------------------------------------------------------------- 1 | const { AlreadyExistsError } = require("../../common/errors/errors") 2 | 3 | const controller = {} 4 | 5 | controller.updateMetadata = async (userId, updates) => { 6 | // const user = await auth0.getUser({ id: userId }) 7 | // const metadata = { ...user.user_metadata, ...updates } 8 | // const updatedUser = await auth0.updateUserMetadata({ id: userId }, metadata) 9 | return "updatedUser" 10 | } 11 | 12 | 13 | 14 | module.exports = controller -------------------------------------------------------------------------------- /backend/modules/sandbox/routes.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const asyncHandler = require('express-async-handler') 3 | 4 | const AuthController = require('../auth/controller') 5 | 6 | const router = express.Router() 7 | 8 | const updateMetadata = asyncHandler(async (req, res) => { 9 | const userId = req.body.userId 10 | const data = req.body.data 11 | console.log(userId, data, body) 12 | //const update = await AuthController.updateMetadata(userId, data) 13 | return res.status(200).json("update") 14 | }) 15 | 16 | 17 | 18 | // router 19 | // .route('/metadata') 20 | // .patch( 21 | // updateMetadata, 22 | // ) 23 | 24 | 25 | 26 | module.exports = router 27 | -------------------------------------------------------------------------------- /backend/modules/user-profile/helpers.js: -------------------------------------------------------------------------------- 1 | const { RegistrationFields } = require('@hackjunction/shared') 2 | const yup = require('yup') 3 | 4 | const UserProfileHelpers = { 5 | validate: data => { 6 | const validations = {} 7 | Object.keys(data).forEach(field => { 8 | const fieldConfig = RegistrationFields.getField(field) 9 | if (fieldConfig) { 10 | validations[field] = fieldConfig.validationSchema(false) 11 | } 12 | }) 13 | 14 | validations.avatar = yup 15 | .string() 16 | .url() 17 | .nullable() 18 | 19 | const schema = yup.object().shape(validations) 20 | return schema.validate(data, { stripUnknown: true }) 21 | }, 22 | } 23 | 24 | module.exports = UserProfileHelpers 25 | -------------------------------------------------------------------------------- /backend/modules/voting-token/model.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | 3 | const VotingTokenSchema = new mongoose.Schema({ 4 | name: { 5 | type: String, 6 | }, 7 | project: { 8 | type: mongoose.Schema.Types.ObjectId, 9 | ref: 'Project', 10 | }, 11 | event: { 12 | type: mongoose.Schema.Types.ObjectId, 13 | ref: 'Event', 14 | required: true, 15 | }, 16 | createdBy: { 17 | type: String, 18 | required: true, 19 | }, 20 | isRevoked: { 21 | type: Boolean, 22 | default: false, 23 | }, 24 | revokedAt: { 25 | type: Date, 26 | default: null, 27 | }, 28 | }) 29 | 30 | VotingTokenSchema.set('timestamps', true) 31 | 32 | const VotingToken = mongoose.model('VotingToken', VotingTokenSchema) 33 | 34 | module.exports = VotingToken 35 | -------------------------------------------------------------------------------- /backend/modules/winner-votes/model.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | 3 | const WinnerVoteSchema = new mongoose.Schema({ 4 | event: { 5 | type: mongoose.Schema.Types.ObjectId, 6 | ref: 'Event', 7 | required: true, 8 | }, 9 | user: { 10 | type: String, 11 | required: true, 12 | }, 13 | project: { 14 | type: mongoose.Schema.Types.ObjectId, 15 | ref: 'Project', 16 | required: true, 17 | }, 18 | }) 19 | 20 | WinnerVoteSchema.set('timestamps', true) 21 | WinnerVoteSchema.index( 22 | { 23 | user: 1, 24 | event: 1, 25 | }, 26 | { 27 | unique: true, 28 | }, 29 | ) 30 | 31 | const WinnerVote = mongoose.model('WinnerVote', WinnerVoteSchema) 32 | 33 | module.exports = WinnerVote 34 | -------------------------------------------------------------------------------- /backend/nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "events": { 3 | "restart": "kill-port 2222", 4 | "crash": "kill-port 2222" 5 | }, 6 | "delay": "1500" 7 | } -------------------------------------------------------------------------------- /backend/utils/permissions.js: -------------------------------------------------------------------------------- 1 | const PermissionsUtils = { 2 | userHasPermission: (user, requiredPermission) => { 3 | if (!user) return false 4 | return ( 5 | user && 6 | user.permissions && 7 | user.permissions.indexOf(requiredPermission) !== -1 8 | ) 9 | }, 10 | userHasPermissions: (user, requiredPermissions) => { 11 | if (!user) return false 12 | try { 13 | requiredPermissions.forEach(permission => { 14 | if (!PermissionsUtils.userHasPermission(user, permission)) { 15 | throw new Error( 16 | `User does not have a required permission: ${permission}`, 17 | ) 18 | } 19 | }) 20 | return true 21 | } catch (e) { 22 | return false 23 | } 24 | }, 25 | } 26 | 27 | module.exports = PermissionsUtils 28 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | services: 3 | mongodb: 4 | image: mongo 5 | container_name: mongodb_stag 6 | environment: 7 | - PUID=1000 8 | - PGID=1000 9 | ports: 10 | - 27012:27017 11 | volumes: 12 | - ../documents/databases/dump 13 | restart: unless-stopped 14 | -------------------------------------------------------------------------------- /frontend/.babelrc.js: -------------------------------------------------------------------------------- 1 | const plugins = [ 2 | [ 3 | 'babel-plugin-import', 4 | { 5 | libraryName: '@material-ui/core', 6 | // Use "'libraryDirectory': ''," if your bundler does not support ES modules 7 | libraryDirectory: 'esm', 8 | camel2DashComponentName: false, 9 | }, 10 | 'core', 11 | ], 12 | [ 13 | 'babel-plugin-import', 14 | { 15 | libraryName: '@material-ui/icons', 16 | // Use "'libraryDirectory': ''," if your bundler does not support ES modules 17 | libraryDirectory: 'esm', 18 | camel2DashComponentName: false, 19 | }, 20 | 'icons', 21 | ], 22 | ] 23 | 24 | module.exports = { plugins } 25 | -------------------------------------------------------------------------------- /frontend/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["react-app", "plugin:prettier/recommended"], 3 | "env": { 4 | "jest": true, 5 | "browser": true 6 | }, 7 | "rules": { 8 | "import/no-extraneous-dependencies": "off", 9 | "import/prefer-default-export": "off", 10 | "no-confusing-arrow": "off", 11 | "linebreak-style": "off", 12 | "arrow-parens": ["error", "as-needed"], 13 | "comma-dangle": [ 14 | "error", 15 | { 16 | "arrays": "always-multiline", 17 | "objects": "always-multiline", 18 | "imports": "always-multiline", 19 | "exports": "always-multiline", 20 | "functions": "ignore" 21 | } 22 | ], 23 | "no-plusplus": "off", 24 | "import/no-anonymous-default-export": "off" 25 | }, 26 | "parser": "babel-eslint", 27 | "plugins": ["react"], 28 | "globals": { 29 | "browser": true, 30 | "$": true, 31 | "before": true, 32 | "document": true 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | /server/build 14 | 15 | # misc 16 | .DS_Store 17 | .env 18 | .env.local 19 | .env.development.local 20 | .env.test.local 21 | .env.production.local 22 | 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | /shared 28 | 29 | -------------------------------------------------------------------------------- /frontend/config-overrides.js: -------------------------------------------------------------------------------- 1 | const { override, useBabelRc } = require('customize-cra') 2 | 3 | module.exports = override(useBabelRc()) 4 | -------------------------------------------------------------------------------- /frontend/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es2016", 5 | "jsx": "preserve", 6 | "checkJs": true, 7 | "baseUrl": "./src" 8 | }, 9 | "exclude": ["node_modules", "**/node_modules/*"] 10 | } 11 | -------------------------------------------------------------------------------- /frontend/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [require('tailwindcss'), require('autoprefixer')], 3 | } 4 | -------------------------------------------------------------------------------- /frontend/public/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackjunction/JunctionApp/393217599050b43d144c35b3dbf1d3f63997be37/frontend/public/android-chrome-192x192.png -------------------------------------------------------------------------------- /frontend/public/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackjunction/JunctionApp/393217599050b43d144c35b3dbf1d3f63997be37/frontend/public/android-chrome-512x512.png -------------------------------------------------------------------------------- /frontend/public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackjunction/JunctionApp/393217599050b43d144c35b3dbf1d3f63997be37/frontend/public/apple-touch-icon.png -------------------------------------------------------------------------------- /frontend/public/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #ffffff 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /frontend/public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackjunction/JunctionApp/393217599050b43d144c35b3dbf1d3f63997be37/frontend/public/favicon-16x16.png -------------------------------------------------------------------------------- /frontend/public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackjunction/JunctionApp/393217599050b43d144c35b3dbf1d3f63997be37/frontend/public/favicon-32x32.png -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackjunction/JunctionApp/393217599050b43d144c35b3dbf1d3f63997be37/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Junction App", 3 | "name": "Junction App HackPlatform", 4 | "default_locale": "en", 5 | "icons": [ 6 | { 7 | "src": "favicon.ico", 8 | "sizes": "64x64 32x32 24x24 16x16", 9 | "type": "image/x-icon" 10 | } 11 | ], 12 | "start_url": ".", 13 | "display": "standalone", 14 | "theme_color": "#000000", 15 | "background_color": "#ffffff" 16 | } 17 | -------------------------------------------------------------------------------- /frontend/public/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackjunction/JunctionApp/393217599050b43d144c35b3dbf1d3f63997be37/frontend/public/mstile-150x150.png -------------------------------------------------------------------------------- /frontend/public/site.webmanifest: -------------------------------------------------------------------------------- 1 | {"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} -------------------------------------------------------------------------------- /frontend/src/assets/images/dashboardDefault.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackjunction/JunctionApp/393217599050b43d144c35b3dbf1d3f63997be37/frontend/src/assets/images/dashboardDefault.jpg -------------------------------------------------------------------------------- /frontend/src/assets/images/default_cover_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackjunction/JunctionApp/393217599050b43d144c35b3dbf1d3f63997be37/frontend/src/assets/images/default_cover_image.png -------------------------------------------------------------------------------- /frontend/src/assets/images/default_thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackjunction/JunctionApp/393217599050b43d144c35b3dbf1d3f63997be37/frontend/src/assets/images/default_thumbnail.png -------------------------------------------------------------------------------- /frontend/src/assets/images/laser_2016.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackjunction/JunctionApp/393217599050b43d144c35b3dbf1d3f63997be37/frontend/src/assets/images/laser_2016.jpg -------------------------------------------------------------------------------- /frontend/src/assets/images/visa_signature.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackjunction/JunctionApp/393217599050b43d144c35b3dbf1d3f63997be37/frontend/src/assets/images/visa_signature.jpg -------------------------------------------------------------------------------- /frontend/src/assets/logos/JO_wordmark_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackjunction/JunctionApp/393217599050b43d144c35b3dbf1d3f63997be37/frontend/src/assets/logos/JO_wordmark_black.png -------------------------------------------------------------------------------- /frontend/src/assets/logos/JO_wordmark_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackjunction/JunctionApp/393217599050b43d144c35b3dbf1d3f63997be37/frontend/src/assets/logos/JO_wordmark_white.png -------------------------------------------------------------------------------- /frontend/src/assets/logos/emblem_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackjunction/JunctionApp/393217599050b43d144c35b3dbf1d3f63997be37/frontend/src/assets/logos/emblem_black.png -------------------------------------------------------------------------------- /frontend/src/assets/logos/emblem_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackjunction/JunctionApp/393217599050b43d144c35b3dbf1d3f63997be37/frontend/src/assets/logos/emblem_white.png -------------------------------------------------------------------------------- /frontend/src/assets/logos/flagCN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackjunction/JunctionApp/393217599050b43d144c35b3dbf1d3f63997be37/frontend/src/assets/logos/flagCN.png -------------------------------------------------------------------------------- /frontend/src/assets/logos/flagUK.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackjunction/JunctionApp/393217599050b43d144c35b3dbf1d3f63997be37/frontend/src/assets/logos/flagUK.png -------------------------------------------------------------------------------- /frontend/src/components/PricingMenu/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useDispatch } from 'react-redux' 3 | import { push } from 'connected-react-router' 4 | 5 | import { Box } from '@material-ui/core' 6 | import Button from 'components/generic/Button' 7 | 8 | export default () => { 9 | const dispatch = useDispatch() 10 | 11 | return ( 12 | 13 | 21 | 22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /frontend/src/components/Team/TeamMembers/index.js: -------------------------------------------------------------------------------- 1 | import { Typography } from '@material-ui/core' 2 | import ParticipantPreview from 'components/Participant/ParticipantPreview' 3 | import React from 'react' 4 | import { useTranslation } from 'react-i18next' 5 | 6 | export default ({ 7 | viewModeStyle = 'list', 8 | teamMembers = [], 9 | enabledTeamMemberView = false, 10 | }) => { 11 | const { t } = useTranslation() 12 | return ( 13 |
14 | 19 | {t('Team_members_')} 20 | {/* Team members */} 21 | 22 | {teamMembers?.map((member, index) => { 23 | return ( 24 | 30 | ) 31 | })} 32 |
33 | ) 34 | } 35 | -------------------------------------------------------------------------------- /frontend/src/components/animated/FadeInWrapper.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { motion } from 'framer-motion' 3 | 4 | const FadeInWrapper = props => { 5 | const variants = { 6 | visible: { 7 | opacity: 1, 8 | y: 0, 9 | transition: { 10 | when: 'beforeChildren', 11 | duration: 0.2, 12 | delay: props.enterDelay || 0, 13 | }, 14 | }, 15 | hidden: { 16 | opacity: 0, 17 | y: props.verticalOffset || 0, 18 | transition: { 19 | duration: 0.2, 20 | }, 21 | }, 22 | } 23 | 24 | return ( 25 | 32 | ) 33 | } 34 | 35 | export default FadeInWrapper 36 | -------------------------------------------------------------------------------- /frontend/src/components/animated/StaggeredList.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { motion } from 'framer-motion' 3 | 4 | const StaggeredList = props => { 5 | const variants = { 6 | visible: { 7 | opacity: 1, 8 | transition: { 9 | delay: props.enterDelay || 0, 10 | when: 'beforeChildren', 11 | staggerChildren: props.staggerDelay || 0.1, 12 | }, 13 | }, 14 | hidden: { 15 | opacity: 0, 16 | transition: { 17 | when: 'afterChildren', 18 | staggerChildren: 0.05, 19 | }, 20 | }, 21 | collapsed: { 22 | opacity: 0, 23 | height: 0, 24 | overflow: 'hidden', 25 | transition: { 26 | when: 'afterChildren', 27 | staggerChildren: 0.05, 28 | }, 29 | }, 30 | } 31 | return ( 32 | 39 | ) 40 | } 41 | 42 | export default StaggeredList 43 | -------------------------------------------------------------------------------- /frontend/src/components/animated/StaggeredListItem.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { motion } from 'framer-motion' 3 | 4 | const StaggeredListItem = props => { 5 | const variants = { 6 | visible: { 7 | opacity: 1, 8 | y: 0, 9 | }, 10 | hidden: { 11 | opacity: 0, 12 | y: props.staggerDistance || 50, 13 | }, 14 | } 15 | return 16 | } 17 | 18 | export default StaggeredListItem 19 | -------------------------------------------------------------------------------- /frontend/src/components/buttons/StepButtons.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { makeStyles } from '@material-ui/core/styles' 4 | import { Button, Box } from '@material-ui/core' 5 | import { useTranslation } from 'react-i18next' 6 | const useButtonStyles = makeStyles(theme => ({ 7 | button: { 8 | marginLeft: theme.spacing(2), 9 | }, 10 | })) 11 | 12 | const StepButtons = ({ numSteps, activeStep, onBack, onNext, onFinish }) => { 13 | const classes = useButtonStyles() 14 | const { t } = useTranslation() 15 | return ( 16 | 17 | 24 | 32 | 33 | ) 34 | } 35 | 36 | export default StepButtons 37 | -------------------------------------------------------------------------------- /frontend/src/components/calendar/calendarView.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackjunction/JunctionApp/393217599050b43d144c35b3dbf1d3f63997be37/frontend/src/components/calendar/calendarView.js -------------------------------------------------------------------------------- /frontend/src/components/challenges/ChallengeDetail.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Box, Divider } from '@material-ui/core' 3 | 4 | import GradientBox from 'components/generic/GradientBox' 5 | import ChallengeSection from './ChallengeSection' 6 | 7 | const makeBoxStyles = focus => ({ 8 | boxShadow: `2px 7px 30px rgb(0 0 0 / ${focus ? '12' : '4'}%)`, 9 | cursor: 'pointer', 10 | }) 11 | 12 | const ChallengeDetail = ({ 13 | partner, 14 | title, 15 | subtitle, 16 | logo, 17 | link, 18 | isFocused, 19 | }) => { 20 | return ( 21 | <> 22 | 23 | 28 | 35 | 36 | 37 | 38 | 39 | 40 | ) 41 | } 42 | export default ChallengeDetail -------------------------------------------------------------------------------- /frontend/src/components/events/EventPageScript/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import EventUtils from 'utils/events' 3 | 4 | export default ({ event, pageId }) => { 5 | React.useEffect(() => { 6 | const wrapper = document.createElement('div') 7 | wrapper.id = 'custom-scripts' 8 | const scriptEl = document 9 | .createRange() 10 | .createContextualFragment( 11 | EventUtils.getApprovedEventPageScripts(event, pageId), 12 | ) 13 | wrapper.appendChild(scriptEl) 14 | document.body.appendChild(wrapper) 15 | return () => { 16 | wrapper.remove() 17 | } 18 | }, [event, pageId]) 19 | return null 20 | } 21 | -------------------------------------------------------------------------------- /frontend/src/components/events/EventPageScriptIFrame/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import EventUtils from 'utils/events' 3 | 4 | export default ({ pageId, slug, event }) => { 5 | return EventUtils.getApprovedEventPageScripts(event, pageId) ? ( 6 | 10 | ) : null 11 | } 12 | -------------------------------------------------------------------------------- /frontend/src/components/generic/DescriptionItem/DescriptionItem.module.scss: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | display: flex; 3 | flex-direction: column; 4 | 5 | background: #f8f8f8; 6 | padding: 1rem; 7 | margin-bottom: 1rem; 8 | } 9 | -------------------------------------------------------------------------------- /frontend/src/components/generic/Divider/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { makeStyles } from '@material-ui/core/styles' 3 | 4 | const useStyles = makeStyles(theme => ({ 5 | root: ({ size }) => ({ 6 | width: theme.spacing(size), 7 | height: theme.spacing(size), 8 | }), 9 | })) 10 | 11 | export default ({ size = 1 }) => { 12 | const classes = useStyles({ size }) 13 | 14 | return
15 | } 16 | -------------------------------------------------------------------------------- /frontend/src/components/generic/ErrorsBox/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { Box, Typography } from '@material-ui/core' 4 | import { makeStyles } from '@material-ui/core/styles' 5 | import MiscUtils from 'utils/misc' 6 | 7 | const useStyles = makeStyles(theme => ({ 8 | title: { 9 | color: theme.palette.error.main, 10 | fontWeight: 'bold', 11 | }, 12 | error: { 13 | color: theme.palette.error.main, 14 | }, 15 | })) 16 | 17 | const ErrorsBox = ({ title = 'Please correct the following', errors }) => { 18 | const classes = useStyles() 19 | return ( 20 | 21 | 22 | {title} 23 | 24 | 25 | {MiscUtils.parseFormikErrors(errors).map(error => ( 26 | 27 | * {error} 28 | 29 | ))} 30 | 31 | 32 | ) 33 | } 34 | 35 | export default ErrorsBox 36 | -------------------------------------------------------------------------------- /frontend/src/components/generic/GradientBox/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { makeStyles } from '@material-ui/core/styles' 4 | import { Box } from '@material-ui/core' 5 | 6 | const useStyles = makeStyles(theme => ({ 7 | root: ({ color, radius }) => { 8 | const _color = theme.palette[color] 9 | const bg = `linear-gradient(145deg, ${_color.dark} 0%, ${_color.main} 100%)` 10 | return { 11 | background: bg, 12 | borderRadius: radius || '13px', 13 | color: _color.contrastText, 14 | boxShadow: '0px 3px 15px rgba(0,0,0,0.1)', 15 | // transitionDuration: '2s', 16 | // transitionProperty: 'all', 17 | // transitionTimingFunction: 'ease' 18 | } 19 | }, 20 | })) 21 | 22 | const GradientBox = ({ color, radius, children, ...boxProps }) => { 23 | const classes = useStyles({ color, radius }) 24 | return ( 25 | 26 | {children} 27 | 28 | ) 29 | } 30 | 31 | export default GradientBox 32 | -------------------------------------------------------------------------------- /frontend/src/components/generic/LineDivider/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { makeStyles } from '@material-ui/core/styles' 3 | 4 | const useStyles = makeStyles(theme => ({ 5 | root: { 6 | height: '1px', 7 | background: '#f8f8f8', 8 | width: '100%', 9 | maxWidth: '640px', 10 | margin: '0 auto', 11 | }, 12 | })) 13 | 14 | const LineDivider = () => { 15 | const classes = useStyles() 16 | return
17 | } 18 | 19 | export default LineDivider 20 | -------------------------------------------------------------------------------- /frontend/src/components/generic/List/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackjunction/JunctionApp/393217599050b43d144c35b3dbf1d3f63997be37/frontend/src/components/generic/List/index.js -------------------------------------------------------------------------------- /frontend/src/components/generic/Tag/Variants.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { withStyles, lighten } from '@material-ui/core/styles' 3 | import { Chip } from '@material-ui/core' 4 | 5 | export const Yes = withStyles(theme => ({ 6 | root: { 7 | backgroundColor: lighten('#00ff00', 0.4), 8 | }, 9 | label: { 10 | ...theme.typography.overline, 11 | fontWeight: 'bold', 12 | color: 'white', 13 | }, 14 | }))(({ classes }) => ) 15 | 16 | export const No = withStyles(theme => ({ 17 | root: { 18 | backgroundColor: lighten('#ff0000', 0.4), 19 | }, 20 | label: { 21 | ...theme.typography.overline, 22 | fontWeight: 'bold', 23 | color: 'white', 24 | }, 25 | }))(({ classes }) => ) 26 | 27 | export const NotAvailable = withStyles(theme => ({ 28 | root: { 29 | backgroundColor: lighten('#ffa500', 0.2), 30 | }, 31 | label: { 32 | ...theme.typography.overline, 33 | fontWeight: 'bold', 34 | color: 'white', 35 | }, 36 | }))(({ classes }) => ) 37 | -------------------------------------------------------------------------------- /frontend/src/components/generic/Tag/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | 4 | import { makeStyles } from '@material-ui/core/styles' 5 | import { Chip, Avatar } from '@material-ui/core' 6 | 7 | const useStyles = makeStyles(theme => ({ 8 | label: ({ color }) => ({ 9 | ...theme.typography.overline, 10 | }), 11 | avatar: ({ color }) => ({ 12 | backgroundColor: color, 13 | }), 14 | })) 15 | 16 | const propTypes = { 17 | color: PropTypes.string.isRequired, 18 | label: PropTypes.string.isRequired, 19 | } 20 | 21 | const defaultProps = { 22 | color: 'lightgrey', 23 | } 24 | 25 | const Tag = ({ color, label }) => { 26 | const classes = useStyles({ color }) 27 | return ( 28 | {''}} 30 | classes={classes} 31 | size="small" 32 | label={label} 33 | /> 34 | ) 35 | } 36 | 37 | Tag.propTypes = propTypes 38 | Tag.defaultProps = defaultProps 39 | 40 | export default Tag 41 | -------------------------------------------------------------------------------- /frontend/src/components/generic/TimelineDot/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { makeStyles } from '@material-ui/core/styles' 4 | import CheckIcon from '@material-ui/icons/Check' 5 | 6 | const useStyles = makeStyles(theme => ({ 7 | root: { 8 | width: '14px', 9 | height: '14px', 10 | display: 'flex', 11 | alignItems: 'center', 12 | justifyContent: 'center', 13 | margin: 0, 14 | }, 15 | dot: ({ active, accentColor }) => ({ 16 | width: '14px', 17 | height: '14px', 18 | borderStyle: 'solid', 19 | borderRadius: '50%', 20 | borderColor: active ? accentColor || '#19DDEA' : '#ccc', 21 | backgroundColor: active ? 'transparent' : accentColor || '#19DDEA', 22 | borderWidth: '1px', 23 | }), 24 | })) 25 | 26 | const TimelineDot = ({ active, completed, accentColor = undefined }) => { 27 | const classes = useStyles({ active, accentColor }) 28 | return ( 29 |
30 | {completed ? ( 31 | 32 | ) : ( 33 |
34 | )} 35 |
36 | ) 37 | } 38 | 39 | export default TimelineDot 40 | -------------------------------------------------------------------------------- /frontend/src/components/generic/UserListItem/OrganiserListItem.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useSelector } from 'react-redux' 3 | import * as OrganiserSelectors from 'redux/organiser/selectors' 4 | import UserListItem from './index' 5 | 6 | export default ({ userId }) => { 7 | const organisersMap = useSelector(OrganiserSelectors.organisersMap) 8 | return 9 | } 10 | -------------------------------------------------------------------------------- /frontend/src/components/generic/UserListItem/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { 3 | ListItem, 4 | ListItemAvatar, 5 | ListItemText, 6 | Avatar, 7 | } from '@material-ui/core' 8 | 9 | const UserListItem = ({ 10 | user, 11 | selectable = false, 12 | selected = false, 13 | onSelect = () => {}, 14 | }) => { 15 | const userName = user ? `${user.firstName} ${user.lastName}` : '' 16 | const userEmail = user ? user.email : '' 17 | 18 | return ( 19 | 20 | {user ? ( 21 | <> 22 | 23 | 24 | 25 | 26 | 27 | ) : ( 28 | 29 | )} 30 | 31 | ) 32 | } 33 | 34 | export default UserListItem 35 | -------------------------------------------------------------------------------- /frontend/src/components/generic/_Table/PageSelect.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { Box, Typography, IconButton } from '@material-ui/core' 4 | import NavigateNextIcon from '@material-ui/icons/NavigateNext' 5 | import NavigateBeforeIcon from '@material-ui/icons/NavigateBefore' 6 | 7 | export default ({ 8 | pageIndex, 9 | pageCount, 10 | canPreviousPage, 11 | previousPage, 12 | canNextPage, 13 | nextPage, 14 | pageSize, 15 | }) => { 16 | return ( 17 | 18 | 19 | 20 | 21 | 22 | 23 | Page {pageIndex + 1} of {pageCount} 24 | 25 | 26 | 27 | 28 | 29 | 30 | ) 31 | } 32 | -------------------------------------------------------------------------------- /frontend/src/components/generic/_Table/PageSizeSelect.js: -------------------------------------------------------------------------------- 1 | import React, { useCallback } from 'react' 2 | 3 | import { Box, Button, ButtonGroup } from '@material-ui/core' 4 | 5 | export default ({ gotoPage, pageSize, setPageSize }) => { 6 | const opts = [ 7 | [10, 10], 8 | [25, 25], 9 | [50, 50], 10 | [100, 100], 11 | ['All', 100000], 12 | ] 13 | 14 | const _setPageSize = useCallback( 15 | size => { 16 | setPageSize(size) 17 | gotoPage(0) 18 | }, 19 | [setPageSize, gotoPage], 20 | ) 21 | 22 | return ( 23 | 24 | 25 | {opts.map(([label, value]) => ( 26 | 33 | ))} 34 | 35 | 36 | ) 37 | } 38 | -------------------------------------------------------------------------------- /frontend/src/components/generic/_Table/filterFunctions.js: -------------------------------------------------------------------------------- 1 | import * as FilterTypes from './filterTypes' 2 | 3 | export default { 4 | [FilterTypes.SINGLE_SELECT]: (rows, id, filterValue) => { 5 | if (!filterValue) return rows 6 | return rows.filter(row => row.values[id] === filterValue) 7 | }, 8 | [FilterTypes.MULTIPLE_SELECT]: (rows, id, filterValue) => { 9 | if (!Array.isArray(filterValue) || filterValue.length === 0) return rows 10 | return rows.filter(row => filterValue.indexOf(row.values[id]) !== -1) 11 | }, 12 | [FilterTypes.CONTAINS_SEARCH]: (rows, id, filterValue) => { 13 | return rows.filter(row => row.values[id]?.indexOf(filterValue) !== -1) 14 | }, 15 | [FilterTypes.EXACT_SEARCH]: (rows, id, filterValue) => { 16 | return rows.filter(row => row.values[id] === filterValue) 17 | }, 18 | } 19 | -------------------------------------------------------------------------------- /frontend/src/components/generic/_Table/filterTypes.js: -------------------------------------------------------------------------------- 1 | export const CONTAINS_SEARCH = 'contains-search' 2 | export const FUZZY_SEARCH = 'fuzzy-search' 3 | export const EXACT_SEARCH = 'exact-search' 4 | export const SINGLE_SELECT = 'single-select' 5 | export const MULTIPLE_SELECT = 'multiple-select' 6 | -------------------------------------------------------------------------------- /frontend/src/components/generic/_Table/filters/ContainsSearch.js: -------------------------------------------------------------------------------- 1 | import React, { useCallback } from 'react' 2 | import { TextField } from '@material-ui/core' 3 | 4 | import * as FilterTypes from '../filterTypes' 5 | 6 | const Component = ({ column }) => { 7 | const { filterValue, setFilter } = column 8 | const handleChange = useCallback( 9 | e => { 10 | setFilter(e.target.value) 11 | }, 12 | [setFilter], 13 | ) 14 | 15 | return ( 16 | 24 | ) 25 | } 26 | 27 | const ContainsSearchFilter = { 28 | Filter: Component, 29 | filter: FilterTypes.CONTAINS_SEARCH, 30 | } 31 | 32 | export default ContainsSearchFilter 33 | -------------------------------------------------------------------------------- /frontend/src/components/generic/_Table/index.js: -------------------------------------------------------------------------------- 1 | import Table from './Table' 2 | import * as SortFunctions from './sortFunctions' 3 | import SingleSelectFilter from './filters/SingleSelectFilter' 4 | import MultipleSelectFilter from './filters/MultipleSelectFilter' 5 | import ContainsSearchFilter from './filters/ContainsSearch' 6 | 7 | const Filters = { 8 | SingleSelect: SingleSelectFilter, 9 | MultipleSelect: MultipleSelectFilter, 10 | ContainsSearch: ContainsSearchFilter, 11 | Disabled: { 12 | Filter: () => null, 13 | filter: null, 14 | canFilter: false, 15 | }, 16 | } 17 | 18 | const Sorters = { 19 | Numeric: { 20 | sortType: SortFunctions.Numeric, 21 | disableSortBy: false, 22 | }, 23 | Alphabetic: { 24 | sortType: SortFunctions.Alphabetic, 25 | disableSortBy: false, 26 | }, 27 | DateTime: { 28 | sortType: SortFunctions.DateTime, 29 | disableSortBy: false, 30 | }, 31 | ArrayLength: { 32 | sortType: SortFunctions.ArrayLength, 33 | disableSortBy: false, 34 | }, 35 | Disabled: { 36 | disableSortBy: true, 37 | }, 38 | Default: { 39 | disableSortBy: false, 40 | }, 41 | } 42 | 43 | export { Table, Filters, Sorters } 44 | -------------------------------------------------------------------------------- /frontend/src/components/generic/_Table/sortFunctions.js: -------------------------------------------------------------------------------- 1 | import { memoize } from 'lodash-es' 2 | 3 | const getValues = (rowA, rowB, key, defaultValue) => { 4 | return [ 5 | rowA?.values?.[key] ?? defaultValue, 6 | rowB?.values?.[key] ?? defaultValue, 7 | ] 8 | } 9 | 10 | export const Numeric = (rowA, rowB, key) => { 11 | const [a, b] = getValues(rowA, rowB, key, 0) 12 | return a - b 13 | } 14 | 15 | export const Alphabetic = (rowA, rowB, key) => { 16 | const [a, b] = getValues(rowA, rowB, key, '') 17 | return memoize((a, b) => { 18 | return a.localeCompare(b) 19 | })(a, b) 20 | } 21 | 22 | export const DateTime = (rowA, rowB, key) => { 23 | const [a, b] = getValues(rowA, rowB, key, 0) 24 | return memoize((a, b) => { 25 | return new Date(a) - new Date(b) 26 | })(a, b) 27 | } 28 | 29 | export const ArrayLength = (rowA, rowB, key) => { 30 | const [a, b] = getValues(rowA, rowB, key, []) 31 | return memoize((a, b) => { 32 | return a.length - b.length 33 | })(a, b) 34 | } 35 | -------------------------------------------------------------------------------- /frontend/src/components/hackerpack/HackerpackDetail.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Box, Divider } from '@material-ui/core' 3 | 4 | import CompanySection from './CompanySection' 5 | import GradientBox from 'components/generic/GradientBox' 6 | 7 | 8 | 9 | const HackerpackDetail = ({ hackerpack, redeemable = false }) => { 10 | 11 | return ( 12 | <> 13 | 14 | 15 | 24 | 25 | 26 | 27 | 28 | 29 | ) 30 | } 31 | export default HackerpackDetail 32 | -------------------------------------------------------------------------------- /frontend/src/components/inputs/BlockExitIfDirty/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const BlockExitIfDirty = props => { 4 | function unloadPage() { 5 | if (props.dirty) { 6 | return 'You have unsaved changes! Are you sure you want to leave this page?' 7 | } 8 | } 9 | 10 | React.useEffect(() => { 11 | window.onbeforeunload = unloadPage 12 | window.onpopstate = unloadPage 13 | 14 | return () => { 15 | window.onbeforeunload = null 16 | window.onpopstate = null 17 | } 18 | }) 19 | 20 | return null 21 | } 22 | 23 | export default BlockExitIfDirty 24 | -------------------------------------------------------------------------------- /frontend/src/components/inputs/EventTagsSelect/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import Select from 'components/inputs/Select' 4 | 5 | const EventTagsSelect = ({ 6 | value, 7 | onChange, 8 | tags = [], 9 | placeholder = 'Select tags', 10 | }) => { 11 | return ( 12 | ({ 19 | label: status.label, 20 | value: status.id, 21 | }))} 22 | /> 23 | ) 24 | } 25 | 26 | export default RegistrationStatusSelect 27 | -------------------------------------------------------------------------------- /frontend/src/components/layouts/CookieConsentBar/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import CookieConsent from 'react-cookie-consent' 3 | import { makeStyles } from '@material-ui/core' 4 | import config from 'constants/config' 5 | 6 | const useStyles = makeStyles(theme => ({ 7 | primary: { 8 | color: theme.palette.primary.main, 9 | }, 10 | })) 11 | 12 | export default () => { 13 | const classes = useStyles() 14 | return ( 15 | 20 | We use necessary cookies to make our site work. We'd also like to 21 | set analytics cookies that help us make improvements by measuring 22 | how you use the site. These will be set only if you accept. For more 23 | detailed information about the cookies we use, see our{' '} 24 | 30 | Privacy Policy 31 | 32 | 33 | ) 34 | } 35 | -------------------------------------------------------------------------------- /frontend/src/components/messaging/alerts/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import moment from 'moment-timezone' 3 | import { Typography } from '@material-ui/core' 4 | import GradientBox from 'components/generic/GradientBox' 5 | 6 | export function Alerts({ alerts = [] }) { 7 | const sortedAlerts = alerts.sort( 8 | (a, b) => +new Date(b.sentAt) - +new Date(a.sentAt), 9 | ) 10 | console.log('alerts', alerts) 11 | return ( 12 |
15 | {sortedAlerts.map((a, index) => ( 16 | 21 | 25 | {a.content} 26 | 27 | 28 | {moment(a.sentAt).format('ddd HH:mm')} 29 | 30 | 31 | // 32 | ))} 33 |
34 | ) 35 | } 36 | 37 | export default Alerts 38 | -------------------------------------------------------------------------------- /frontend/src/components/modals/ProjectReviewModal/ReviewElement.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { 4 | Typography, 5 | Dialog, 6 | DialogTitle, 7 | DialogContent, 8 | Avatar, 9 | } from '@material-ui/core' 10 | 11 | const ReviewElement = ({ reviewer, showScore }) => { 12 | console.log('Reviewer from component', reviewer) 13 | return ( 14 |
15 | {reviewer?.avatar ? ( 16 | 17 | ) : ( 18 | 19 | {reviewer?.firstname ? reviewer?.firstname.charAt(0) : 'R'} 20 | 21 | )} 22 |
23 | 24 | {reviewer?.firstname ? reviewer.firstname : 'Anonymous'} 25 | 26 | {reviewer.message} 27 | {showScore && ( 28 | 29 | Score received: {reviewer.score} 30 | 31 | )} 32 |
33 |
34 | ) 35 | } 36 | 37 | export default ReviewElement 38 | -------------------------------------------------------------------------------- /frontend/src/components/modals/QRCodeModal/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import QRCode from 'qrcode.react' 3 | 4 | import { Box, Typography } from '@material-ui/core' 5 | 6 | import Modal from 'components/generic/Modal' 7 | 8 | const QRCodeModal = ({ open, onClose, value, title, message }) => { 9 | return ( 10 | 11 | 19 | 20 | {title} 21 | 22 | 23 | {message} 24 | 25 | 26 | 27 | 28 | ) 29 | } 30 | 31 | export default QRCodeModal 32 | -------------------------------------------------------------------------------- /frontend/src/components/navbars/BasicNavBar/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import UserAvatar from 'components/UserAvatar' 3 | import LanguageMenu from 'components/LanguageMenu' 4 | 5 | const BasicNavBar = () => { 6 | return ( 7 |
8 |
9 | 10 | 11 |
12 |
13 | ) 14 | } 15 | 16 | export default BasicNavBar 17 | -------------------------------------------------------------------------------- /frontend/src/components/plots/ApplicationsCount.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import Statistic from 'components/generic/Statistic' 4 | import { useSelector } from 'react-redux' 5 | import * as OrganiserSelectors from 'redux/organiser/selectors' 6 | 7 | export default () => { 8 | const value = useSelector(OrganiserSelectors.registrationsCount) 9 | return 10 | } 11 | -------------------------------------------------------------------------------- /frontend/src/components/plots/ApplicationsLast24h.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import Statistic from 'components/generic/Statistic' 4 | import { useSelector } from 'react-redux' 5 | import * as OrganiserSelectors from 'redux/organiser/selectors' 6 | 7 | export default () => { 8 | const value = useSelector(OrganiserSelectors.registrationsLast24h) 9 | return 10 | } 11 | -------------------------------------------------------------------------------- /frontend/src/components/plots/ReviewedAverage.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import Statistic from 'components/generic/Statistic' 4 | import StarIcon from '@material-ui/icons/Star' 5 | import { useSelector } from 'react-redux' 6 | import * as OrganiserSelectors from 'redux/organiser/selectors' 7 | 8 | export default () => { 9 | const value = useSelector(OrganiserSelectors.averageRating) 10 | return ( 11 | } 15 | /> 16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /frontend/src/components/plots/ReviewedPercent.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import Statistic from 'components/generic/Statistic' 4 | import { useSelector } from 'react-redux' 5 | import * as OrganiserSelectors from 'redux/organiser/selectors' 6 | 7 | export default () => { 8 | const value = useSelector(OrganiserSelectors.percentReviewed) ?? 0 9 | return 10 | } 11 | -------------------------------------------------------------------------------- /frontend/src/components/plots/TeamsCount.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import Statistic from 'components/generic/Statistic' 4 | import { useSelector } from 'react-redux' 5 | import * as OrganiserSelectors from 'redux/organiser/selectors' 6 | 7 | export default () => { 8 | const value = useSelector(OrganiserSelectors.teamsCount) 9 | return 10 | } 11 | -------------------------------------------------------------------------------- /frontend/src/components/projects/ScoreCriteria/index.js: -------------------------------------------------------------------------------- 1 | const ScoreCriteriaBase = [ 2 | { 3 | criteria: 'creativity', 4 | label: 'Creativity', 5 | }, 6 | { 7 | criteria: 'innovation', 8 | label: 'Innovation', 9 | }, 10 | { 11 | criteria: 'problemSolving', 12 | label: 'Problem Solving', 13 | }, 14 | { 15 | criteria: 'companyFit', 16 | label: 'Company Fit', 17 | }, 18 | { 19 | criteria: 'teamwork', 20 | label: 'Teamwork', 21 | }, 22 | ] 23 | 24 | export default ScoreCriteriaBase 25 | -------------------------------------------------------------------------------- /frontend/src/constants/events.js: -------------------------------------------------------------------------------- 1 | const EventConstants = { 2 | STATUS: { 3 | Published: { 4 | id: 'Published', 5 | label: 'Published', 6 | index: 0, 7 | }, 8 | Registration: { 9 | id: 'Registration', 10 | label: 'Registration in progress', 11 | index: 1, 12 | }, 13 | Confirmation: { 14 | id: 'Confirmation', 15 | label: 'Confirm participation', 16 | index: 2, 17 | }, 18 | InProgress: { 19 | id: 'InProgress', 20 | label: 'InProgress', 21 | index: 3, 22 | }, 23 | Finished: { 24 | id: 'Finished', 25 | label: 'Finished', 26 | index: 4, 27 | }, 28 | }, 29 | } 30 | 31 | export default EventConstants 32 | -------------------------------------------------------------------------------- /frontend/src/graphql/mutations/alertOps.js: -------------------------------------------------------------------------------- 1 | import { gql } from '@apollo/client' 2 | 3 | export const SEND_ALERT_MUTATION = gql` 4 | mutation SendAlert($input: AlertInput!) { 5 | sendAlert(alert: $input) { 6 | eventId 7 | content 8 | sender 9 | sentAt 10 | } 11 | } 12 | ` 13 | -------------------------------------------------------------------------------- /frontend/src/graphql/mutations/eventOps.js: -------------------------------------------------------------------------------- 1 | import { gql } from '@apollo/client' 2 | 3 | const Fragments = { 4 | EventFull: gql` 5 | fragment EventFull on Event { 6 | _id 7 | slug 8 | name 9 | galleryOpen 10 | coverImage { 11 | url 12 | publicId 13 | } 14 | eventType 15 | startTime 16 | registrationStartTime 17 | registrationEndTime 18 | organizations { 19 | name 20 | slug 21 | about 22 | link 23 | icon 24 | } 25 | published 26 | approved 27 | _eventLocationFormatted 28 | _eventTimeFormatted 29 | } 30 | `, 31 | } 32 | 33 | export const UPDATE_EVENT = gql` 34 | mutation UpdateEvent($_id: ID!, $input: EventInput!) { 35 | updateEvent(_id: $_id, event: $input) { 36 | ...EventFull 37 | } 38 | } 39 | ${Fragments.EventFull} 40 | ` 41 | -------------------------------------------------------------------------------- /frontend/src/graphql/mutations/messageOps.js: -------------------------------------------------------------------------------- 1 | import { gql } from '@apollo/client' 2 | 3 | export const SEND_MESSAGE_MUTATION = gql` 4 | mutation SendMessage($input: MessageInput!) { 5 | sendMessage(message: $input) { 6 | recipients 7 | content 8 | sender 9 | sentAt 10 | readAt 11 | } 12 | } 13 | ` 14 | -------------------------------------------------------------------------------- /frontend/src/graphql/queries/alert.js: -------------------------------------------------------------------------------- 1 | import { gql } from '@apollo/client' 2 | 3 | export const ALERTS_QUERY = gql` 4 | query alerts($eventId: String!) { 5 | alerts(eventId: $eventId) { 6 | content 7 | sender 8 | sentAt 9 | } 10 | } 11 | ` 12 | -------------------------------------------------------------------------------- /frontend/src/graphql/queries/hackerpack.js: -------------------------------------------------------------------------------- 1 | import { gql, useQuery } from '@apollo/client' 2 | 3 | const Fragments = { 4 | HackerpackListing: gql` 5 | fragment HackerpackListing on Hackerpack { 6 | _id 7 | slug 8 | name 9 | description 10 | icon { 11 | url 12 | publicId 13 | } 14 | link 15 | } 16 | `, 17 | } 18 | 19 | export const GET_HACKERPACK_ITEM = gql` 20 | query Hackerpack($_id: ID!) { 21 | hackerpackById(_id: $_id) { 22 | ...HackerpackListing 23 | } 24 | } 25 | ${Fragments.HackerpackListing} 26 | ` 27 | 28 | export const useHackerpack = _id => { 29 | const { data, loading, error } = useQuery(GET_HACKERPACK_ITEM, { 30 | variables: { 31 | _id, 32 | }, 33 | }) 34 | 35 | return [data?.hackerpackById, loading, error] 36 | } 37 | 38 | export const GET_FULL_HACKERPACK = gql` 39 | query Hackerpack { 40 | HackerpackListing { 41 | ...HackerpackListing 42 | } 43 | } 44 | ${Fragments.HackerpackListing} 45 | ` 46 | 47 | export const useHackerpackListing = () => { 48 | const { data, loading, error } = useQuery(GET_FULL_HACKERPACK) 49 | 50 | return [data?.HackerpackListing, loading, error] 51 | } 52 | -------------------------------------------------------------------------------- /frontend/src/graphql/queries/messages.js: -------------------------------------------------------------------------------- 1 | import { gql } from '@apollo/client' 2 | 3 | export const MY_MESSAGES_QUERY = gql` 4 | query MyMessages { 5 | messages { 6 | recipients 7 | content 8 | sender 9 | sentAt 10 | readAt 11 | } 12 | } 13 | ` 14 | -------------------------------------------------------------------------------- /frontend/src/graphql/queries/organization.js: -------------------------------------------------------------------------------- 1 | import { gql, useQuery } from '@apollo/client' 2 | 3 | export const GET_ORGANIZATION = gql` 4 | query Organization($userId: ID!) { 5 | organization(userId: $userId) { 6 | _id 7 | slug 8 | name 9 | about 10 | icon 11 | link 12 | } 13 | } 14 | ` 15 | 16 | export const useOrganization = _id => { 17 | const { data, loading, error } = useQuery(GET_ORGANIZATION, { 18 | variables: { 19 | _id, 20 | }, 21 | }) 22 | 23 | return [data?.organizationById, loading, error] 24 | } 25 | 26 | export const GET_ORGANIZATIONS = gql` 27 | query Organizations { 28 | organizations { 29 | _id 30 | slug 31 | name 32 | about 33 | icon 34 | link 35 | } 36 | } 37 | ` 38 | 39 | export const useAllOrganizations = () => { 40 | const { data, loading, error } = useQuery(GET_ORGANIZATIONS) 41 | return [data?.organizations, loading, error] 42 | } 43 | -------------------------------------------------------------------------------- /frontend/src/graphql/queries/registrations.js: -------------------------------------------------------------------------------- 1 | import { gql, useQuery } from '@apollo/client' 2 | 3 | const Fragments = { 4 | RegistrationPreview: gql` 5 | fragment RegistrationPreview on Registration { 6 | _id 7 | status 8 | event { 9 | _id 10 | slug 11 | name 12 | description 13 | startTime 14 | endTime 15 | coverImage { 16 | publicId 17 | } 18 | _eventTimeFormatted 19 | _eventLocationFormatted 20 | } 21 | } 22 | `, 23 | } 24 | 25 | export const GET_REGISTRATIONS_BY_USER = gql` 26 | query Registration($userId: ID!) { 27 | registrationsByUser(userId: $userId) { 28 | ...RegistrationPreview 29 | } 30 | } 31 | ${Fragments.RegistrationPreview} 32 | ` 33 | 34 | export const useRegistrationsByUser = userId => { 35 | const { data, loading, error } = useQuery(GET_REGISTRATIONS_BY_USER, { 36 | variables: { 37 | userId, 38 | }, 39 | }) 40 | return [data?.registrationsByUser, loading, error] 41 | } 42 | -------------------------------------------------------------------------------- /frontend/src/graphql/queries/userProfile.js: -------------------------------------------------------------------------------- 1 | import { gql, useQuery } from '@apollo/client' 2 | 3 | const Fragments = { 4 | ProfilePreview: gql` 5 | fragment ProfilePreview on UserProfile { 6 | avatar 7 | firstName 8 | lastName 9 | email 10 | } 11 | `, 12 | } 13 | 14 | export const GET_PROFILE_PREVIEW = gql` 15 | query UserProfile($userId: ID!) { 16 | userProfileById(userId: $userId) { 17 | ...ProfilePreview 18 | } 19 | } 20 | ${Fragments.ProfilePreview} 21 | ` 22 | 23 | export const useUserAvatar = userId => { 24 | const { data, loading, error } = useQuery(GET_PROFILE_PREVIEW, { 25 | variables: { 26 | userId, 27 | }, 28 | }) 29 | 30 | return [data?.userProfileById?.avatar, loading, error] 31 | } 32 | 33 | export const GET_MY_PROFILE_PREVIEW = gql` 34 | query UserProfile { 35 | myProfile { 36 | ...ProfilePreview 37 | } 38 | } 39 | ${Fragments.ProfilePreview} 40 | ` 41 | 42 | export const useMyProfilePreview = () => { 43 | const { data, loading, error } = useQuery(GET_MY_PROFILE_PREVIEW) 44 | 45 | return [data?.myProfile, loading, error] 46 | } 47 | -------------------------------------------------------------------------------- /frontend/src/graphql/subscriptions/alert.js: -------------------------------------------------------------------------------- 1 | import { gql } from '@apollo/client' 2 | 3 | export const NEW_ALERTS_SUBSCRIPTION = gql` 4 | subscription newAlert($eventId: String, $slug: String) { 5 | newAlert(eventId: $eventId, slug: $slug) { 6 | eventId 7 | content 8 | sender 9 | sentAt 10 | } 11 | } 12 | ` 13 | -------------------------------------------------------------------------------- /frontend/src/graphql/subscriptions/messages.js: -------------------------------------------------------------------------------- 1 | import { gql } from '@apollo/client' 2 | 3 | export const MY_MESSAGES_SUBSCRIPTION = gql` 4 | subscription MyMessages { 5 | newMessage { 6 | recipients 7 | content 8 | sender 9 | sentAt 10 | readAt 11 | } 12 | } 13 | ` 14 | -------------------------------------------------------------------------------- /frontend/src/hocs/EventStatusWrapper.js: -------------------------------------------------------------------------------- 1 | const EventStatusWrapper = ({ 2 | eventStatus, 3 | statuses = [], 4 | children, 5 | render, 6 | }) => { 7 | if (statuses.indexOf(eventStatus) !== -1) { 8 | return typeof render === 'function' ? render() : children 9 | } 10 | return null 11 | } 12 | 13 | export default EventStatusWrapper 14 | -------------------------------------------------------------------------------- /frontend/src/hocs/ShowIfPermission.js: -------------------------------------------------------------------------------- 1 | import React, { useMemo } from 'react' 2 | import { useSelector } from 'react-redux' 3 | import { difference } from 'lodash-es' 4 | 5 | import * as AuthSelectors from 'redux/auth/selectors' 6 | import * as UserSelectors from 'redux/user/selectors' 7 | 8 | /** Hide a component if the user doesn't have a given permission, but don't redirect to login/error */ 9 | 10 | export default (ComposedComponent, requiredPermissions = []) => { 11 | return props => { 12 | const hasProfile = useSelector(UserSelectors.hasProfile) 13 | const isAuthenticated = useSelector(AuthSelectors.isAuthenticated) 14 | const permissions = useSelector(AuthSelectors.getPermissions) 15 | 16 | const hasRequiredPermissions = useMemo(() => { 17 | return difference(requiredPermissions, permissions).length === 0 18 | }, [permissions]) 19 | 20 | if (!isAuthenticated) return null 21 | if (!hasProfile) return null 22 | if (!hasRequiredPermissions) return null 23 | 24 | return 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /frontend/src/hooks/apiHooks.js: -------------------------------------------------------------------------------- 1 | import { useState, useCallback } from 'react' 2 | 3 | export const usePromise = promise => { 4 | const [data, setData] = useState() 5 | const [loading, setLoading] = useState(true) 6 | const [error, setError] = useState(false) 7 | 8 | const handleDone = useCallback(data => { 9 | setData(data) 10 | setLoading(false) 11 | }, []) 12 | 13 | const handleErr = useCallback(err => { 14 | setLoading(false) 15 | setError(err) 16 | }, []) 17 | 18 | const fetch = useCallback( 19 | (...args) => { 20 | promise(...args) 21 | .then(data => { 22 | handleDone(data) 23 | }) 24 | .catch(err => { 25 | handleErr(err) 26 | }) 27 | }, 28 | [handleDone, handleErr, promise], 29 | ) 30 | 31 | return { data, loading, error, fetch } 32 | } 33 | -------------------------------------------------------------------------------- /frontend/src/i18n.js: -------------------------------------------------------------------------------- 1 | import i18n from 'i18next' 2 | import { initReactI18next } from 'react-i18next' 3 | import LanguageDetector from 'i18next-browser-languagedetector' 4 | 5 | import Backend from 'i18next-xhr-backend' 6 | 7 | // translations are already at 8 | // '../public/locales/en/translation.json' 9 | // which is the default for the xhr backend to load from 10 | 11 | i18n 12 | // load translation using xhr -> see /public/locales (i.e. https://github.com/i18next/react-i18next/tree/master/example/react/public/locales) 13 | // learn more: https://github.com/i18next/i18next-xhr-backend 14 | .use(Backend) 15 | // detect user language 16 | // learn more: https://github.com/i18next/i18next-browser-languageDetector 17 | .use(LanguageDetector) 18 | // pass the i18n instance to react-i18next. 19 | .use(initReactI18next) 20 | // init i18next 21 | // for all options read: https://www.i18next.com/overview/configuration-options 22 | .init({ 23 | fallbackLng: 'en', 24 | debug: true, 25 | saveMissing: true, // send not translated keys to endpoint 26 | keySeparator: false, // we do not use keys in form messages.welcome 27 | interpolation: { 28 | escapeValue: false, // not needed for react as it escapes by default 29 | }, 30 | }) 31 | 32 | export default i18n 33 | -------------------------------------------------------------------------------- /frontend/src/pages/_admin/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useRouteMatch } from 'react-router' 3 | import { Route, Switch, Redirect } from 'react-router-dom' 4 | 5 | import DefaultPage from './default' 6 | import HackerpackForm from './hackerpack' 7 | import BannerForm from './banner' 8 | import OrganizationForm from './organization' 9 | 10 | export default () => { 11 | const match = useRouteMatch() 12 | return ( 13 | 14 | 15 | 20 | 25 | 30 | 31 | 32 | ) 33 | } 34 | -------------------------------------------------------------------------------- /frontend/src/pages/_dashboard/renderDashboard/default/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { useRouteMatch, useLocation } from 'react-router' 4 | 5 | import SidebarLayout from 'components/layouts/SidebarLayout' //TODO: make normal sidepar work with default view 6 | import BasicNavBar from 'components/navbars/BasicNavBar' 7 | import PageWrapper from 'components/layouts/PageWrapper' 8 | import defaultImage from 'assets/images/dashboardDefault.jpg' 9 | 10 | export default () => { 11 | const match = useRouteMatch() 12 | const location = useLocation() 13 | 14 | return ( 15 | 16 | 21 | } 22 | topContent={} 23 | routes={[]} 24 | /> 25 | 26 | ) 27 | } 28 | -------------------------------------------------------------------------------- /frontend/src/pages/_dashboard/renderDashboard/default/logout/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default () => { 4 | 5 | return ( 6 |
7 |

LOGOUT

8 |
9 | ) 10 | } -------------------------------------------------------------------------------- /frontend/src/pages/_dashboard/renderDashboard/default/profile/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default () => { 4 | 5 | return ( 6 |
7 |

PROFILE

8 |
9 | ) 10 | } -------------------------------------------------------------------------------- /frontend/src/pages/_dashboard/renderDashboard/generalPages/default/Blocks/TimeLineBlock.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { useSelector } from 'react-redux' 4 | import { Grid, Typography } from '@material-ui/core' 5 | 6 | import * as DashboardSelectors from 'redux/dashboard/selectors' 7 | 8 | 9 | import EventTimeline from 'pages/_events/slug/default/EventTimeline' 10 | 11 | export default () => { 12 | const event = useSelector(DashboardSelectors.event) 13 | return ( 14 | 15 | 16 | 21 | 22 | 23 | ) 24 | } 25 | // -------------------------------------------------------------------------------- /frontend/src/pages/_dashboard/renderDashboard/organiser/default/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import PageWrapper from 'components/layouts/PageWrapper' 4 | import GlobalNavBar from 'components/navbars/GlobalNavBar' 5 | import Footer from 'components/layouts/Footer' 6 | 7 | import Container from 'components/generic/Container' 8 | 9 | import NewEventForm from './NewEventForm' 10 | import EventsList from './EventsList' 11 | 12 | import { useMyEvents } from 'graphql/queries/events' 13 | 14 | export default () => { 15 | const [events, loading] = useMyEvents() 16 | 17 | return ( 18 | } 21 | footer={() =>