├── .dockerignore ├── .eslintrc.js ├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── task.md ├── .gitignore ├── .prettierignore ├── .prettierrc ├── CODE_OF_CONDUCT.md ├── Dockerfile ├── LICENSE ├── README.md ├── babel.config.js ├── buildspec-preview.yml ├── buildspec.yml ├── ci └── install_deps.sh ├── features-local.yaml ├── jest.config.js ├── k8s ├── Chart.yaml ├── templates │ ├── 00-namespace.yaml │ ├── deployment.yaml │ └── service.yaml ├── values.yaml └── values │ ├── dev-preview.yaml │ ├── dev.yaml │ ├── prod.yaml │ ├── test-preview.yaml │ └── test.yaml ├── myke.yml ├── nginx.conf ├── package-lock.json ├── package.json ├── public ├── contribute.json ├── favicon.ico ├── index.html ├── logbackin.html ├── opensearch.xml ├── reload.html └── webapp │ ├── m-192.png │ ├── m.512.png │ └── manifest.json ├── src ├── App.vue ├── assets │ ├── images │ │ ├── chevron-down-blue-60.svg │ │ ├── chevron-down-white.svg │ │ ├── dino-1.png │ │ ├── dino-1@2x.png │ │ ├── dino-1@3x.png │ │ ├── favicon.png │ │ ├── mozilla-m.svg │ │ ├── mozilla.svg │ │ ├── people-dots.png │ │ ├── people-dots@2x.png │ │ ├── quad.svg │ │ ├── skewed-triangle.svg │ │ ├── target.png │ │ ├── target@2x.png │ │ ├── target@3x.png │ │ ├── tour-foundation.png │ │ ├── tour-heads.png │ │ ├── tour-technology.png │ │ ├── tricrowd.png │ │ ├── tricrowd@2x.png │ │ └── user-demo.png │ ├── js │ │ ├── access-groups-api.config.js │ │ ├── access-groups-api.js │ │ ├── access-groups.js │ │ ├── avatars.js │ │ ├── component-utils.js │ │ ├── content-lookup.js │ │ ├── display-levels.js │ │ ├── features.js │ │ ├── fetcher.js │ │ ├── fluent.js │ │ ├── fluent.spec.js │ │ ├── identicon-avatar.js │ │ ├── identities.js │ │ ├── onboarding.js │ │ ├── related.js │ │ ├── reload.js │ │ ├── scope.js │ │ ├── scrolling.js │ │ ├── swipe.js │ │ └── trap-focus.js │ └── svg │ │ ├── activity.svg │ │ ├── at-sign.svg │ │ ├── bell.svg │ │ ├── bookmark.svg │ │ ├── chain.svg │ │ ├── check.svg │ │ ├── chevron-down.svg │ │ ├── chevron-left.svg │ │ ├── chevron-right.svg │ │ ├── chevron-up.svg │ │ ├── circle-fill.svg │ │ ├── circle.svg │ │ ├── clock.svg │ │ ├── collapse.svg │ │ ├── copy.svg │ │ ├── cpg.svg │ │ ├── crown-fill.svg │ │ ├── crown.svg │ │ ├── dashboard.svg │ │ ├── dino.svg │ │ ├── discourse.svg │ │ ├── edit.svg │ │ ├── email.svg │ │ ├── envelope.svg │ │ ├── expand.svg │ │ ├── external.svg │ │ ├── eye.svg │ │ ├── faq.svg │ │ ├── github.svg │ │ ├── grid.svg │ │ ├── idcard.svg │ │ ├── info.svg │ │ ├── irc.svg │ │ ├── keys.svg │ │ ├── list.svg │ │ ├── lock.svg │ │ ├── log-out.svg │ │ ├── mail-outline.svg │ │ ├── mail.svg │ │ ├── matrix.svg │ │ ├── moz.svg │ │ ├── org-chart.svg │ │ ├── pencil.svg │ │ ├── phone-forwarded.svg │ │ ├── phone.svg │ │ ├── plus.svg │ │ ├── private.svg │ │ ├── question-mark-circle.svg │ │ ├── rotate.svg │ │ ├── search.svg │ │ ├── slack.svg │ │ ├── sliders.svg │ │ ├── square.svg │ │ ├── staff.svg │ │ ├── successSquare.svg │ │ ├── toggle-right.svg │ │ ├── triangle.svg │ │ ├── user-check.svg │ │ ├── user-plus.svg │ │ ├── user.svg │ │ ├── users-outline.svg │ │ ├── users.svg │ │ ├── world.svg │ │ ├── x.svg │ │ └── zoom.svg ├── components │ ├── Fluent.vue │ ├── _functional │ │ ├── Modal.spec.js │ │ ├── Modal.vue │ │ └── ShowMore.vue │ ├── _mixins │ │ ├── AccountsMixin.spec.js │ │ ├── AccountsMixin.vue │ │ ├── CompanyMixin.spec.js │ │ ├── CompanyMixin.vue │ │ ├── EnvironmentMixin.vue │ │ ├── LinksMixin.vue │ │ ├── MembersListMixin.vue │ │ ├── OfficesMixin.vue │ │ ├── PhoneNumbersMixin.spec.js │ │ └── PhoneNumbersMixin.vue │ ├── access_group │ │ ├── AccessGroup.vue │ │ ├── AccessGroupCreate.spec.js │ │ ├── AccessGroupCreate.vue │ │ ├── AccessGroupDescription.spec.js │ │ ├── AccessGroupDescription.vue │ │ ├── AccessGroupDetails.spec.js │ │ ├── AccessGroupDetails.vue │ │ ├── AccessGroupEdit.vue │ │ ├── AccessGroupEditPanel.spec.js │ │ ├── AccessGroupEditPanel.vue │ │ ├── AccessGroupHistoryEdit.spec.js │ │ ├── AccessGroupHistoryEdit.vue │ │ ├── AccessGroupIndex.spec.js │ │ ├── AccessGroupIndex.vue │ │ ├── AccessGroupInformationEdit.spec.js │ │ ├── AccessGroupInformationEdit.vue │ │ ├── AccessGroupInvitationNotification.spec.js │ │ ├── AccessGroupInvitationNotification.vue │ │ ├── AccessGroupInvitationsEdit.spec.js │ │ ├── AccessGroupInvitationsEdit.vue │ │ ├── AccessGroupLeaveConfirmationNotification.js │ │ ├── AccessGroupLeaveConfirmationNotification.vue │ │ ├── AccessGroupList.spec.js │ │ ├── AccessGroupList.vue │ │ ├── AccessGroupListItem.spec.js │ │ ├── AccessGroupListItem.vue │ │ ├── AccessGroupMarkdownGuide.spec.js │ │ ├── AccessGroupMarkdownGuide.vue │ │ ├── AccessGroupMemberItem.spec.js │ │ ├── AccessGroupMemberItem.vue │ │ ├── AccessGroupMemberListDisplay.spec.js │ │ ├── AccessGroupMemberListDisplay.vue │ │ ├── AccessGroupMembers.spec.js │ │ ├── AccessGroupMembers.vue │ │ ├── AccessGroupMembersEdit.spec.js │ │ ├── AccessGroupMembersEdit.vue │ │ ├── AccessGroupMembersTable.spec.js │ │ ├── AccessGroupMembersTable.vue │ │ ├── AccessGroupMembersTableRow.spec.js │ │ ├── AccessGroupMembersTableRow.vue │ │ ├── AccessGroupMembershipManagement.spec.js │ │ ├── AccessGroupMembershipManagement.vue │ │ ├── AccessGroupTOSAcceptanceNotification.spec.js │ │ ├── AccessGroupTOSAcceptanceNotification.vue │ │ ├── AccessGroupTerms.vue │ │ └── AccessGroupView.vue │ ├── guide │ │ ├── OnboardingModal.vue │ │ ├── ProgressDots.vue │ │ └── TourTooltip.vue │ ├── profile │ │ ├── AccessLabel.vue │ │ ├── PreviewAs.vue │ │ ├── PrivacySetting.vue │ │ ├── Profile.vue │ │ ├── ProfileDescription.vue │ │ ├── ProfileName.vue │ │ ├── ProfileNav.vue │ │ ├── ProfilePreview.vue │ │ ├── ProfileSection.vue │ │ ├── ProfileTeamLocation.vue │ │ ├── ProfileTitle.vue │ │ ├── ReportingStructure.vue │ │ ├── ReportingStructureGroup.vue │ │ ├── edit │ │ │ ├── Cropper.vue │ │ │ ├── EditAccessGroups.vue │ │ │ ├── EditAccounts.vue │ │ │ ├── EditContact.vue │ │ │ ├── EditFieldTypeSelect.vue │ │ │ ├── EditIdentities.vue │ │ │ ├── EditKeys.vue │ │ │ ├── EditLanguages.vue │ │ │ ├── EditMutationWrapper.vue │ │ │ ├── EditPersonalInfo.vue │ │ │ ├── EditPictureModal.vue │ │ │ └── EditTags.vue │ │ └── view │ │ │ ├── EmptyCard.vue │ │ │ ├── ViewAccessGroups.vue │ │ │ ├── ViewAccounts.vue │ │ │ ├── ViewColleagues.vue │ │ │ ├── ViewContact.vue │ │ │ ├── ViewIdentities.vue │ │ │ ├── ViewKeys.vue │ │ │ ├── ViewLanguages.vue │ │ │ ├── ViewPersonalInfo.vue │ │ │ └── ViewTags.vue │ ├── search │ │ ├── SearchResult.vue │ │ ├── SearchResultList.vue │ │ └── SearchToggle.vue │ └── ui │ │ ├── Banner.vue │ │ ├── Button.vue │ │ ├── Card.vue │ │ ├── CardRow.vue │ │ ├── Checkbox.vue │ │ ├── Combobox.vue │ │ ├── ContactMe.vue │ │ ├── DinoType.vue │ │ ├── EditButton.spec.js │ │ ├── EditButton.vue │ │ ├── Error.vue │ │ ├── ExpirationSelect.vue │ │ ├── ExternalButtonLink.vue │ │ ├── ExternalLink.vue │ │ ├── Footer.vue │ │ ├── GlobalNotifications.vue │ │ ├── Icon.vue │ │ ├── IconBlock.vue │ │ ├── IconBlockList.vue │ │ ├── Key.vue │ │ ├── LoadingSpinner.vue │ │ ├── Meta.vue │ │ ├── MetaList.vue │ │ ├── NumberScrollerInput.vue │ │ ├── Option.vue │ │ ├── PanelSection.vue │ │ ├── Person.vue │ │ ├── PersonList.vue │ │ ├── Popover.vue │ │ ├── SearchForm.vue │ │ ├── Select.vue │ │ ├── SelectCustom.vue │ │ ├── Tag.spec.js │ │ ├── Tag.vue │ │ ├── TagSelector.vue │ │ ├── TextArea.vue │ │ ├── TextInput.vue │ │ ├── Toast.vue │ │ ├── Toggle.vue │ │ ├── Tooltip.vue │ │ ├── TopBar.vue │ │ ├── UserMenu.vue │ │ ├── UserPicture.vue │ │ └── Vouch.vue ├── locales │ ├── en-GB │ │ └── strings.ftl │ ├── en-US │ │ └── strings.ftl │ └── hi │ │ └── strings.ftl ├── main.js ├── pages │ ├── PageAccessGroups.vue │ ├── PageHome.vue │ ├── PageOrgchart.vue │ ├── PagePermissionRequired.vue │ ├── PageProfile.vue │ ├── PageSearchResult.vue │ └── PageUnknown.vue ├── queries │ └── profile.js ├── router.js ├── server.js ├── store │ ├── features.store.js │ ├── index.js │ ├── invitation-email.store.js │ ├── invitationgroupdata.json │ ├── scope.store.js │ └── user.store.js └── view_models │ ├── AccessGroupViewModel.js │ ├── AccessGroupViewModel.spec.js │ ├── ProfileViewModel.js │ └── ProfileViewModel.spec.js ├── terraform ├── codebuild-preview │ ├── data.tf │ ├── main.tf │ ├── outputs.tf │ ├── provider.tf │ ├── variables.tf │ └── versions.tf └── codebuild │ ├── data.tf │ ├── main.tf │ ├── outputs.tf │ ├── provider.tf │ ├── variables.tf │ └── versions.tf ├── tests ├── mocks │ ├── abcfluentMock.js │ ├── accessgroupallmembers.json │ ├── accessgroupcurators.json │ ├── accessgroupdata.json │ ├── accessgroupmemberinvitations.json │ ├── accessgroupmembers.json │ ├── accessgrouptermsofservice.json │ ├── enUSfluentMock.js │ ├── invitationgroupdata.json │ └── mockStore.js ├── unit │ └── .eslintrc.js └── utils │ ├── getMountedComponentWithStore.js │ └── getRenderedText.js └── vue.config.js /.dockerignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | myke.yml 5 | k8s/ 6 | 7 | # local env files 8 | .env.local 9 | .env.*.local 10 | 11 | # Log files 12 | npm-debug.log* 13 | yarn-debug.log* 14 | yarn-error.log* 15 | 16 | # Editor directories and files 17 | .idea 18 | .vscode 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw* 24 | 25 | Dockerfile 26 | **/.terraform/* -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true, 5 | 'jest/globals': true, 6 | }, 7 | extends: ['plugin:vue/essential', '@vue/airbnb', 'prettier'], 8 | plugins: ['jest'], 9 | rules: { 10 | 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 11 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', 12 | 'linebreak-style': 'off', 13 | 'no-param-reassign': 'off', 14 | }, 15 | parserOptions: { 16 | parser: 'babel-eslint', 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | **Describe the bug** 8 | A clear and concise description of what the bug is. 9 | 10 | **To Reproduce** 11 | Steps to reproduce the behavior: 12 | 1. Go to '...' 13 | 2. Click on '....' 14 | 3. Scroll down to '....' 15 | 4. See error 16 | 17 | **Expected behavior** 18 | A clear and concise description of what you expected to happen. 19 | 20 | **Screenshots** 21 | If applicable, add screenshots to help explain your problem. 22 | 23 | **Desktop (please complete the following information):** 24 | - OS: [e.g. iOS] 25 | - Browser [e.g. chrome, safari] 26 | - Version [e.g. 22] 27 | 28 | **Smartphone (please complete the following information):** 29 | - Device: [e.g. iPhone6] 30 | - OS: [e.g. iOS8.1] 31 | - Browser [e.g. stock browser, safari] 32 | - Version [e.g. 22] 33 | 34 | **Additional context** 35 | Add any other context about the problem here. 36 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | **Is your feature request related to a problem? Please describe.** 8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 9 | 10 | **Describe the solution you'd like** 11 | A clear and concise description of what you want to happen. 12 | 13 | **Describe alternatives you've considered** 14 | A clear and concise description of any alternative solutions or features you've considered. 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/task.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Task 3 | about: 'Create tasks for DinoPark''s front-end developers. ' 4 | 5 | --- 6 | 7 | As the issue title, please start with a verb, for example: 8 | 9 | * Add default profile picture 10 | * Update footer with new icons 11 | * Remove padding underneath profile card 12 | 13 | Please avoid adding browser/device information in the title, this can be added in the issue itself. 14 | 15 | Please provide as much information as possible in the issue description. 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | node_modules_bak 4 | /dist 5 | 6 | # local env files 7 | .env.local 8 | .env.*.local 9 | 10 | # Log files 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | 15 | # Editor directories and files 16 | .idea 17 | .vscode 18 | *.suo 19 | *.ntvs* 20 | *.njsproj 21 | *.sln 22 | *.sw* 23 | 24 | .terraform 25 | 26 | # Jest coverage directories 27 | coverage -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | k8s/ -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all", 4 | "arrowParens": "always" 5 | } 6 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Community Participation Guidelines 2 | 3 | This repository is governed by Mozilla's code of conduct and etiquette guidelines. 4 | For more details, please read 5 | [Mozilla Community Participation Guidelines](https://www.mozilla.org/about/governance/policies/participation/). 6 | 7 | ## How to Report 8 | 9 | For more information on how to report violations of the CPG, please read our '[How to Report](https://www.mozilla.org/en-US/about/governance/policies/participation/reporting/)' page. 10 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # build stage 2 | FROM node:latest as build-stage 3 | 4 | WORKDIR /app 5 | COPY package.json ./ 6 | COPY package-lock.json ./ 7 | RUN npm install 8 | COPY . . 9 | ARG baseurl=/ 10 | ENV DP_BASE_URL=$baseurl 11 | RUN npm run build 12 | 13 | # production stage 14 | FROM nginx:1.21.6-alpine as production-stage 15 | COPY nginx.conf /etc/nginx/conf.d/default.conf 16 | ARG baseurl=/ 17 | COPY --from=build-stage /app/dist /usr/share/nginx/html$baseurl 18 | COPY --from=build-stage /app/src/assets/images/user-demo.png /usr/share/nginx/html${baseurl}img/ 19 | COPY --from=build-stage /app/dist/index.html /usr/share/nginx/html/index.html 20 | RUN ln -s /config /usr/share/nginx/html${baseurl}/config 21 | EXPOSE 80 22 | CMD ["nginx", "-g", "daemon off;"] 23 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['@vue/app'], 3 | plugins: [ 4 | [ 5 | 'module-resolver', 6 | { 7 | root: ['./node_modules'], 8 | alias: { 9 | '^preact$': 'preact/compat', 10 | '^hack/preact$': 'preact', 11 | }, 12 | }, 13 | ], 14 | ], 15 | }; 16 | -------------------------------------------------------------------------------- /buildspec-preview.yml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | 3 | phases: 4 | install: 5 | runtime-versions: 6 | docker: 18 7 | nodejs: 12 8 | commands: 9 | - '. ci/install_deps.sh' 10 | pre_build: 11 | commands: 12 | - export COMMIT_SHA=$CODEBUILD_RESOLVED_SOURCE_VERSION 13 | - export CODEBUILD_WEBHOOK_TRIGGER=${CODEBUILD_WEBHOOK_TRIGGER:-branch/preview} 14 | - export PREVIEW=$(if [ "$CODEBUILD_WEBHOOK_TRIGGER" = "branch/preview" ]; then echo 1; fi) 15 | - echo "running for ${COMMIT_SHA} in dev and test" 16 | - aws ecr get-login --region us-west-2 --no-include-email | bash 17 | build: 18 | commands: 19 | - if [ "$PREVIEW" = 1 ]; then myke docker --baseurl=/preview --name=dino-park-front-end-preview --rev=$COMMIT_SHA push-image --name=dino-park-front-end-preview --rev=$COMMIT_SHA; fi 20 | post_build: 21 | commands: 22 | - | 23 | if [ "$PREVIEW" = 1 ]; then 24 | echo "Deploying to dev and test envirnoments..." 25 | aws eks update-kubeconfig --name kubernetes-stage-us-west-2 26 | myke deploy-preview --deploy_env=dev --rev=$COMMIT_SHA 27 | aws eks update-kubeconfig --name kubernetes-prod-us-west-2 28 | myke deploy-preview --deploy_env=test --rev=$COMMIT_SHA 29 | fi 30 | -------------------------------------------------------------------------------- /buildspec.yml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | 3 | phases: 4 | install: 5 | runtime-versions: 6 | docker: 18 7 | nodejs: 12 8 | commands: 9 | - '. ci/install_deps.sh' 10 | pre_build: 11 | commands: 12 | - export COMMIT_SHA=$CODEBUILD_RESOLVED_SOURCE_VERSION 13 | - export CODEBUILD_WEBHOOK_TRIGGER=${CODEBUILD_WEBHOOK_TRIGGER:-branch/master} 14 | - export DEPLOY_ENV=$(echo $CODEBUILD_WEBHOOK_TRIGGER | sed -e 's/tag\/.*-\(.*\)/\1/' | sed -e 's/branch\/master/dev/') 15 | - export PR=$(if case $CODEBUILD_WEBHOOK_TRIGGER in pr/*) ;; *) false;; esac; then echo 1; fi) 16 | - export MASTER=$(if [ "$CODEBUILD_WEBHOOK_TRIGGER" = "branch/master" ]; then echo 1; fi) 17 | - echo "running for ${COMMIT_SHA} in ${DEPLOY_ENV}" 18 | - aws ecr get-login --region us-west-2 --no-include-email | bash 19 | - echo "Logging Into Docker Hub" 20 | - echo $DOCKERHUB_PASSWORD | docker login --username $DOCKERHUB_USERNAME --password-stdin 21 | build: 22 | commands: 23 | - if [ "$PR" = 1 ]; then myke docker; fi 24 | - if [ "$MASTER" = 1 ]; then myke docker --rev=$COMMIT_SHA push-image --rev=$COMMIT_SHA; fi 25 | post_build: 26 | commands: 27 | - if [ "$CODEBUILD_BUILD_SUCCEEDING" = 0 ] ; then exit 1 ; fi 28 | - | 29 | if [ "$PR" = "" ] && { [ "$DEPLOY_ENV" = "dev" ] || [ "$DEPLOY_ENV" = "test" ] ;}; then 30 | echo "Deploying to dev and test envirnoments..." 31 | aws eks update-kubeconfig --name kubernetes-stage-us-west-2 32 | myke deploy --deploy_env=dev --rev=$COMMIT_SHA 33 | aws eks update-kubeconfig --name kubernetes-prod-us-west-2 34 | myke deploy --deploy_env=test --rev=$COMMIT_SHA 35 | fi 36 | - | 37 | if [ "$PR" = "" ] && [ "$DEPLOY_ENV" = "prod" ]; then 38 | echo "Deploying to prod envirnoment..." 39 | aws eks update-kubeconfig --name kubernetes-prod-us-west-2 40 | myke deploy --deploy_env=prod --rev=$COMMIT_SHA 41 | fi 42 | -------------------------------------------------------------------------------- /ci/install_deps.sh: -------------------------------------------------------------------------------- 1 | HELM_INSTALL_DIR=/bin 2 | export DESIRED_VERSION="v3.5.4" 3 | curl https://raw.githubusercontent.com/helm/helm/master/scripts/get | bash 4 | curl -s -L -o myke https://github.com/fiji-flo/myke/releases/download/0.9.11/myke-0.9.11-x86_64-unknown-linux-musl 5 | chmod +x ./myke 6 | mv ./myke /bin/myke -------------------------------------------------------------------------------- /features-local.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | customInvitationText: true 3 | historyTab: false 4 | editGroupType: true 5 | accessGroupsToggle: true 6 | accessGroupsNav: true 7 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | moduleFileExtensions: ['js', 'jsx', 'json', 'vue'], 3 | transform: { 4 | '^.+\\.vue$': 'vue-jest', 5 | '.+\\.(css|styl|less|sass|scss|png|jpg|ttf|woff|woff2)$': 6 | 'jest-transform-stub', 7 | '^.+\\.jsx?$': '/node_modules/babel-jest', 8 | '^.+\\.ftl$': 'jest-raw-loader', 9 | }, 10 | moduleNameMapper: { 11 | 'non-mock_en-US_strings.ftl': '/src/locales/en-US/strings.ftl', 12 | '.*/en-US/.*\\.ftl$': '/tests/mocks/enUSfluentMock.js', 13 | '.*\\.ftl$': '/tests/mocks/abcfluentMock.js', 14 | '^@/(.*)$': '/src/$1', 15 | }, 16 | snapshotSerializers: ['jest-serializer-vue'], 17 | testMatch: ['/**/*.spec.(js|jsx|ts|tsx)'], 18 | }; 19 | -------------------------------------------------------------------------------- /k8s/Chart.yaml: -------------------------------------------------------------------------------- 1 | name: dino-park-front-end 2 | version: 0.0.1 3 | -------------------------------------------------------------------------------- /k8s/templates/00-namespace.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Namespace 4 | metadata: 5 | name: {{ .Values.namespace }} 6 | -------------------------------------------------------------------------------- /k8s/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: ConfigMap 4 | metadata: 5 | name: front-end-features 6 | namespace: {{ .Values.namespace }} 7 | data: 8 | features.json: | 9 | { 10 | {{- range $key, $value := .Values.features }} 11 | "{{ $key | trimPrefix "env_" }}": {{ $value }}, 12 | {{- end }} 13 | "json-end": true 14 | } 15 | --- 16 | apiVersion: apps/v1 17 | kind: Deployment 18 | metadata: 19 | name: {{ .Values.name }}-deployment 20 | namespace: {{ .Values.namespace }} 21 | labels: 22 | app: {{ .Values.name }} 23 | spec: 24 | replicas: {{ .Values.replicas }} 25 | selector: 26 | matchLabels: 27 | app: {{ .Values.name }} 28 | template: 29 | metadata: 30 | labels: 31 | app: {{ .Values.name }} 32 | spec: 33 | containers: 34 | - name: {{ .Values.name }} 35 | image: {{ .Values.docker_registry }}/{{ .Values.name }}:{{ .Values.rev }} 36 | imagePullPolicy: Always 37 | resources: 38 | requests: 39 | memory: 512Mi 40 | limits: 41 | memory: 1Gi 42 | ports: 43 | - containerPort: 80 44 | readinessProbe: 45 | httpGet: 46 | path: /healthz 47 | port: 80 48 | env: 49 | - name: FORCE_UPDATE 50 | value: "{{ .Values.force_update | default 0 }}" 51 | volumeMounts: 52 | - name: front-end-features 53 | mountPath: /config 54 | readOnly: true 55 | volumes: 56 | - name: front-end-features 57 | configMap: 58 | name: front-end-features 59 | -------------------------------------------------------------------------------- /k8s/templates/service.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: {{ .Values.name }}-service 6 | namespace: {{ .Values.namespace }} 7 | spec: 8 | selector: 9 | app: {{ .Values.name }} 10 | ports: 11 | - protocol: TCP 12 | port: 80 13 | targetPort: 80 -------------------------------------------------------------------------------- /k8s/values.yaml: -------------------------------------------------------------------------------- 1 | name: dino-park-front-end 2 | namespace: dino-park 3 | rev: latest 4 | replicas: 1 5 | features: 6 | customInvitationText: true 7 | historyTab: false 8 | editGroupType: true 9 | accessGroupsToggle: true 10 | accessGroupsNav: true -------------------------------------------------------------------------------- /k8s/values/dev-preview.yaml: -------------------------------------------------------------------------------- 1 | name: dino-park-front-end-preview 2 | -------------------------------------------------------------------------------- /k8s/values/dev.yaml: -------------------------------------------------------------------------------- 1 | env: dev 2 | namespace: dinopark-dev 3 | -------------------------------------------------------------------------------- /k8s/values/prod.yaml: -------------------------------------------------------------------------------- 1 | env: prod 2 | namespace: dinopark-prod 3 | replicas: 3 -------------------------------------------------------------------------------- /k8s/values/test-preview.yaml: -------------------------------------------------------------------------------- 1 | name: dino-park-front-end-preview 2 | -------------------------------------------------------------------------------- /k8s/values/test.yaml: -------------------------------------------------------------------------------- 1 | env: test 2 | namespace: dinopark-test 3 | -------------------------------------------------------------------------------- /myke.yml: -------------------------------------------------------------------------------- 1 | project: dino-park-front-end 2 | desc: The new front-end for DinoPark. 3 | env: 4 | docker_registry: 320464205386.dkr.ecr.us-west-2.amazonaws.com 5 | name: dino-park-front-end 6 | rev: latest 7 | default_base: / 8 | tasks: 9 | run-local: 10 | cmd: npm run serve 11 | run-local-online: 12 | cmd: |- 13 | export DP_K8S=1 14 | npm run serve 15 | install-local: 16 | cmd: npm install 17 | docker: 18 | cmd: docker build --build-arg baseurl={{ .baseurl | default .default_base }} -t {{ .docker_registry }}/{{ .name }}:{{ .rev }} -f Dockerfile . 19 | push-image: 20 | cmd: docker push {{ .docker_registry }}/{{ .name }}:{{ .rev }} 21 | deploy: 22 | cmd: | 23 | helm template -f k8s/values.yaml -f k8s/values/{{ .deploy_env | required }}.yaml \ 24 | --set docker_registry={{ .docker_registry }},rev={{ .rev }} k8s/ | kubectl apply -f - 25 | deploy-preview: 26 | cmd: | 27 | helm template -f k8s/values.yaml -f k8s/values/{{ .deploy_env | required }}.yaml -f k8s/values/{{ .deploy_env | required }}-preview.yaml \ 28 | --set docker_registry={{ .docker_registry }},rev={{ .rev }} k8s/ | kubectl apply -f - 29 | -------------------------------------------------------------------------------- /nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | 4 | #charset koi8-r; 5 | #access_log /var/log/nginx/host.access.log main; 6 | 7 | root /usr/share/nginx/html; 8 | 9 | location /healthz { 10 | return 204; 11 | } 12 | 13 | location / { 14 | try_files $uri $uri/ /index.html; 15 | } 16 | 17 | location = /index.html { 18 | add_header Cache-Control no-store; 19 | } 20 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mozillians-front-end", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "export SET NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service serve", 7 | "build": "export SET NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service build", 8 | "lint": "export SET NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service lint", 9 | "test:unit": "export SET NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service test:unit --verbose", 10 | "inspect": "export SET NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service inspect" 11 | }, 12 | "dependencies": { 13 | "@fluent/bundle": "^0.15.1", 14 | "@types/jest": "^25.2.3", 15 | "apollo-boost": "^0.4.9", 16 | "apollo-cache-inmemory": "^1.6.6", 17 | "blueimp-load-image": "^5.12.0", 18 | "core-js": "^3.6.5", 19 | "cropd": "^1.0.3", 20 | "downshift": "^5.4.2", 21 | "eslint-plugin-jest": "^23.13.2", 22 | "graphql": "^15.1.0", 23 | "identicon.js": "^2.3.3", 24 | "insane": "^2.6.2", 25 | "lodash.throttle": "^4.1.1", 26 | "marked": "^1.1.0", 27 | "object.pick": "^1.3.0", 28 | "preact": "^10.4.4", 29 | "vue": "^2.6.11", 30 | "vue-apollo": "^3.0.3", 31 | "vue-router": "^3.3.2", 32 | "vuex": "^3.4.0" 33 | }, 34 | "devDependencies": { 35 | "@babel/preset-env": "^7.10.2", 36 | "@vue/cli-plugin-babel": "^4.4.1", 37 | "@vue/cli-plugin-eslint": "^4.4.1", 38 | "@vue/cli-plugin-unit-jest": "^4.4.1", 39 | "@vue/cli-service": "^4.4.1", 40 | "@vue/eslint-config-airbnb": "^5.0.2", 41 | "@vue/test-utils": "^1.0.3", 42 | "babel-core": "7.0.0-bridge.0", 43 | "babel-eslint": "^10.1.0", 44 | "babel-jest": "^26.0.1", 45 | "babel-plugin-module-resolver": "^4.0.0", 46 | "eslint": "^7.2.0", 47 | "eslint-config-prettier": "^6.11.0", 48 | "eslint-plugin-vue": "^6.2.2", 49 | "husky": "^4.2.5", 50 | "i18n-lint": "github:jwarby/i18n-lint", 51 | "image-webpack-loader": "^6.0.0", 52 | "jest-raw-loader": "^1.0.1", 53 | "prettier": "^2.0.5", 54 | "pretty-quick": "^2.0.1", 55 | "raw-loader": "^4.0.1", 56 | "svg-sprite-loader": "^5.0.0", 57 | "vue-axe": "^2.3.0", 58 | "vue-template-compiler": "^2.6.11", 59 | "yaml": "^1.10.0" 60 | }, 61 | "husky": { 62 | "hooks": { 63 | "pre-commit": "i18n-lint -t '{{,}}' \"src/**/*.vue\" && pretty-quick --staged" 64 | } 65 | }, 66 | "browserslist": [ 67 | "> 0.5%", 68 | "last 2 Firefox versions", 69 | "Firefox ESR", 70 | "not ie < 12", 71 | "not op_mini all" 72 | ], 73 | "resolutions": { 74 | "event-stream": "3.3.4" 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /public/contribute.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "DinoPark", 3 | "description": "DinoPark - people.mozilla.org - the Mozilla people directory.", 4 | "repository": { 5 | "url": "https://github.com/mozilla-iam/dino-park-issues", 6 | "license": "MPL2/MIT" 7 | }, 8 | "bugs": { 9 | "list": "https://github.com/mozilla-iam/dino-park-issues", 10 | "report": "https://github.com/mozilla-iam/dino-park-issues" 11 | }, 12 | "participate": { 13 | "docs": "https://github.com/mozilla-iam/dino-park-issues", 14 | "chat": { 15 | "url": "https://chat.mozilla.org/#/room/#iam:mozilla.org" 16 | } 17 | }, 18 | "urls": { 19 | "prod": "https://people.mozilla.org/" 20 | }, 21 | "keywords": ["profile", "identity", "mozillians", "iam", "access", "rust"] 22 | } 23 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla-iam/dino-park-front-end/58b1836a96d72adfe090ccfdf7151b715316357d/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 14 | 20 | Mozilla People Directory 21 | 22 | 23 | 29 |
30 | 31 | 32 | -------------------------------------------------------------------------------- /public/logbackin.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 24 | Mozilla People Directory 25 | 26 | 27 |
Successfully logged out. Log back in.
28 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /public/opensearch.xml: -------------------------------------------------------------------------------- 1 | 3 | Mozilla People Directory 4 | Search the Mozilla People Directory 5 | 7 | UTF-8 8 | https://people.mozilla.org 9 | data:image/x-icon;base64, 10 | AAABAAEAEBAAAAAAAABoBQAAFgAAACgAAAAQAAAAIAAAAAEACAAAAAAAAAEAAAAAAAAAAAAAAAEA 11 | AAABAAAAAAAAAACAAACAAAAAgIAAgAAAAIAAgACAgAAAwMDAAMDcwADwyqYABAQEAAgICAAMDAwA 12 | ERERABYWFgAcHBwAIiIiACkpKQBVVVUATU1NAEJCQgA5OTkAgHz/AFBQ/wCTANYA/+zMAMbW7wDW 13 | 5+cAkKmtAAAAMwAAAGYAAACZAAAAzAAAMwAAADMzAAAzZgAAM5kAADPMAAAz/wAAZgAAAGYzAABm 14 | ZgAAZpkAAGbMAABm/wAAmQAAAJkzAACZZgAAmZkAAJnMAACZ/wAAzAAAAMwzAADMZgAAzJkAAMzM 15 | AADM/wAA/2YAAP+ZAAD/zAAzAAAAMwAzADMAZgAzAJkAMwDMADMA/wAzMwAAMzMzADMzZgAzM5kA 16 | MzPMADMz/wAzZgAAM2YzADNmZgAzZpkAM2bMADNm/wAzmQAAM5kzADOZZgAzmZkAM5nMADOZ/wAz 17 | zAAAM8wzADPMZgAzzJkAM8zMADPM/wAz/zMAM/9mADP/mQAz/8wAM///AGYAAABmADMAZgBmAGYA 18 | mQBmAMwAZgD/AGYzAABmMzMAZjNmAGYzmQBmM8wAZjP/AGZmAABmZjMAZmZmAGZmmQBmZswAZpkA 19 | AGaZMwBmmWYAZpmZAGaZzABmmf8AZswAAGbMMwBmzJkAZszMAGbM/wBm/wAAZv8zAGb/mQBm/8wA 20 | zAD/AP8AzACZmQAAmTOZAJkAmQCZAMwAmQAAAJkzMwCZAGYAmTPMAJkA/wCZZgAAmWYzAJkzZgCZ 21 | ZpkAmWbMAJkz/wCZmTMAmZlmAJmZmQCZmcwAmZn/AJnMAACZzDMAZsxmAJnMmQCZzMwAmcz/AJn/ 22 | AACZ/zMAmcxmAJn/mQCZ/8wAmf//AMwAAACZADMAzABmAMwAmQDMAMwAmTMAAMwzMwDMM2YAzDOZ 23 | AMwzzADMM/8AzGYAAMxmMwCZZmYAzGaZAMxmzACZZv8AzJkAAMyZMwDMmWYAzJmZAMyZzADMmf8A 24 | zMwAAMzMMwDMzGYAzMyZAMzMzADMzP8AzP8AAMz/MwCZ/2YAzP+ZAMz/zADM//8AzAAzAP8AZgD/ 25 | AJkAzDMAAP8zMwD/M2YA/zOZAP8zzAD/M/8A/2YAAP9mMwDMZmYA/2aZAP9mzADMZv8A/5kAAP+Z 26 | MwD/mWYA/5mZAP+ZzAD/mf8A/8wAAP/MMwD/zGYA/8yZAP/MzAD/zP8A//8zAMz/ZgD//5kA///M 27 | AGZm/wBm/2YAZv//AP9mZgD/Zv8A//9mACEApQBfX18Ad3d3AIaGhgCWlpYAy8vLALKysgDX19cA 28 | 3d3dAOPj4wDq6uoA8fHxAPj4+ADw+/8ApKCgAICAgAAAAP8AAP8AAAD//wD/AAAA/wD/AP//AAD/ 29 | //8ACgoKCgoKCgoKCgoKCgoKCgoKCgoHAQEMbQoKCgoKCgoAAAdDH/kgHRIAAAAAAAAAAADrHfn5 30 | ASQQAAAAAAAAAArsBx0B+fkgHesAAAAAAAD/Cgwf+fn5IA4dEus/IvcACgcMAfkg+QEB+SABHush 31 | bf8QHR/5HQH5+QEdHetEHx4K7B/5+QH5+fkdDBL5+SBE/wwdJfkf+fn5AR8g+fkfEArsCh/5+QEe 32 | JR/5+SAeBwAACgoe+SAlHwFAEhAfAAAAAPcKHh8eASYBHhAMAAAAAAAA9EMdIB8gHh0dBwAAAAAA 33 | AAAA7BAdQ+wHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP//AADwfwAAwH8AAMB/AAAAPwAAAAEAAAAA 34 | AAAAAAAAAAAAAAAAAAAAAQAAgAcAAIAPAADADwAA8D8AAP//AAA= 35 | 36 | -------------------------------------------------------------------------------- /public/reload.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 20 | Mozilla People Directory 21 | 22 | 23 |
Session expired.
24 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /public/webapp/m-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla-iam/dino-park-front-end/58b1836a96d72adfe090ccfdf7151b715316357d/public/webapp/m-192.png -------------------------------------------------------------------------------- /public/webapp/m.512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla-iam/dino-park-front-end/58b1836a96d72adfe090ccfdf7151b715316357d/public/webapp/m.512.png -------------------------------------------------------------------------------- /public/webapp/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "background_color": "white", 3 | "description": "Mozilla People Directory", 4 | "display": "standalone", 5 | "icons": [ 6 | { 7 | "src": "/webapp/m-192.png", 8 | "sizes": "192x192", 9 | "type": "image/png" 10 | }, 11 | { 12 | "src": "/webapp/m-512.png", 13 | "sizes": "512x512", 14 | "type": "image/png" 15 | } 16 | ], 17 | "name": "Mozilla People Directory", 18 | "short_name": "PMO", 19 | "start_url": "/", 20 | "theme_color": "white" 21 | } 22 | -------------------------------------------------------------------------------- /src/assets/images/chevron-down-blue-60.svg: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/assets/images/chevron-down-white.svg: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/assets/images/dino-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla-iam/dino-park-front-end/58b1836a96d72adfe090ccfdf7151b715316357d/src/assets/images/dino-1.png -------------------------------------------------------------------------------- /src/assets/images/dino-1@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla-iam/dino-park-front-end/58b1836a96d72adfe090ccfdf7151b715316357d/src/assets/images/dino-1@2x.png -------------------------------------------------------------------------------- /src/assets/images/dino-1@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla-iam/dino-park-front-end/58b1836a96d72adfe090ccfdf7151b715316357d/src/assets/images/dino-1@3x.png -------------------------------------------------------------------------------- /src/assets/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla-iam/dino-park-front-end/58b1836a96d72adfe090ccfdf7151b715316357d/src/assets/images/favicon.png -------------------------------------------------------------------------------- /src/assets/images/mozilla-m.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/assets/images/mozilla.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | 18 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/assets/images/people-dots.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla-iam/dino-park-front-end/58b1836a96d72adfe090ccfdf7151b715316357d/src/assets/images/people-dots.png -------------------------------------------------------------------------------- /src/assets/images/people-dots@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla-iam/dino-park-front-end/58b1836a96d72adfe090ccfdf7151b715316357d/src/assets/images/people-dots@2x.png -------------------------------------------------------------------------------- /src/assets/images/quad.svg: -------------------------------------------------------------------------------- 1 | 9 | 17 | 18 | -------------------------------------------------------------------------------- /src/assets/images/skewed-triangle.svg: -------------------------------------------------------------------------------- 1 | 8 | 13 | 14 | -------------------------------------------------------------------------------- /src/assets/images/target.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla-iam/dino-park-front-end/58b1836a96d72adfe090ccfdf7151b715316357d/src/assets/images/target.png -------------------------------------------------------------------------------- /src/assets/images/target@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla-iam/dino-park-front-end/58b1836a96d72adfe090ccfdf7151b715316357d/src/assets/images/target@2x.png -------------------------------------------------------------------------------- /src/assets/images/target@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla-iam/dino-park-front-end/58b1836a96d72adfe090ccfdf7151b715316357d/src/assets/images/target@3x.png -------------------------------------------------------------------------------- /src/assets/images/tour-foundation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla-iam/dino-park-front-end/58b1836a96d72adfe090ccfdf7151b715316357d/src/assets/images/tour-foundation.png -------------------------------------------------------------------------------- /src/assets/images/tour-heads.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla-iam/dino-park-front-end/58b1836a96d72adfe090ccfdf7151b715316357d/src/assets/images/tour-heads.png -------------------------------------------------------------------------------- /src/assets/images/tour-technology.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla-iam/dino-park-front-end/58b1836a96d72adfe090ccfdf7151b715316357d/src/assets/images/tour-technology.png -------------------------------------------------------------------------------- /src/assets/images/tricrowd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla-iam/dino-park-front-end/58b1836a96d72adfe090ccfdf7151b715316357d/src/assets/images/tricrowd.png -------------------------------------------------------------------------------- /src/assets/images/tricrowd@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla-iam/dino-park-front-end/58b1836a96d72adfe090ccfdf7151b715316357d/src/assets/images/tricrowd@2x.png -------------------------------------------------------------------------------- /src/assets/images/user-demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla-iam/dino-park-front-end/58b1836a96d72adfe090ccfdf7151b715316357d/src/assets/images/user-demo.png -------------------------------------------------------------------------------- /src/assets/js/avatars.js: -------------------------------------------------------------------------------- 1 | export default function avatarUrl(profilePicturePath, size = 264, own = false) { 2 | const url = new URL(profilePicturePath, window.location.href); 3 | url.hostname = window.location.hostname; 4 | url.searchParams.set('size', size); 5 | url.searchParams.set('own', own); 6 | return url.toString(); 7 | } 8 | -------------------------------------------------------------------------------- /src/assets/js/content-lookup.js: -------------------------------------------------------------------------------- 1 | export default { 2 | contact: 'Contact', 3 | accounts: 'Accounts', 4 | languages: 'Languages', 5 | tags: 'Tags', 6 | 'access-groups': 'Access Groups', 7 | keys: 'Keys', 8 | identities: 'Identities', 9 | relations: 'relations', 10 | 'personal-info': 'Primary Info', 11 | }; 12 | -------------------------------------------------------------------------------- /src/assets/js/display-levels.js: -------------------------------------------------------------------------------- 1 | export const DISPLAY_LEVELS = { 2 | public: { 3 | label: 'Public', 4 | value: 'PUBLIC', 5 | icon: 'world', 6 | }, 7 | authenticated: { 8 | label: 'Registered', 9 | value: 'AUTHENTICATED', 10 | icon: 'lock', 11 | }, 12 | vouched: { 13 | label: 'Active', 14 | value: 'VOUCHED', 15 | icon: 'activity', 16 | }, 17 | ndaed: { 18 | label: "NDA'd", 19 | value: 'NDAED', 20 | icon: 'triangle', 21 | }, 22 | staff: { 23 | label: 'Staff', 24 | value: 'STAFF', 25 | icon: 'staff', 26 | }, 27 | private: { 28 | label: 'Private', 29 | value: 'PRIVATE', 30 | icon: 'private', 31 | }, 32 | }; 33 | 34 | const DISPLAY_PUBLIC_ONLY = [DISPLAY_LEVELS.public]; 35 | const DISPLAY_NOT_PRIVATE = [ 36 | DISPLAY_LEVELS.staff, 37 | DISPLAY_LEVELS.ndaed, 38 | // DISPLAY_LEVELS.vouched, 39 | DISPLAY_LEVELS.authenticated, 40 | DISPLAY_LEVELS.public, 41 | ]; 42 | const DISPLAY_ANY = [ 43 | DISPLAY_LEVELS.private, 44 | DISPLAY_LEVELS.staff, 45 | DISPLAY_LEVELS.ndaed, 46 | // DISPLAY_LEVELS.vouched, 47 | DISPLAY_LEVELS.authenticated, 48 | DISPLAY_LEVELS.public, 49 | ]; 50 | const DISPLAY_PRIVATE_STAFF = [DISPLAY_LEVELS.private, DISPLAY_LEVELS.staff]; 51 | 52 | const VALID_DISPLAY_LEVELS = { 53 | primaryUsername: DISPLAY_PUBLIC_ONLY, 54 | phoneNumbers: DISPLAY_ANY, 55 | 'accessInformation.ldap': DISPLAY_PRIVATE_STAFF, 56 | 'accessInformation.mozilliansorg': DISPLAY_NOT_PRIVATE, 57 | }; 58 | 59 | const NON_STAFF_DISPLAY_LEVELS = { 60 | primaryEmail: DISPLAY_ANY, 61 | }; 62 | 63 | export function displayLevelsFor(field, scope = null) { 64 | return ( 65 | (scope && !scope.isStaff && NON_STAFF_DISPLAY_LEVELS[field]) || 66 | VALID_DISPLAY_LEVELS[field] || 67 | DISPLAY_NOT_PRIVATE 68 | ); 69 | } 70 | -------------------------------------------------------------------------------- /src/assets/js/features.js: -------------------------------------------------------------------------------- 1 | import Fetcher from '@/assets/js/fetcher'; 2 | export default class Features { 3 | constructor() { 4 | this.fetcher = new Fetcher({ failoverOn: [302] }); 5 | } 6 | async get() { 7 | try { 8 | return await this.fetcher.fetch('/config/features.json'); 9 | } catch (e) { 10 | console.error(e.message); 11 | throw new Error(e.message); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/assets/js/identicon-avatar.js: -------------------------------------------------------------------------------- 1 | import Identicon from 'identicon.js'; 2 | 3 | function hex(buffer) { 4 | const hexCodes = []; 5 | const view = new DataView(buffer); 6 | for (let i = 0; i < view.byteLength; i += 4) { 7 | const value = view.getUint32(i); 8 | const stringValue = value.toString(16); 9 | const padding = '00000000'; 10 | const paddedValue = (padding + stringValue).slice(-padding.length); 11 | hexCodes.push(paddedValue); 12 | } 13 | return hexCodes.join(''); 14 | } 15 | 16 | function sha256(str) { 17 | const buffer = new TextEncoder('utf-8').encode(str); 18 | return crypto.subtle.digest('SHA-256', buffer).then(hex); 19 | } 20 | 21 | async function generateIdenticon(username, size) { 22 | let hash; 23 | if (window.crypto && window.crypto.subtle) { 24 | hash = await sha256(username); 25 | } else { 26 | hash = '04f8996da763b7a969b1028ee3007569eaf3a635486ddab211d512c85b9df8fb'; // 'user' 27 | } 28 | const identicon = new Identicon(hash, { 29 | size, 30 | format: 'svg', 31 | }); 32 | return `data:image/svg+xml;base64,${identicon.toString()}`; 33 | } 34 | 35 | export default generateIdenticon; 36 | -------------------------------------------------------------------------------- /src/assets/js/onboarding.js: -------------------------------------------------------------------------------- 1 | const TOUR = [ 2 | { num: 1, total: 3, phase: 1, selector: '.hl-search-form' }, 3 | { 4 | num: 2, 5 | total: 3, 6 | phase: 1, 7 | selector: '.preview-as__button', 8 | }, 9 | { num: 3, total: 3, phase: 1, selector: '.profile__intro > .edit-button' }, 10 | { 11 | num: 1, 12 | total: 2, 13 | phase: 2, 14 | selector: '.edit-personal-info__picture-edit-button', 15 | }, 16 | { 17 | num: 2, 18 | total: 2, 19 | phase: 2, 20 | selector: '#field-first-name + .privacy-select', 21 | }, 22 | ]; 23 | 24 | const PHASE_2 = 4; 25 | 26 | class Onboarding { 27 | constructor() { 28 | this.modal = false; 29 | this.tooltipTour = null; 30 | this.isPhase1 = false; 31 | this.isPhase2 = false; 32 | } 33 | 34 | enable() { 35 | this.modal = true; 36 | } 37 | 38 | modalDone() { 39 | this.modal = false; 40 | this.tooltipTour = 1; 41 | this.isPhase1 = true; 42 | } 43 | 44 | tttStep() { 45 | return TOUR[this.tooltipTour - 1] || null; 46 | } 47 | 48 | tttNext() { 49 | this.tooltipTour += 1; 50 | if (this.tooltipTour >= PHASE_2) { 51 | this.isPhase1 = false; 52 | this.isPhase2 = true; 53 | } 54 | } 55 | 56 | tttSkip() { 57 | this.tooltipTour = null; 58 | this.isPhase1 = false; 59 | this.isPhase2 = false; 60 | } 61 | 62 | tttNextPhase() { 63 | if (this.isPhase1) { 64 | this.tttStartPhase2(); 65 | } else { 66 | this.tooltipTour = null; 67 | this.isPhase1 = false; 68 | this.isPhase2 = false; 69 | } 70 | } 71 | 72 | tttStartPhase2() { 73 | if (this.tooltipTour && this.tooltipTour < PHASE_2) { 74 | this.tooltipTour = PHASE_2; 75 | this.isPhase1 = false; 76 | this.isPhase2 = true; 77 | } 78 | } 79 | } 80 | 81 | export default Onboarding; 82 | -------------------------------------------------------------------------------- /src/assets/js/related.js: -------------------------------------------------------------------------------- 1 | import Fetcher from '@/assets/js/fetcher'; 2 | 3 | const fetcher = new Fetcher(); 4 | 5 | class Related { 6 | constructor() { 7 | this.manager = null; 8 | this.directs = []; 9 | this.peers = []; 10 | this.show = false; 11 | } 12 | 13 | async update(username) { 14 | try { 15 | const { manager, directs, peers } = await fetcher.fetch( 16 | `/api/v4/orgchart/related/${encodeURIComponent(username)}`, 17 | ); 18 | this.manager = manager || null; 19 | this.directs = directs || []; 20 | this.peers = peers || []; 21 | } catch (e) { 22 | this.manager = null; 23 | this.directs = []; 24 | this.peers = []; 25 | } 26 | this.show = 27 | this.manager != null || this.directs.length > 0 || this.peers.length > 0; 28 | } 29 | } 30 | 31 | export default Related; 32 | -------------------------------------------------------------------------------- /src/assets/js/reload.js: -------------------------------------------------------------------------------- 1 | function reload() { 2 | const reloadUrl = new URL('/reload.html', window.location.origin); 3 | reloadUrl.search = new URLSearchParams([ 4 | ['reload', window.location.href], 5 | ]).toString(); 6 | window.location.href = reloadUrl.toString(); 7 | } 8 | 9 | export { reload as default }; 10 | -------------------------------------------------------------------------------- /src/assets/js/scope.js: -------------------------------------------------------------------------------- 1 | /* 2 | Exposes the scope of a user. 3 | 4 | Scopes work inclusive e.g. the scope staff (isStaff) allows access to display 5 | levels [public, authenticated, (vouched), staff]. 6 | */ 7 | class Scope { 8 | constructor(user = {}) { 9 | this.update(user); 10 | } 11 | 12 | update(user) { 13 | const { 14 | uuid: { value: uuid = '00000000-0000-0000-0000-000000000000' } = {}, 15 | primaryUsername: { value: username = '' } = {}, 16 | staffInformation: { staff: { value: isStaff = false } = {} } = {}, 17 | accessInformation: { 18 | mozilliansorg: { values: mozilliansorgGroups = {} } = {}, 19 | ldap: { values: ldapGroups = {} } = {}, 20 | } = {}, 21 | } = user || {}; 22 | this.username = username; 23 | this.uuid = uuid; 24 | this.isLoggedIn = Boolean(username || user?.loggedIn); 25 | this.firstTime = 26 | this.isLoggedIn && (username === '' || username.startsWith('r--')); 27 | this.isReady = Boolean(username || user?.profileError); 28 | this.isStaff = isStaff; 29 | this.isNdaed = 30 | 'nda' in (mozilliansorgGroups || {}) || 31 | 'ghe_group_curators' in (mozilliansorgGroups || {}) || 32 | 'contingentworkernda' in (mozilliansorgGroups || {}); 33 | this.isLdap = Boolean(ldapGroups); 34 | this.isGroupCreator = 35 | 'group_creators' in (mozilliansorgGroups || {}) || 36 | 'group_admins' in (mozilliansorgGroups || {}); 37 | this.profileError = user?.profileError 38 | } 39 | 40 | hasTrust(trust) { 41 | if (this.isStaff) { 42 | return true; 43 | } 44 | if (this.isNdaed) { 45 | return trust !== 'Staff'; 46 | } 47 | return trust === 'Authenticated'; 48 | } 49 | } 50 | 51 | export default Scope; 52 | -------------------------------------------------------------------------------- /src/assets/js/scrolling.js: -------------------------------------------------------------------------------- 1 | export default { 2 | fromEditToSelf(to, from, store) { 3 | return ( 4 | to.name === 'Profile' && 5 | from.name === 'Edit Profile' && 6 | to.params.username === store.state.scope.username 7 | ); 8 | }, 9 | toEdit(to) { 10 | return to.name === 'Edit Profile'; 11 | }, 12 | toProfile(to, from) { 13 | return to.name === 'Profile' && to.path !== from.path; 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /src/assets/js/swipe.js: -------------------------------------------------------------------------------- 1 | class Swipe { 2 | constructor(element, left = () => {}, right = () => {}) { 3 | this.element = element; 4 | this.touchStartX = 0; 5 | this.touchStartY = 0; 6 | this.touchEndX = 0; 7 | this.touchEndY = 0; 8 | this.left = left; 9 | this.right = right; 10 | this.touchstart = ({ changedTouches: [{ screenX, screenY }] }) => { 11 | this.touchStartX = screenX; 12 | this.touchStartY = screenY; 13 | }; 14 | this.touchend = ({ changedTouches: [{ screenX, screenY }] }) => { 15 | this.touchEndX = screenX; 16 | this.touchEndY = screenY; 17 | this.handleSwipe(); 18 | }; 19 | this.element.addEventListener('touchstart', this.touchstart); 20 | this.element.addEventListener('touchend', this.touchend); 21 | } 22 | 23 | handleSwipe() { 24 | const diffX = this.touchEndX - this.touchStartX; 25 | const absDiffY = Math.abs(this.touchEndY - this.touchStartY); 26 | if (diffX > 10 && absDiffY < 50) { 27 | this.right(); 28 | } 29 | if (diffX < -10 && absDiffY < 50) { 30 | this.left(); 31 | } 32 | } 33 | 34 | stop() { 35 | this.element.removeEventListener(''); 36 | } 37 | } 38 | 39 | export default Swipe; 40 | -------------------------------------------------------------------------------- /src/assets/js/trap-focus.js: -------------------------------------------------------------------------------- 1 | function trapFocus(event) { 2 | const element = event.target; 3 | const KEYCODE_TAB = 9; 4 | 5 | if (!element.hasAttribute('trapped')) { 6 | element.setAttribute('trapped', ''); 7 | element.addEventListener('keydown', (e) => { 8 | const focusableEls = element.querySelectorAll( 9 | 'a[href], button:not([disabled]), textarea:not([disabled]), input[type="text"]:not([disabled]), input[type="radio"]:not([disabled]), input[type="checkbox"]:not([disabled]), select:not([disabled])', 10 | ); 11 | const firstFocusableEl = focusableEls[0]; 12 | const lastFocusableEl = focusableEls[focusableEls.length - 1]; 13 | const isTabPressed = e.key === 'Tab' || e.keyCode === KEYCODE_TAB; 14 | 15 | if (!isTabPressed) { 16 | return; 17 | } 18 | 19 | if (e.shiftKey) { 20 | /* shift + tab */ if (document.activeElement === firstFocusableEl) { 21 | lastFocusableEl.focus(); 22 | e.preventDefault(); 23 | } 24 | } /* tab */ else if (document.activeElement === lastFocusableEl) { 25 | firstFocusableEl.focus(); 26 | e.preventDefault(); 27 | } 28 | }); 29 | } 30 | } 31 | 32 | function bindFocusTrap(element) { 33 | element.addEventListener('keydown', trapFocus); 34 | } 35 | 36 | function unbindFocusTrap(element) { 37 | element.removeEventListener('keydown', trapFocus); 38 | } 39 | 40 | export { trapFocus, bindFocusTrap, unbindFocusTrap }; 41 | -------------------------------------------------------------------------------- /src/assets/svg/activity.svg: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | -------------------------------------------------------------------------------- /src/assets/svg/at-sign.svg: -------------------------------------------------------------------------------- 1 | 2 | 12 | 20 | 21 | -------------------------------------------------------------------------------- /src/assets/svg/bell.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/svg/bookmark.svg: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | -------------------------------------------------------------------------------- /src/assets/svg/chain.svg: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /src/assets/svg/check.svg: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | -------------------------------------------------------------------------------- /src/assets/svg/chevron-down.svg: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | -------------------------------------------------------------------------------- /src/assets/svg/chevron-left.svg: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | -------------------------------------------------------------------------------- /src/assets/svg/chevron-right.svg: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | -------------------------------------------------------------------------------- /src/assets/svg/chevron-up.svg: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | -------------------------------------------------------------------------------- /src/assets/svg/circle-fill.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/assets/svg/circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/assets/svg/clock.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/svg/collapse.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/assets/svg/copy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/assets/svg/cpg.svg: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /src/assets/svg/crown-fill.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/assets/svg/crown.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/assets/svg/dashboard.svg: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /src/assets/svg/dino.svg: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /src/assets/svg/discourse.svg: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | -------------------------------------------------------------------------------- /src/assets/svg/edit.svg: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /src/assets/svg/email.svg: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /src/assets/svg/envelope.svg: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /src/assets/svg/expand.svg: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /src/assets/svg/external.svg: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /src/assets/svg/eye.svg: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /src/assets/svg/faq.svg: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /src/assets/svg/github.svg: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /src/assets/svg/grid.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/assets/svg/idcard.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 17 | 26 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/assets/svg/info.svg: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /src/assets/svg/irc.svg: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /src/assets/svg/keys.svg: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /src/assets/svg/list.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/assets/svg/lock.svg: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /src/assets/svg/log-out.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/svg/mail-outline.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/svg/mail.svg: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /src/assets/svg/matrix.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/assets/svg/moz.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/assets/svg/org-chart.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/svg/pencil.svg: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /src/assets/svg/phone-forwarded.svg: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /src/assets/svg/phone.svg: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /src/assets/svg/plus.svg: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /src/assets/svg/private.svg: -------------------------------------------------------------------------------- 1 | 2 | 10 | 21 | 22 | -------------------------------------------------------------------------------- /src/assets/svg/question-mark-circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/assets/svg/rotate.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/assets/svg/search.svg: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /src/assets/svg/slack.svg: -------------------------------------------------------------------------------- 1 | 2 | 9 | 12 | 15 | 18 | 21 | 24 | 27 | 30 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/assets/svg/sliders.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/svg/square.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | -------------------------------------------------------------------------------- /src/assets/svg/staff.svg: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /src/assets/svg/successSquare.svg: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /src/assets/svg/toggle-right.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/svg/triangle.svg: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | -------------------------------------------------------------------------------- /src/assets/svg/user-check.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/svg/user-plus.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/svg/user.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/svg/users-outline.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/svg/users.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/svg/world.svg: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/assets/svg/x.svg: -------------------------------------------------------------------------------- 1 | 2 | 6 | 14 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/assets/svg/zoom.svg: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /src/components/Fluent.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 26 | -------------------------------------------------------------------------------- /src/components/_functional/Modal.spec.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Modal from './Modal.vue'; 3 | import getRenderedText from '@/../tests/utils/getRenderedText.js'; 4 | import MockStore from '../../../tests/mocks/mockStore'; 5 | import getMountedComponentWithStore from '../../../tests/utils/getMountedComponentWithStore'; 6 | describe('Modal', () => { 7 | const Constructor = Vue.extend(Modal); 8 | 9 | it('should exist', () => { 10 | const component = new Constructor(MockStore); 11 | expect(component).toBeInstanceOf(Vue); 12 | }); 13 | 14 | it('should show close button if props.closeButton is true', () => { 15 | const wrapper = getMountedComponentWithStore(Modal, { 16 | propsData: { closeButton: true }, 17 | }); 18 | const closeButton = wrapper.vm.$el.querySelector('.modal__close'); 19 | expect(closeButton).toBeTruthy(); 20 | }); 21 | 22 | it('should show heading if props.heading is passed', () => { 23 | const msg = 'test'; 24 | const text = getRenderedText( 25 | Modal, 26 | { heading: msg }, 27 | '.modal__header > h1' 28 | ); 29 | expect(text).toEqual(msg); 30 | }); 31 | 32 | it('should html passed in as children', () => { 33 | const msg = 'test'; 34 | const component = new Constructor(MockStore); 35 | const newHtml = component.$createElement('article', msg); 36 | component.$slots.default = [newHtml]; 37 | component.$mount(); 38 | const content = component.$el.querySelector('article'); 39 | 40 | expect(content.textContent).toEqual(msg); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /src/components/_mixins/AccountsMixin.spec.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import AccountsMixin from './AccountsMixin.vue'; 3 | 4 | describe('AccountsMixin', () => { 5 | const MixinConstructor = Vue.extend(AccountsMixin); 6 | const component = new MixinConstructor(); 7 | it('should exist', () => { 8 | expect(component.availableAccounts.length).toBeGreaterThan(0); 9 | expect(typeof component.EXTERNAL_ACCOUNTS).toEqual('object'); 10 | }); 11 | 12 | it('should have availableAccounts that are all in EXTERNAL_ACCOUNTS', () => { 13 | const missing = false; 14 | for (let i = 0, len = component.availableAccounts.length; i < len; i++) { 15 | if (!(component.availableAccounts[i] in component.EXTERNAL_ACCOUNTS)) { 16 | missing = true; 17 | } 18 | } 19 | expect(missing).toEqual(false); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /src/components/_mixins/CompanyMixin.spec.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import CompanyMixin from './CompanyMixin.vue'; 3 | 4 | describe('CompanyMixin', () => { 5 | const MixinConstructor = Vue.extend(CompanyMixin); 6 | const component = new MixinConstructor(); 7 | it('should exist', () => { 8 | expect(component).toBeTruthy(); 9 | }); 10 | describe('company()', () => { 11 | it('should return null when bad params are passed', () => { 12 | const test = component.company(null, ''); 13 | expect(test).toBeNull(); 14 | }); 15 | 16 | it('should return "Mozilla Foundation" when primary email ends in "mozillafoundation.org" from valid stafff', () => { 17 | const test = component.company( 18 | { staff: { value: true } }, 19 | 'test@mozillafoundation.org', 20 | ); 21 | expect(test).toEqual('Mozilla Foundation'); 22 | }); 23 | 24 | it('should return "Mozilla Corporation" when primary email is anything other than "mozillafoundation.org" from valid stafff', () => { 25 | const test = component.company( 26 | { staff: { value: true } }, 27 | 'test@test.org', 28 | ); 29 | expect(test).toEqual('Mozilla Corporation'); 30 | }); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /src/components/_mixins/CompanyMixin.vue: -------------------------------------------------------------------------------- 1 | 16 | -------------------------------------------------------------------------------- /src/components/_mixins/EnvironmentMixin.vue: -------------------------------------------------------------------------------- 1 | 13 | -------------------------------------------------------------------------------- /src/components/_mixins/LinksMixin.vue: -------------------------------------------------------------------------------- 1 | 30 | -------------------------------------------------------------------------------- /src/components/_mixins/OfficesMixin.vue: -------------------------------------------------------------------------------- 1 | 23 | -------------------------------------------------------------------------------- /src/components/_mixins/PhoneNumbersMixin.spec.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import PhoneNumbersMixin from './PhoneNumbersMixin.vue'; 3 | 4 | describe('PhoneNumbersMixin', () => { 5 | const MixinConstructor = Vue.extend(PhoneNumbersMixin); 6 | const component = new MixinConstructor(); 7 | it('should exist', () => { 8 | expect(component).toBeTruthy(); 9 | }); 10 | describe('destructPhoneKey()', () => { 11 | it('should return destructed phone object', () => { 12 | const destructed = component.destructPhoneKey('Web#1231231234#y', 2); 13 | expect(destructed.view).toEqual('Web'); 14 | expect(destructed.num).toEqual('1231231234'); 15 | expect(destructed.contact).toEqual(true); 16 | }); 17 | 18 | it('should return destructed phone object with defaulted contact', () => { 19 | const destructed = component.destructPhoneKey('Web#1231231234', 2); 20 | expect(destructed.view).toEqual('Web'); 21 | expect(destructed.num).toEqual('1231231234'); 22 | expect(destructed.contact).toEqual(false); 23 | }); 24 | it('should return destructed phone object with defaulted contact and number', () => { 25 | const destructed = component.destructPhoneKey('Web', 2); 26 | expect(destructed.view).toEqual('Web'); 27 | expect(destructed.num).toEqual(2); 28 | expect(destructed.contact).toEqual(false); 29 | }); 30 | it('should return destructed phone object with defaulted contact and number and view', () => { 31 | const destructed = component.destructPhoneKey('', 2); 32 | // TODO: this case is wrong and probably should be addressed 33 | expect(destructed.view).toEqual(''); 34 | expect(destructed.num).toEqual(2); 35 | expect(destructed.contact).toEqual(false); 36 | }); 37 | }); 38 | 39 | describe('constructPhoneKey()', () => { 40 | it('should return constructed phone object', () => { 41 | const constructed = component.constructPhoneKey({ 42 | view: 'Web', 43 | num: '1231231234', 44 | contact: true, 45 | }); 46 | expect(constructed).toEqual('Web#1231231234#y'); 47 | }); 48 | 49 | it('should return constructed phone object with defaulted contact', () => { 50 | const constructed = component.constructPhoneKey({ 51 | view: 'Web', 52 | num: '1231231234', 53 | }); 54 | expect(constructed).toEqual('Web#1231231234#n'); 55 | }); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /src/components/_mixins/PhoneNumbersMixin.vue: -------------------------------------------------------------------------------- 1 | 23 | -------------------------------------------------------------------------------- /src/components/access_group/AccessGroup.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /src/components/access_group/AccessGroupCreate.spec.js: -------------------------------------------------------------------------------- 1 | import { createLocalVue } from '@vue/test-utils'; 2 | import Vue from 'vue'; 3 | import Vuex from 'vuex'; 4 | import AccessGroupCreate from './AccessGroupCreate.vue'; 5 | 6 | const localVue = createLocalVue(); 7 | 8 | localVue.use(Vuex); 9 | 10 | describe('AccessGroupCreate', () => { 11 | const Constructor = Vue.extend(AccessGroupCreate); 12 | 13 | it('should exist', () => { 14 | const component = new Constructor(); 15 | expect(component).toBeInstanceOf(Vue); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /src/components/access_group/AccessGroupDescription.spec.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import AccessGroupDescription from './AccessGroupDescription.vue'; 3 | import getMountedComponentWithStore from '../../../tests/utils/getMountedComponentWithStore'; 4 | 5 | describe('AccessGroupDescription', () => { 6 | it('should exist', () => { 7 | const wrapper = getMountedComponentWithStore(AccessGroupDescription); 8 | expect(wrapper.vm).toBeInstanceOf(Vue); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /src/components/access_group/AccessGroupDetails.spec.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import AccessGroupDetails from './AccessGroupDetails.vue'; 3 | import getMountedComponentWithStore from '../../../tests/utils/getMountedComponentWithStore'; 4 | 5 | describe('AccessGroupDetails', () => { 6 | it('should exist', () => { 7 | const wrapper = getMountedComponentWithStore(AccessGroupDetails); 8 | expect(wrapper.vm).toBeInstanceOf(Vue); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /src/components/access_group/AccessGroupEditPanel.spec.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla-iam/dino-park-front-end/58b1836a96d72adfe090ccfdf7151b715316357d/src/components/access_group/AccessGroupEditPanel.spec.js -------------------------------------------------------------------------------- /src/components/access_group/AccessGroupHistoryEdit.spec.js: -------------------------------------------------------------------------------- 1 | import { createLocalVue } from '@vue/test-utils'; 2 | import Vue from 'vue'; 3 | import Vuex from 'vuex'; 4 | import AccessGroupHistoryEdit from './AccessGroupHistoryEdit.vue'; 5 | 6 | const localVue = createLocalVue(); 7 | 8 | localVue.use(Vuex); 9 | 10 | describe('AccessGroupHistoryEdit', () => { 11 | const Constructor = Vue.extend(AccessGroupHistoryEdit); 12 | 13 | it('should exist', () => { 14 | const component = new Constructor(); 15 | expect(component).toBeInstanceOf(Vue); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /src/components/access_group/AccessGroupIndex.spec.js: -------------------------------------------------------------------------------- 1 | import { createLocalVue } from '@vue/test-utils'; 2 | import Vue from 'vue'; 3 | import Vuex from 'vuex'; 4 | import AccessGroupIndex from './AccessGroupIndex.vue'; 5 | 6 | const localVue = createLocalVue(); 7 | 8 | localVue.use(Vuex); 9 | 10 | describe('AccessGroupIndex', () => { 11 | const Constructor = Vue.extend(AccessGroupIndex); 12 | 13 | it('should exist', () => { 14 | const component = new Constructor(); 15 | expect(component).toBeInstanceOf(Vue); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /src/components/access_group/AccessGroupInformationEdit.spec.js: -------------------------------------------------------------------------------- 1 | import { createLocalVue } from '@vue/test-utils'; 2 | import Vue from 'vue'; 3 | import Vuex from 'vuex'; 4 | import AccessGroupMembersEdit from './AccessGroupMembersEdit.vue'; 5 | 6 | const localVue = createLocalVue(); 7 | 8 | localVue.use(Vuex); 9 | 10 | describe('AccessGroupMembersEdit', () => { 11 | const Constructor = Vue.extend(AccessGroupMembersEdit); 12 | 13 | it('should exist', () => { 14 | const component = new Constructor(); 15 | expect(component).toBeInstanceOf(Vue); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /src/components/access_group/AccessGroupInvitationNotification.spec.js: -------------------------------------------------------------------------------- 1 | import { createLocalVue } from '@vue/test-utils'; 2 | import Vue from 'vue'; 3 | import Vuex from 'vuex'; 4 | import AccessGroupInvitationNotification from './AccessGroupInvitationNotification.vue'; 5 | 6 | const localVue = createLocalVue(); 7 | 8 | localVue.use(Vuex); 9 | 10 | describe('AccessGroupInvitationNotification', () => { 11 | const Constructor = Vue.extend(AccessGroupInvitationNotification); 12 | 13 | it('should exist', () => { 14 | const component = new Constructor(); 15 | expect(component).toBeInstanceOf(Vue); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /src/components/access_group/AccessGroupInvitationsEdit.spec.js: -------------------------------------------------------------------------------- 1 | import { createLocalVue } from '@vue/test-utils'; 2 | import Vue from 'vue'; 3 | import Vuex from 'vuex'; 4 | import AccessGroupInvitationsEdit from './AccessGroupInvitationsEdit.vue'; 5 | 6 | const localVue = createLocalVue(); 7 | 8 | localVue.use(Vuex); 9 | 10 | describe('AccessGroupInvitationsEdit', () => { 11 | const Constructor = Vue.extend(AccessGroupInvitationsEdit); 12 | 13 | it('should exist', () => { 14 | const component = new Constructor(); 15 | expect(component).toBeInstanceOf(Vue); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /src/components/access_group/AccessGroupLeaveConfirmationNotification.js: -------------------------------------------------------------------------------- 1 | import { createLocalVue } from '@vue/test-utils'; 2 | import Vue from 'vue'; 3 | import Vuex from 'vuex'; 4 | import AccessGroupLeaveConfirmationNotification from './AccessGroupLeaveConfirmationNotification.vue'; 5 | 6 | const localVue = createLocalVue(); 7 | 8 | localVue.use(Vuex); 9 | 10 | describe('AccessGroupLeaveConfirmationNotification', () => { 11 | const Constructor = Vue.extend(AccessGroupLeaveConfirmationNotification); 12 | 13 | it('should exist', () => { 14 | const component = new Constructor(); 15 | expect(component).toBeInstanceOf(Vue); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /src/components/access_group/AccessGroupList.spec.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import AccessGroupList from './AccessGroupList.vue'; 3 | import getMountedComponentWithStore from '../../../tests/utils/getMountedComponentWithStore'; 4 | 5 | describe('AccessGroupList', () => { 6 | it('should exist', () => { 7 | const wrapper = getMountedComponentWithStore(AccessGroupList); 8 | expect(wrapper.vm).toBeInstanceOf(Vue); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /src/components/access_group/AccessGroupListItem.spec.js: -------------------------------------------------------------------------------- 1 | import { createLocalVue } from '@vue/test-utils'; 2 | import Vue from 'vue'; 3 | import Vuex from 'vuex'; 4 | import AccessGroupListItem from './AccessGroupListItem.vue'; 5 | 6 | const localVue = createLocalVue(); 7 | 8 | localVue.use(Vuex); 9 | 10 | describe('AccessGroupListItem', () => { 11 | const Constructor = Vue.extend(AccessGroupListItem); 12 | 13 | it('should exist', () => { 14 | const component = new Constructor(); 15 | expect(component).toBeInstanceOf(Vue); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /src/components/access_group/AccessGroupMarkdownGuide.spec.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import AccessGroupMarkdownGuide from './AccessGroupMarkdownGuide.vue'; 3 | import getMountedComponentWithStore from '../../../tests/utils/getMountedComponentWithStore'; 4 | 5 | describe('AccessGroupMarkdownGuide', () => { 6 | it('should exist', () => { 7 | const wrapper = getMountedComponentWithStore(AccessGroupMarkdownGuide); 8 | expect(wrapper.vm).toBeInstanceOf(Vue); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /src/components/access_group/AccessGroupMemberItem.spec.js: -------------------------------------------------------------------------------- 1 | import { createLocalVue } from '@vue/test-utils'; 2 | import Vue from 'vue'; 3 | import Vuex from 'vuex'; 4 | import AccessGroupMemberItem from './AccessGroupMemberItem.vue'; 5 | 6 | const localVue = createLocalVue(); 7 | 8 | localVue.use(Vuex); 9 | 10 | describe('AccessGroupMemberItem', () => { 11 | const Constructor = Vue.extend(AccessGroupMemberItem); 12 | 13 | it('should exist', () => { 14 | const component = new Constructor(); 15 | expect(component).toBeInstanceOf(Vue); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /src/components/access_group/AccessGroupMemberListDisplay.spec.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla-iam/dino-park-front-end/58b1836a96d72adfe090ccfdf7151b715316357d/src/components/access_group/AccessGroupMemberListDisplay.spec.js -------------------------------------------------------------------------------- /src/components/access_group/AccessGroupMembers.spec.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import AccessGroupMembers from './AccessGroupMembers.vue'; 3 | import getMountedComponentWithStore from '../../../tests/utils/getMountedComponentWithStore'; 4 | 5 | describe('AccessGroupMembers', () => { 6 | it('should exist', () => { 7 | const wrapper = getMountedComponentWithStore(AccessGroupMembers); 8 | expect(wrapper.vm).toBeInstanceOf(Vue); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /src/components/access_group/AccessGroupMembersEdit.spec.js: -------------------------------------------------------------------------------- 1 | import { createLocalVue } from '@vue/test-utils'; 2 | import Vue from 'vue'; 3 | import Vuex from 'vuex'; 4 | import AccessGroupInformationEdit from './AccessGroupInformationEdit.vue'; 5 | 6 | const localVue = createLocalVue(); 7 | 8 | localVue.use(Vuex); 9 | 10 | describe('AccessGroupInformationEdit', () => { 11 | const Constructor = Vue.extend(AccessGroupInformationEdit); 12 | 13 | it('should exist', () => { 14 | const component = new Constructor(); 15 | expect(component).toBeInstanceOf(Vue); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /src/components/access_group/AccessGroupMembersTable.spec.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Router from 'vue-router'; 3 | import AccessGroupMembersTable from './AccessGroupMembersTable.vue'; 4 | import getMountedComponentWithStore from '../../../tests/utils/getMountedComponentWithStore'; 5 | 6 | Vue.use(Router); 7 | 8 | describe('AccessGroupMembersTable', () => { 9 | it('should exist', () => { 10 | const wrapper = getMountedComponentWithStore(AccessGroupMembersTable); 11 | expect(wrapper.vm).toBeInstanceOf(Vue); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /src/components/access_group/AccessGroupMembersTableRow.spec.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Router from 'vue-router'; 3 | import AccessGroupMembersTableRow from './AccessGroupMembersTableRow.vue'; 4 | import getMountedComponentWithStore from '../../../tests/utils/getMountedComponentWithStore'; 5 | 6 | Vue.use(Router); 7 | 8 | describe('AccessGroupMembersTableRow', () => { 9 | it('should exist', () => { 10 | const wrapper = getMountedComponentWithStore(AccessGroupMembersTableRow); 11 | expect(wrapper.vm).toBeInstanceOf(Vue); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /src/components/access_group/AccessGroupMembershipManagement.spec.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Router from 'vue-router'; 3 | import AccessGroupMembershipManagement from './AccessGroupMembershipManagement.vue'; 4 | import getMountedComponentWithStore from '../../../tests/utils/getMountedComponentWithStore'; 5 | 6 | Vue.use(Router); 7 | 8 | describe('AccessGroupMembershipManagement', () => { 9 | it('should exist', () => { 10 | const wrapper = getMountedComponentWithStore( 11 | AccessGroupMembershipManagement 12 | ); 13 | expect(wrapper.vm).toBeInstanceOf(Vue); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /src/components/access_group/AccessGroupTOSAcceptanceNotification.spec.js: -------------------------------------------------------------------------------- 1 | import { createLocalVue } from '@vue/test-utils'; 2 | import Vue from 'vue'; 3 | import Vuex from 'vuex'; 4 | import AccessGroupTOSAcceptanceNotification from './AccessGroupTOSAcceptanceNotification.vue'; 5 | 6 | const localVue = createLocalVue(); 7 | 8 | localVue.use(Vuex); 9 | 10 | describe('AccessGroupTOSAcceptanceNotification', () => { 11 | const Constructor = Vue.extend(AccessGroupTOSAcceptanceNotification); 12 | 13 | it('should exist', () => { 14 | const component = new Constructor(); 15 | expect(component).toBeInstanceOf(Vue); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /src/components/access_group/AccessGroupTOSAcceptanceNotification.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 21 | 22 | 41 | -------------------------------------------------------------------------------- /src/components/guide/ProgressDots.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 21 | 22 | 43 | -------------------------------------------------------------------------------- /src/components/profile/PrivacySetting.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 86 | -------------------------------------------------------------------------------- /src/components/profile/ProfileDescription.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 51 | 52 | 81 | -------------------------------------------------------------------------------- /src/components/profile/ProfileName.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 38 | 39 | 85 | -------------------------------------------------------------------------------- /src/components/profile/ProfileTitle.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 17 | 18 | 45 | -------------------------------------------------------------------------------- /src/components/profile/ReportingStructure.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 52 | -------------------------------------------------------------------------------- /src/components/profile/edit/Cropper.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 27 | -------------------------------------------------------------------------------- /src/components/profile/edit/EditFieldTypeSelect.vue: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla-iam/dino-park-front-end/58b1836a96d72adfe090ccfdf7151b715316357d/src/components/profile/edit/EditFieldTypeSelect.vue -------------------------------------------------------------------------------- /src/components/profile/view/EmptyCard.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 25 | -------------------------------------------------------------------------------- /src/components/profile/view/ViewAccessGroups.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 56 | 57 | 73 | -------------------------------------------------------------------------------- /src/components/profile/view/ViewAccounts.vue: -------------------------------------------------------------------------------- 1 | 45 | 46 | 75 | -------------------------------------------------------------------------------- /src/components/profile/view/ViewContact.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 62 | -------------------------------------------------------------------------------- /src/components/profile/view/ViewIdentities.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 70 | 71 | 81 | -------------------------------------------------------------------------------- /src/components/profile/view/ViewLanguages.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 29 | -------------------------------------------------------------------------------- /src/components/profile/view/ViewTags.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 26 | -------------------------------------------------------------------------------- /src/components/search/SearchResultList.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 24 | 25 | 39 | -------------------------------------------------------------------------------- /src/components/search/SearchToggle.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 72 | -------------------------------------------------------------------------------- /src/components/ui/Banner.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 23 | 24 | 51 | -------------------------------------------------------------------------------- /src/components/ui/Card.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 81 | -------------------------------------------------------------------------------- /src/components/ui/CardRow.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 41 | -------------------------------------------------------------------------------- /src/components/ui/Checkbox.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 40 | 41 | 63 | -------------------------------------------------------------------------------- /src/components/ui/DinoType.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 15 | -------------------------------------------------------------------------------- /src/components/ui/EditButton.spec.js: -------------------------------------------------------------------------------- 1 | import { shallowMount, createLocalVue } from '@vue/test-utils'; 2 | import VueRouter from 'vue-router'; 3 | import Vue from 'vue'; 4 | import EditButton from './EditButton.vue'; 5 | 6 | const localVue = createLocalVue(); 7 | localVue.use(VueRouter); 8 | const router = new VueRouter(); 9 | let mounted; 10 | describe('EditButton', () => { 11 | it('should exist', () => { 12 | mounted = shallowMount(EditButton, { 13 | stubs: ['router-link', 'router-view'], 14 | propsData: { 15 | sendTo: { 16 | name: '', 17 | query: { 18 | section: '', 19 | }, 20 | }, 21 | section: '', 22 | sectionId: '', 23 | }, 24 | 25 | localVue, 26 | router, 27 | }); 28 | expect(mounted.vm).toBeInstanceOf(Vue); 29 | }); 30 | describe('noEditCardOpen', () => { 31 | it('should return true if route name is not "Edit Profile"', () => { 32 | mounted = shallowMount(EditButton, { 33 | stubs: ['router-link', 'router-view'], 34 | propsData: { 35 | sendTo: { 36 | name: 'Edit Access Group', 37 | query: { 38 | section: 'edit', 39 | }, 40 | }, 41 | section: '', 42 | sectionId: '', 43 | }, 44 | localVue, 45 | router, 46 | }); 47 | expect(mounted.vm.noEditCardOpen()).toEqual(true); 48 | }); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /src/components/ui/EditButton.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 38 | 39 | 49 | -------------------------------------------------------------------------------- /src/components/ui/Error.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 17 | 18 | 47 | -------------------------------------------------------------------------------- /src/components/ui/ExternalButtonLink.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 23 | 24 | 41 | -------------------------------------------------------------------------------- /src/components/ui/ExternalLink.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 26 | 27 | 35 | -------------------------------------------------------------------------------- /src/components/ui/GlobalNotifications.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 21 | 22 | 52 | -------------------------------------------------------------------------------- /src/components/ui/Icon.vue: -------------------------------------------------------------------------------- 1 | 44 | 45 | 86 | 87 | 92 | -------------------------------------------------------------------------------- /src/components/ui/IconBlock.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 36 | 37 | 82 | -------------------------------------------------------------------------------- /src/components/ui/IconBlockList.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 27 | -------------------------------------------------------------------------------- /src/components/ui/LoadingSpinner.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 28 | 29 | 47 | -------------------------------------------------------------------------------- /src/components/ui/Meta.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 30 | 31 | 49 | -------------------------------------------------------------------------------- /src/components/ui/MetaList.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 21 | -------------------------------------------------------------------------------- /src/components/ui/NumberScrollerInput.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 34 | 35 | 51 | -------------------------------------------------------------------------------- /src/components/ui/Option.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 71 | -------------------------------------------------------------------------------- /src/components/ui/PanelSection.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 37 | 38 | 103 | -------------------------------------------------------------------------------- /src/components/ui/PersonList.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 19 | -------------------------------------------------------------------------------- /src/components/ui/Tag.spec.js: -------------------------------------------------------------------------------- 1 | import Tag from './Tag.vue'; 2 | import { createLocalVue } from '@vue/test-utils'; 3 | import Vuex from 'vuex'; 4 | import getRenderedText from '../../../tests/utils/getRenderedText.js'; 5 | 6 | const localVue = createLocalVue(); 7 | 8 | localVue.use(Vuex); 9 | 10 | describe('Tag.vue', () => { 11 | it('renders props.tag when passed', () => { 12 | const msg = 'test'; 13 | const text = getRenderedText(Tag, { tag: msg }).trim(); 14 | expect(text).toBe(msg); 15 | }); 16 | it('renders button with "remove" text when props.removable is passed', () => { 17 | const vis = getRenderedText( 18 | Tag, 19 | { tag: 'test', removable: true }, 20 | '.visually-hidden' 21 | ); 22 | expect(vis).toBe('Remove \u2068test\u2069'); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /src/components/ui/Tag.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 51 | 52 | 78 | -------------------------------------------------------------------------------- /src/components/ui/TextArea.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 30 | 31 | 67 | -------------------------------------------------------------------------------- /src/components/ui/Toast.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 46 | 47 | 77 | -------------------------------------------------------------------------------- /src/components/ui/Tooltip.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 43 | 44 | 70 | -------------------------------------------------------------------------------- /src/components/ui/Vouch.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 22 | 23 | 37 | -------------------------------------------------------------------------------- /src/locales/en-GB/strings.ftl: -------------------------------------------------------------------------------- 1 | profile_first-name = First Name 2 | .tooltip = Pro tip: If first and last names don’t work for you, we have you covered, please use alternative name instead. 3 | profile_alt-name = Alternative Name 4 | .tooltip-open = Open alternative name info 5 | .tooltip-close = Close alternative name info 6 | .tooltip = This field allows you to enter an additional or alternative name to your profile. 7 | .privacy = Alternative name privacy levels 8 | profile_cost-center = Cost Centre 9 | .tooltip-open = Open cost centre info 10 | .tooltip-close = Close cost centre info 11 | .tooltip = This is your cost centre as provided by our HR Team (Workday). {-staff-not-editable} 12 | .privacy = Cost centre privacy levels 13 | -------------------------------------------------------------------------------- /src/locales/hi/strings.ftl: -------------------------------------------------------------------------------- 1 | home_welcome = मोज़िला की संपर्क सूची में आपका स्वागत है 2 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import VueApollo from 'vue-apollo'; 3 | import { mapGetters } from 'vuex'; 4 | 5 | import store from '@/store'; 6 | import { ACCESS_GROUP_TYPES } from '@/view_models/AccessGroupViewModel'; 7 | import FluentComponent from '@/components/Fluent.vue'; 8 | import App from './App.vue'; 9 | import DPRouter from './router'; 10 | import Fluent from './assets/js/fluent'; 11 | import { apolloProvider } from './server'; 12 | import { Api } from '@/assets/js/access-groups-api.js'; 13 | 14 | // polyfill/fallback adapted from MDN (https://developer.mozilla.org/en-US/docs/Web/API/Background_Tasks_API#Falling_back_to_setTimeout) 15 | window.requestIdleCallback = 16 | window.requestIdleCallback || 17 | ((handler) => { 18 | const startTime = Date.now(); 19 | 20 | return setTimeout(() => { 21 | handler({ 22 | didTimeout: false, 23 | timeRemaining() { 24 | return Math.max(0, 50.0 - (Date.now() - startTime)); 25 | }, 26 | }); 27 | }, 1); 28 | }); 29 | 30 | Vue.use(VueApollo); 31 | 32 | store.dispatch('setLoading'); 33 | // eslint-disable-next-line 34 | Promise.all([ 35 | store.dispatch('features/fetch'), 36 | store.dispatch('fetchUser'), 37 | Fluent.init(), 38 | ]).then(([features, , fluent]) => { 39 | const router = new DPRouter(features, fluent, store); 40 | store.dispatch('completeLoading'); 41 | 42 | Vue.component('Fluent', FluentComponent); 43 | Vue.mixin({ 44 | data() { 45 | return { 46 | accessGroupApi: new Api(), 47 | administratorEmail: 'fiji@mozilla.com', 48 | }; 49 | }, 50 | computed: { 51 | ...mapGetters({ 52 | getFeature: 'features/get', 53 | }), 54 | scope() { 55 | return this.$store.state.scope; 56 | }, 57 | }, 58 | methods: { 59 | fluent(...args) { 60 | return fluent.get(...args); 61 | }, 62 | // Currently only works with one variable replace 63 | tinyNotification(fluentSelector, args = '') { 64 | this.$root.$emit('toast', { 65 | content: this.fluent('tiny-notification', fluentSelector).replace( 66 | '[]', 67 | args, 68 | ), 69 | }); 70 | }, 71 | tinyNotificationError(message) { 72 | this.$root.$emit('toast', { 73 | error: true, 74 | content: message, 75 | }); 76 | }, 77 | }, 78 | }); 79 | new Vue({ 80 | router: router.vueRouter, 81 | apolloProvider, 82 | render: (h) => h(App), 83 | store, 84 | }).$mount('#app'); 85 | }); 86 | -------------------------------------------------------------------------------- /src/pages/PageAccessGroups.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/pages/PagePermissionRequired.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/pages/PageUnknown.vue: -------------------------------------------------------------------------------- 1 | 40 | 41 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /src/server.js: -------------------------------------------------------------------------------- 1 | import { InMemoryCache, defaultDataIdFromObject } from 'apollo-cache-inmemory'; 2 | import ApolloClient from 'apollo-boost'; 3 | import VueApollo from 'vue-apollo'; 4 | 5 | function errorHandler(error) { 6 | const failoverOn = [302]; 7 | const { networkError } = error; 8 | if ( 9 | networkError && 10 | ((networkError instanceof TypeError && 11 | networkError.message.startsWith('NetworkError')) || 12 | failoverOn.includes(networkError.statusCode)) 13 | ) { 14 | window.location.reload(); 15 | } 16 | } 17 | 18 | const cache = new InMemoryCache({ 19 | dataIdFromObject: object => { 20 | // eslint-disable-next-line no-underscore-dangle 21 | switch (object.__typename) { 22 | case 'Profile': 23 | return object.uuid.value; 24 | default: 25 | // TODO: do we neet to pass here? 26 | return defaultDataIdFromObject(object); 27 | } 28 | }, 29 | }); 30 | 31 | export const client = new ApolloClient({ 32 | uri: '/api/v4/graphql', 33 | cache, 34 | onError: errorHandler, 35 | }); 36 | 37 | export const mutationClient = new ApolloClient({ 38 | uri: '/api/v4/graphql', 39 | cache, 40 | onError: errorHandler, 41 | }); 42 | 43 | export const apolloProvider = new VueApollo({ 44 | clients: { 45 | default: client, 46 | mutationClient, 47 | }, 48 | defaultClient: client, 49 | errorHandler, 50 | }); 51 | -------------------------------------------------------------------------------- /src/store/features.store.js: -------------------------------------------------------------------------------- 1 | import Features from '@/assets/js/features'; 2 | const features = new Features(); 3 | 4 | export default { 5 | namespaced: true, 6 | state: {}, 7 | actions: { 8 | async fetch({ commit }) { 9 | try { 10 | const featureConfig = await features.get(); 11 | commit('set', featureConfig); 12 | return featureConfig; 13 | } catch (e) { 14 | console.error(e); 15 | throw new Error(e.message); 16 | } 17 | }, 18 | }, 19 | mutations: { 20 | set(state, features) { 21 | for (let k in features) { 22 | if (!features.hasOwnProperty(k)) { 23 | continue; 24 | } 25 | state[k] = features[k]; 26 | } 27 | }, 28 | }, 29 | getters: { 30 | get: state => feature => { 31 | if (feature in state) { 32 | return state[feature]; 33 | } 34 | return false; 35 | }, 36 | }, 37 | }; 38 | -------------------------------------------------------------------------------- /src/store/invitation-email.store.js: -------------------------------------------------------------------------------- 1 | import AccessGroups from '@/assets/js/access-groups'; 2 | 3 | const accessGroupsService = new AccessGroups(); 4 | 5 | export const invitationEmailState = { 6 | invitationEmail: '', 7 | }; 8 | export const invitationEmailActions = { 9 | async fetchInvitationEmail({ state, commit }) { 10 | try { 11 | const { body } = await accessGroupsService.getInvitationEmail( 12 | state.group.name, 13 | ); 14 | commit('setInvitationEmail', body); 15 | return body; 16 | } catch (e) { 17 | throw new Error(e.message); 18 | } 19 | }, 20 | async updateInvitationEmail({ dispatch, state }, text) { 21 | try { 22 | const result = await accessGroupsService.updateInvitationEmail( 23 | state.group.name, 24 | text, 25 | ); 26 | await dispatch('fetchInvitationEmail'); 27 | return result; 28 | } catch (e) { 29 | throw new Error(e.message); 30 | } 31 | }, 32 | async deleteInvitationEmail({ dispatch, state }) { 33 | try { 34 | const result = await accessGroupsService.updateInvitationEmail( 35 | state.group.name, 36 | '', 37 | ); 38 | await dispatch('fetchInvitationEmail'); 39 | return result; 40 | } catch (e) { 41 | throw new Error(e.message); 42 | } 43 | }, 44 | }; 45 | export const invitationEmailMutations = { 46 | setInvitationEmail(state, content) { 47 | try { 48 | state.invitationEmail = !content ? '' : content; 49 | } catch (e) { 50 | state.error = e.message; 51 | throw new Error(e.message); 52 | } 53 | }, 54 | }; 55 | export const invitationEmailGetters = { 56 | getInvitationEmail: (state) => { 57 | return state.invitationEmail; 58 | }, 59 | }; 60 | -------------------------------------------------------------------------------- /src/store/invitationgroupdata.json: -------------------------------------------------------------------------------- 1 | { 2 | "invitations": [ 3 | { 4 | "group_name": "dinopark-test", 5 | "user_uuid": "34a33a8e-d035-4051-acac-497a79235e73", 6 | "invitation_expiration": {}, 7 | "group_expiration": {}, 8 | "terms": true, 9 | "added_by": { 10 | "user_uuid": "34a33a8e-d035-4051-acac-497a79235e73", 11 | "name": "Hans Knall", 12 | "username": "hans", 13 | "email": "hans@knall.org" 14 | } 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /src/store/scope.store.js: -------------------------------------------------------------------------------- 1 | export default { 2 | namespaced: true, 3 | state: { 4 | isStaff: false, 5 | isNdaed: false, 6 | isLdap: false, 7 | }, 8 | actions: { 9 | set({ commit }, user) { 10 | const { 11 | staffInformation: { staff: { value: isStaff = false } = {} } = {}, 12 | accessInformation: { 13 | mozilliansorg: { values: mozilliansorgGroups = {} } = {}, 14 | ldap: { values: ldapGroups = {} } = {}, 15 | } = {}, 16 | } = user || {}; 17 | /* 18 | The other places these groups are handled. 19 | https://github.com/mozilla-iam/dino-park-packs/pull/20 20 | https://github.com/mozilla-iam/dino-park-gate/pull/3 21 | */ 22 | const options = { 23 | isStaff, 24 | isNdaed: 25 | 'nda' in (mozilliansorgGroups || {}) || 26 | 'ghe_group_curators' in (mozilliansorgGroups || {}) || 27 | 'contingentworkernda' in (mozilliansorgGroups || {}), 28 | isLdap: Boolean(ldapGroups), 29 | }; 30 | commit('set', options); 31 | }, 32 | }, 33 | mutations: { 34 | set(state, options) { 35 | state.isStaff = options.isStaff; 36 | state.isNdaed = options.isNdaed; 37 | state.isLdap = options.isLdap; 38 | }, 39 | }, 40 | getters: { 41 | get(state) { 42 | if (state.isStaff) { 43 | return 'Staff'; 44 | } 45 | if (state.isNdaed) { 46 | return 'Ndaed'; 47 | } 48 | if (state.isLdap) { 49 | return 'Authenticated'; 50 | } 51 | return 'Public'; 52 | }, 53 | isStaff: ({ isStaff }) => isStaff, 54 | isNdaed: ({ isNdaed }) => isNdaed, 55 | isLdap: ({ isLdap }) => isLdap, 56 | }, 57 | }; 58 | -------------------------------------------------------------------------------- /src/store/user.store.js: -------------------------------------------------------------------------------- 1 | export default { 2 | namespaced: true, 3 | state: { 4 | personListViewPreference: 'list', 5 | }, 6 | actions: { 7 | setPersonViewPreference({ commit }, preference) { 8 | commit('setPersonViewPreference', preference); 9 | }, 10 | }, 11 | mutations: { 12 | setPersonViewPreference(state, preference) { 13 | state.personListViewPreference = preference; 14 | }, 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /src/view_models/AccessGroupViewModel.spec.js: -------------------------------------------------------------------------------- 1 | import { 2 | AccessGroupDetailsViewModel, 3 | GroupViewModel, 4 | DisplayMemberViewModel, 5 | AbbDisplayMemberViewModel, 6 | } from './AccessGroupViewModel'; 7 | import accessGroupData from '../accessgroupdata.json'; 8 | 9 | describe('AccessGroupDetailsViewModel', () => { 10 | it('should be created', () => { 11 | const model = new AccessGroupDetailsViewModel(accessGroupData); 12 | expect(model).toBeInstanceOf(AccessGroupDetailsViewModel); 13 | }); 14 | }); 15 | 16 | describe('GroupViewModel', () => { 17 | it('should be created', () => { 18 | const model = new GroupViewModel(accessGroupData.group); 19 | expect(model).toBeInstanceOf(GroupViewModel); 20 | }); 21 | }); 22 | 23 | describe('DisplayMemberViewModel', () => { 24 | it('should be created', () => { 25 | const model = new DisplayMemberViewModel( 26 | accessGroupData.members.members[0] 27 | ); 28 | expect(model).toBeInstanceOf(DisplayMemberViewModel); 29 | }); 30 | }); 31 | 32 | describe('AbbDisplayMemberViewModel', () => { 33 | it('should be created', () => { 34 | const model = new AbbDisplayMemberViewModel( 35 | accessGroupData.members.members[0].added_by 36 | ); 37 | expect(model).toBeInstanceOf(AbbDisplayMemberViewModel); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /src/view_models/ProfileViewModel.js: -------------------------------------------------------------------------------- 1 | export class StaffInformationViewModel { 2 | constructor(data) { 3 | this.staff = false; 4 | this.team = ''; 5 | this.title = ''; 6 | this.officeLocation = ''; 7 | this.processData(data); 8 | } 9 | 10 | processData(data) { 11 | if (!data) { 12 | throw new Error('Invalid data format'); 13 | } 14 | try { 15 | this.staff = data.staff.value; 16 | this.team = data.team.value; 17 | this.title = data.title.value; 18 | this.officeLocation = data.officeLocation.value; 19 | } catch (e) { 20 | this.error = e.message; 21 | console.error('Staff information data error: ', e.message); 22 | } 23 | } 24 | } 25 | 26 | export class ProfileViewModel { 27 | constructor(data) { 28 | this.uuid = ''; 29 | this.alternativeName = ''; 30 | this.description = ''; 31 | this.firstName = ''; 32 | this.funTitle = ''; 33 | this.lastName = ''; 34 | this.location = ''; 35 | this.picture = ''; 36 | this.primaryEmail = ''; 37 | this.pronouns = []; 38 | this.timezone = []; 39 | this.primaryUsername = ''; 40 | this.phoneNumbers = []; 41 | this.staffInformation = null; 42 | this.uris = []; 43 | this.processData(data); 44 | } 45 | 46 | processData(data) { 47 | if (!data) { 48 | throw new Error('Invalid data format'); 49 | } 50 | try { 51 | this.uuid = data.uuid.value; 52 | this.alternativeName = data.alternativeName.value; 53 | this.description = data.description.value; 54 | this.firstName = data.firstName.value; 55 | this.funTitle = data.funTitle.value; 56 | this.lastName = data.lastName.value; 57 | this.location = data.location.value; 58 | this.picture = data.picture.value; 59 | this.primaryEmail = data.primaryEmail.value; 60 | this.pronouns = data.pronouns.value; 61 | this.timezone = data.timezone.value; 62 | this.primaryUsername = data.primaryUsername.value; 63 | this.phoneNumbers = data.phoneNumbers.value; 64 | this.staffInformation = new StaffInformationViewModel( 65 | data.staffInformation 66 | ); 67 | this.uris = data.uris.value; 68 | } catch (e) { 69 | this.error = e.message; 70 | console.error('Profile data error: ', e.message); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/view_models/ProfileViewModel.spec.js: -------------------------------------------------------------------------------- 1 | import { ProfileViewModel } from './ProfileViewModel'; 2 | const mockProfile = { 3 | uuid: '', 4 | alternativeName: '', 5 | description: '', 6 | firstName: '', 7 | funTitle: '', 8 | lastName: '', 9 | location: '', 10 | picture: '', 11 | primaryEmail: '', 12 | pronouns: [], 13 | timezone: [], 14 | primaryUsername: '', 15 | phoneNumbers: [], 16 | staffInformation: { 17 | staff: '', 18 | team: '', 19 | title: '', 20 | officeLocation: '', 21 | }, 22 | uris: [], 23 | }; 24 | 25 | describe('ProfileViewModel', () => { 26 | it('should be created', () => { 27 | const model = new ProfileViewModel(mockProfile); 28 | expect(model).toBeInstanceOf(ProfileViewModel); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /terraform/codebuild-preview/data.tf: -------------------------------------------------------------------------------- 1 | data "aws_caller_identity" "current" { 2 | } 3 | 4 | -------------------------------------------------------------------------------- /terraform/codebuild-preview/outputs.tf: -------------------------------------------------------------------------------- 1 | output "iam_role_arn" { 2 | value = aws_iam_role.codebuild.arn 3 | description = "ARN of the codebuild user. Needed by cluster admins to authorize deploying to Kubernetes" 4 | } 5 | 6 | output "ecr_url" { 7 | value = aws_ecr_repository.registry.repository_url 8 | description = "URL of the new container registry which will host your builds" 9 | } 10 | 11 | -------------------------------------------------------------------------------- /terraform/codebuild-preview/provider.tf: -------------------------------------------------------------------------------- 1 | #--- 2 | # Provider Configuration 3 | #--- 4 | 5 | provider "aws" { 6 | region = "us-west-2" 7 | } 8 | 9 | terraform { 10 | required_version = "~> 0.12" 11 | 12 | backend "s3" { 13 | bucket = "eks-terraform-shared-state" 14 | key = "global/codebuild/dino-park-front-end-preview/terraform.tfstate" 15 | region = "us-west-2" 16 | } 17 | } 18 | 19 | -------------------------------------------------------------------------------- /terraform/codebuild-preview/variables.tf: -------------------------------------------------------------------------------- 1 | variable "project_name" { 2 | description = "DinoPark Front-End - Preview - DinoParks beautiful user facing side." 3 | default = "dino-park-front-end-preview" 4 | } 5 | 6 | variable "github_repo" { 7 | default = "https://github.com/mozilla-iam/dino-park-front-end" 8 | } 9 | 10 | variable "buildspec_file" { 11 | description = "Path and name of your builspec file" 12 | default = "buildspec-preview.yml" 13 | } 14 | 15 | # Find all the supported images by AWS here: 16 | # https://docs.aws.amazon.com/codebuild/latest/userguide/build-env-ref-available.html 17 | variable "build_image" { 18 | default = "aws/codebuild/standard:4.0" 19 | } 20 | 21 | -------------------------------------------------------------------------------- /terraform/codebuild-preview/versions.tf: -------------------------------------------------------------------------------- 1 | 2 | terraform { 3 | required_version = ">= 0.12" 4 | } 5 | -------------------------------------------------------------------------------- /terraform/codebuild/data.tf: -------------------------------------------------------------------------------- 1 | data "aws_caller_identity" "current" { 2 | } 3 | 4 | -------------------------------------------------------------------------------- /terraform/codebuild/outputs.tf: -------------------------------------------------------------------------------- 1 | output "iam_role_arn" { 2 | value = aws_iam_role.codebuild.arn 3 | description = "ARN of the codebuild user. Needed by cluster admins to authorize deploying to Kubernetes" 4 | } 5 | 6 | output "ecr_url" { 7 | value = aws_ecr_repository.registry.repository_url 8 | description = "URL of the new container registry which will host your builds" 9 | } 10 | 11 | -------------------------------------------------------------------------------- /terraform/codebuild/provider.tf: -------------------------------------------------------------------------------- 1 | #--- 2 | # Provider Configuration 3 | #--- 4 | 5 | provider "aws" { 6 | region = "us-west-2" 7 | } 8 | 9 | terraform { 10 | required_version = "~> 0.12" 11 | 12 | backend "s3" { 13 | bucket = "eks-terraform-shared-state" 14 | key = "global/codebuild/dino-park-front-end/terraform.tfstate" 15 | region = "us-west-2" 16 | } 17 | } 18 | 19 | -------------------------------------------------------------------------------- /terraform/codebuild/variables.tf: -------------------------------------------------------------------------------- 1 | variable "project_name" { 2 | description = "DinoPark Front-End - DinoParks beautiful user facing side." 3 | default = "dino-park-front-end" 4 | } 5 | 6 | variable "github_repo" { 7 | default = "https://github.com/mozilla-iam/dino-park-front-end" 8 | } 9 | 10 | variable "buildspec_file" { 11 | description = "Path and name of your builspec file" 12 | default = "buildspec.yml" 13 | } 14 | 15 | # Find all the supported images by AWS here: 16 | # https://docs.aws.amazon.com/codebuild/latest/userguide/build-env-ref-available.html 17 | variable "build_image" { 18 | default = "aws/codebuild/standard:4.0" 19 | } 20 | 21 | -------------------------------------------------------------------------------- /terraform/codebuild/versions.tf: -------------------------------------------------------------------------------- 1 | 2 | terraform { 3 | required_version = ">= 0.12" 4 | } 5 | -------------------------------------------------------------------------------- /tests/mocks/abcfluentMock.js: -------------------------------------------------------------------------------- 1 | module.exports = ` 2 | localised = localised 3 | .localised = localised 4 | localised_with_placeable = this is {localised.localised}, but this isn't: {simple} 5 | `; 6 | -------------------------------------------------------------------------------- /tests/mocks/accessgroupcurators.json: -------------------------------------------------------------------------------- 1 | { 2 | "curators": [ 3 | { 4 | "user_uuid": "34a33a8e-d035-4051-acac-497a79235e73", 5 | "picture": "https://picture.api.dev.sso.allizom.org/avatar/get/id/MzI0ZjAwMzA5Yzc1ZGYxYmIzYjdjMGE1NWZmMDc3OGY3NGJjYjAzMjc0NDVhZmI2YWViODBmMzIzOGNhYzM3NyNzdGFmZiMxNTcyODYyNzM3.png", 6 | "name": "Hans Knall", 7 | "username": "hans", 8 | "email": "hans@knall.org", 9 | "is_staff": true, 10 | "since": "2014-11-28T12:45:59Z", 11 | "expiration": "2014-11-28T12:45:59Z", 12 | "role": "Curator", 13 | "added_by": { 14 | "user_uuid": "34a33a8e-d035-4051-acac-497a79235e73", 15 | "name": "Hans Knall", 16 | "username": "hans", 17 | "email": "hans@knall.org" 18 | } 19 | } 20 | ], 21 | "next": 20 22 | } 23 | -------------------------------------------------------------------------------- /tests/mocks/accessgroupmemberinvitations.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "user_uuid": "34a33a8e-d035-4051-acac-497a79235e74", 4 | "picture": "https://picture.api.dev.sso.allizom.org/avatar/get/id/MzI0ZjAwMzA5Yzc1ZGYxYmIzYjdjMGE1NWZmMDc3OGY3NGJjYjAzMjc0NDVhZmI2YWViODBmMzIzOGNhYzM3NyNzdGFmZiMxNTcyODYyNzM3.png", 5 | "name": "Bruce Wayne", 6 | "username": "batman", 7 | "email": "batman@gotham.com", 8 | "is_staff": true, 9 | "role": "Member", 10 | "since": "2014-11-28T12:45:59Z", 11 | "expiration": "2014-11-28T12:45:59Z", 12 | "added_by": { 13 | "user_uuid": "34a33a8e-d035-4051-acac-497a79235e73", 14 | "name": "Andrew Sunada", 15 | "username": "batman", 16 | "email": "batman@mozilla.org" 17 | } 18 | }, 19 | { 20 | "user_uuid": "34a33a8e-d035-4051-acac-497a79235e75", 21 | "picture": "https://picture.api.dev.sso.allizom.org/avatar/get/id/MzI0ZjAwMzA5Yzc1ZGYxYmIzYjdjMGE1NWZmMDc3OGY3NGJjYjAzMjc0NDVhZmI2YWViODBmMzIzOGNhYzM3NyNzdGFmZiMxNTcyODYyNzM3.png", 22 | "name": "Dick Grayson", 23 | "username": "nightwing", 24 | "email": "nightwing@gotham.com", 25 | "is_staff": true, 26 | "role": "Member", 27 | "since": "2014-11-28T12:45:59Z", 28 | "expiration": "2014-11-28T12:45:59Z", 29 | "added_by": { 30 | "user_uuid": "34a33a8e-d035-4051-acac-497a79235e73", 31 | "name": "Batman", 32 | "username": "batman", 33 | "email": "batman@mozilla.org" 34 | } 35 | }, 36 | { 37 | "user_uuid": "34a33a8e-d035-4051-acac-497a79235e76", 38 | "picture": "https://picture.api.dev.sso.allizom.org/avatar/get/id/MzI0ZjAwMzA5Yzc1ZGYxYmIzYjdjMGE1NWZmMDc3OGY3NGJjYjAzMjc0NDVhZmI2YWViODBmMzIzOGNhYzM3NyNzdGFmZiMxNTcyODYyNzM3.png", 39 | "name": "Jason Todd", 40 | "username": "redhood", 41 | "email": "redhood@gotham.com", 42 | "is_staff": true, 43 | "role": "Member", 44 | "since": "2014-11-28T12:45:59Z", 45 | "expiration": "2014-11-28T12:45:59Z", 46 | "added_by": { 47 | "user_uuid": "34a33a8e-d035-4051-acac-497a79235e73", 48 | "name": "Batman", 49 | "username": "batman", 50 | "email": "batman@mozilla.org" 51 | } 52 | } 53 | ] 54 | -------------------------------------------------------------------------------- /tests/mocks/accessgroupmembers.json: -------------------------------------------------------------------------------- 1 | { 2 | "members": [ 3 | { 4 | "user_uuid": "34a33a8e-d035-4051-acac-497a79235e74", 5 | "picture": "https://picture.api.dev.sso.allizom.org/avatar/get/id/MzI0ZjAwMzA5Yzc1ZGYxYmIzYjdjMGE1NWZmMDc3OGY3NGJjYjAzMjc0NDVhZmI2YWViODBmMzIzOGNhYzM3NyNzdGFmZiMxNTcyODYyNzM3.png", 6 | "name": "Bruce Wayne", 7 | "username": "batman", 8 | "email": "batman@gotham.com", 9 | "is_staff": true, 10 | "role": "Member", 11 | "since": "2014-11-28T12:45:59Z", 12 | "expiration": "2014-11-28T12:45:59Z", 13 | "added_by": { 14 | "user_uuid": "34a33a8e-d035-4051-acac-497a79235e73", 15 | "name": "Andrew Sunada", 16 | "username": "batman", 17 | "email": "batman@mozilla.org" 18 | } 19 | }, 20 | { 21 | "user_uuid": "34a33a8e-d035-4051-acac-497a79235e75", 22 | "picture": "https://picture.api.dev.sso.allizom.org/avatar/get/id/MzI0ZjAwMzA5Yzc1ZGYxYmIzYjdjMGE1NWZmMDc3OGY3NGJjYjAzMjc0NDVhZmI2YWViODBmMzIzOGNhYzM3NyNzdGFmZiMxNTcyODYyNzM3.png", 23 | "name": "Dick Grayson", 24 | "username": "nightwing", 25 | "email": "nightwing@gotham.com", 26 | "is_staff": true, 27 | "role": "Member", 28 | "since": "2014-11-28T12:45:59Z", 29 | "expiration": "2014-11-28T12:45:59Z", 30 | "added_by": { 31 | "user_uuid": "34a33a8e-d035-4051-acac-497a79235e73", 32 | "name": "Batman", 33 | "username": "batman", 34 | "email": "batman@mozilla.org" 35 | } 36 | }, 37 | { 38 | "user_uuid": "34a33a8e-d035-4051-acac-497a79235e76", 39 | "picture": "https://picture.api.dev.sso.allizom.org/avatar/get/id/MzI0ZjAwMzA5Yzc1ZGYxYmIzYjdjMGE1NWZmMDc3OGY3NGJjYjAzMjc0NDVhZmI2YWViODBmMzIzOGNhYzM3NyNzdGFmZiMxNTcyODYyNzM3.png", 40 | "name": "Jason Todd", 41 | "username": "redhood", 42 | "email": "redhood@gotham.com", 43 | "is_staff": true, 44 | "role": "Member", 45 | "since": "2014-11-28T12:45:59Z", 46 | "expiration": "2014-11-28T12:45:59Z", 47 | "added_by": { 48 | "user_uuid": "34a33a8e-d035-4051-acac-497a79235e73", 49 | "name": "Batman", 50 | "username": "batman", 51 | "email": "batman@mozilla.org" 52 | } 53 | } 54 | ], 55 | "next": 20 56 | } 57 | -------------------------------------------------------------------------------- /tests/mocks/accessgrouptermsofservice.json: -------------------------------------------------------------------------------- 1 | { 2 | "content": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." 3 | } 4 | -------------------------------------------------------------------------------- /tests/mocks/enUSfluentMock.js: -------------------------------------------------------------------------------- 1 | module.exports = ` 2 | simple = simple string 3 | .attribute = simple attribute 4 | variables = variable x: { $x }, y: { $y } 5 | .attribute = variable a: { $a }, b: { $b } 6 | html = 7 | html: oneone 8 | two 9 | 10 | whitelisted = lorem ipsum 11 | .attributes = lorem ipsum 12 | .stealing = lorem ipsum 13 | localised = this string is localised 14 | .localised = so is this attribute 15 | .not-localised = but this isn't 16 | `; 17 | -------------------------------------------------------------------------------- /tests/mocks/invitationgroupdata.json: -------------------------------------------------------------------------------- 1 | { 2 | "invitations": [ 3 | { 4 | "group_name": "dinopark-test", 5 | "user_uuid": "34a33a8e-d035-4051-acac-497a79235e73", 6 | "invitation_expiration": {}, 7 | "group_expiration": {}, 8 | "terms": true, 9 | "added_by": { 10 | "user_uuid": "34a33a8e-d035-4051-acac-497a79235e73", 11 | "name": "Hans Knall", 12 | "username": "hans", 13 | "email": "hans@knall.org" 14 | } 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /tests/mocks/mockStore.js: -------------------------------------------------------------------------------- 1 | export default { 2 | mocks: { 3 | $store: { 4 | state: { 5 | accessGroup: { 6 | members: [], 7 | curators: [], 8 | group: { 9 | id: '', 10 | name: '', 11 | type: '', 12 | description: '', 13 | terms: false, 14 | }, 15 | member_count: 0, 16 | invitation_count: 0, 17 | renewal_count: 0, 18 | }, 19 | user: { 20 | picture: { 21 | value: '', 22 | }, 23 | }, 24 | }, 25 | }, 26 | }, 27 | }; 28 | -------------------------------------------------------------------------------- /tests/unit/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | jest: true, 4 | }, 5 | rules: { 6 | 'import/no-extraneous-dependencies': 'off', 7 | }, 8 | }; 9 | -------------------------------------------------------------------------------- /tests/utils/getMountedComponentWithStore.js: -------------------------------------------------------------------------------- 1 | import Vuex from 'vuex'; 2 | import { shallowMount, createLocalVue } from '@vue/test-utils'; 3 | import MockStore from '../mocks/mockStore'; 4 | const localVue = createLocalVue(); 5 | 6 | localVue.use(Vuex); 7 | module.exports = function getMountedComponentWithStore(component, extra = {}) { 8 | return shallowMount(component, { 9 | ...MockStore, 10 | ...extra, 11 | localVue, 12 | }); 13 | }; 14 | -------------------------------------------------------------------------------- /tests/utils/getRenderedText.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Fluent from '@/assets/js/fluent'; 3 | import USStrings from 'non-mock_en-US_strings.ftl'; 4 | import { createLocalVue } from '@vue/test-utils'; 5 | import Vuex from 'vuex'; 6 | import getMountedComponentWithStore from './getMountedComponentWithStore'; 7 | 8 | const fluent = new Fluent('en_US', [USStrings]); 9 | Vue.mixin({ 10 | methods: { 11 | fluent(...args) { 12 | return fluent.get(...args); 13 | }, 14 | }, 15 | }); 16 | const localVue = createLocalVue(); 17 | 18 | localVue.use(Vuex); 19 | module.exports = function getRenderedText(Component, propsData, selector) { 20 | // const Constructor = Vue.extend(Component); 21 | // const vm = new Constructor({ propsData: propsData }).$mount(); 22 | const wrapper = getMountedComponentWithStore(Component, { 23 | propsData: propsData, 24 | }); 25 | if (typeof selector === 'string' && selector.length > 0) { 26 | const selected = wrapper.vm.$el.querySelector(selector); 27 | if (!selected) { 28 | return ''; 29 | } 30 | return selected.textContent; 31 | } else { 32 | return wrapper.vm.$el.textContent; 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const yaml = require('yaml'); 3 | 4 | const BASE_URL = process.env.DP_BASE_URL || '/'; 5 | const HTTPS_KEY = process.env.DP_HTTPS_KEY || false; 6 | const HTTPS_CERT = process.env.DP_HTTPS_CERT || false; 7 | const HTTPS = HTTPS_CERT && 8 | HTTPS_KEY && { 9 | key: fs.readFileSync(HTTPS_KEY), 10 | cert: fs.readFileSync(HTTPS_CERT), 11 | }; 12 | module.exports = { 13 | lintOnSave: false, 14 | productionSourceMap: false, 15 | filenameHashing: true, 16 | publicPath: BASE_URL, 17 | chainWebpack: (config) => { 18 | const svgRule = config.module.rule('svg'); 19 | svgRule 20 | .use('image-webpack-loader') 21 | .loader('image-webpack-loader') 22 | .tap(() => { 23 | return { 24 | svgo: { 25 | plugins: [], 26 | }, 27 | }; 28 | }) 29 | .end(); 30 | }, 31 | configureWebpack: { 32 | resolve: { 33 | // .mjs needed for https://github.com/graphql/graphql-js/issues/1272 34 | extensions: ['*', '.mjs', '.js', '.vue', '.json', '.gql', '.graphql'], 35 | }, 36 | module: { 37 | rules: [ 38 | // fixes https://github.com/graphql/graphql-js/issues/1272 39 | { 40 | test: /\.mjs$/, 41 | include: /node_modules/, 42 | type: 'javascript/auto', 43 | }, 44 | { 45 | test: /\.ftl$/, 46 | use: 'raw-loader', 47 | }, 48 | ], 49 | }, 50 | }, 51 | devServer: { 52 | https: HTTPS, 53 | host: 'localhost', 54 | port: 8080, 55 | disableHostCheck: true, 56 | headers: { 57 | 'Access-Control-Allow-Origin': '*', 58 | }, 59 | before(app) { 60 | app.get('/config/features.json', (_, res) => { 61 | res.append('Access-Control-Allow-Origin', '*'); 62 | res.json(yaml.parse(fs.readFileSync('./features-local.yaml', 'utf8'))); 63 | }); 64 | }, 65 | }, 66 | }; 67 | --------------------------------------------------------------------------------