diff --git a/.graphqlconfig b/.graphqlconfig index c40a8acaa..f1dde27f4 100644 --- a/.graphqlconfig +++ b/.graphqlconfig @@ -1,28 +1,10 @@ { "projects": { - "access-boston": { - "schemaPath": "./services-js/access-boston/graphql/schema.graphql", - "includes": [ - "./services-js/access-boston/**/*.{graphql,ts,tsx,js,jsx}" - ] - }, "permit-finder": { "schemaPath": "./services-js/permit-finder/graphql/schema.graphql", "includes": [ "./services-js/permit-finder/**/*.{graphql,ts,tsx,js,jsx}" ] - }, - "commissions-app": { - "schemaPath": "./services-js/commissions-app/graphql/schema.graphql", - "includes": [ - "./services-js/commissions-app/**/*.{graphql,ts,tsx,js,jsx}" - ] - }, - "registry-certs": { - "schemaPath": "./services-js/registry-certs/graphql/schema.graphql", - "includes": [ - "./services-js/registry-certs/**/*.{graphql,ts,tsx,js,jsx}" - ] } } } diff --git a/package.json b/package.json index 58547942e..7f9da938b 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "khaos": "^0.9.3", "lerna": "^4.0.0", "lint-staged": "^8.1.0", + "mockdate": "^2.0.2", "prettier": "^1.17.0", "typescript": "^4.4.2" } diff --git a/services-js/access-boston/.babelrc b/services-js/access-boston/.babelrc deleted file mode 100644 index 9c3efc983..000000000 --- a/services-js/access-boston/.babelrc +++ /dev/null @@ -1,17 +0,0 @@ -{ - "presets": [ - "@cityofboston/config-babel/next", - "@cityofboston/config-babel/typescript", - "@cityofboston/config-babel/storybook" - ], - "plugins": [ - [ - "inline-import", - { - "extensions": [ - ".yaml", - ] - } - ] - ] -} \ No newline at end of file diff --git a/services-js/access-boston/.env.sample b/services-js/access-boston/.env.sample deleted file mode 100644 index ac0555cd3..000000000 --- a/services-js/access-boston/.env.sample +++ /dev/null @@ -1,37 +0,0 @@ -SESSION_COOKIE_PASSWORD_KMS_ENCRYPTED= - -PING_HOST= -SINGLE_LOGOUT_URL= - -HAPI_REDIS_CACHE_HOST= -HAPI_REDIS_CACHE_PORT= -HAPI_REDIS_CACHE_DATABASE= - -API_KEYS= -WEB_API_KEY= - -IDENTITYIQ_URL= -IDENTITYIQ_USERNAME= -IDENTITYIQ_PASSWORD_KMS_ENCRYPTED= - -GROUP_MANAGEMENT_API_URL=http://localhost:7000 - -ROLLBAR_ENVIRONMENT= -ROLLBAR_ACCESS_TOKEN= -ROLLBAR_BROWSER_ACCESS_TOKEN= - -GOOGLE_TRACKING_ID= -GTM_CONTAINER_ID= - -ASSET_HOST= - -ID_VERIFICATION_HTTP_METHOD= -ID_VERIFICATION_PROTOCOL= -ID_VERIFICATION_BASEURL= -ID_VERIFICATION_PORT= -ID_VERIFICATION_PATH= -ID_VERIFICATION_FILTER= -ID_VERIFICATION_USERNAME= -ID_VERIFICATION_PWD= -ID_VERIFICATION_AUTH= -ID_VERIFICATION_COOKIE= diff --git a/services-js/access-boston/.eslintrc b/services-js/access-boston/.eslintrc deleted file mode 100644 index 487cfa9d5..000000000 --- a/services-js/access-boston/.eslintrc +++ /dev/null @@ -1,19 +0,0 @@ -{ - "plugins": [ - "graphql" - ], - "rules": { - "graphql/template-strings": [ - "error", - { - "projectName": "access-boston" - } - ], - "graphql/named-operations": [ - "error", - { - "projectName": "access-boston" - } - ] - } -} diff --git a/services-js/access-boston/.gitignore b/services-js/access-boston/.gitignore deleted file mode 100644 index 2e20105cb..000000000 --- a/services-js/access-boston/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -/saml-metadata.xml -/saml-forgot-metadata.xml -service-provider.crt -service-provider.key -/pingid.properties -/apps.yaml diff --git a/services-js/access-boston/.storybook/.babelrc b/services-js/access-boston/.storybook/.babelrc deleted file mode 100644 index 61ee23b00..000000000 --- a/services-js/access-boston/.storybook/.babelrc +++ /dev/null @@ -1 +0,0 @@ -{ "extends": "../.babelrc" } \ No newline at end of file diff --git a/services-js/access-boston/.storybook/addons.js b/services-js/access-boston/.storybook/addons.js deleted file mode 100644 index a6461528a..000000000 --- a/services-js/access-boston/.storybook/addons.js +++ /dev/null @@ -1 +0,0 @@ -require('@cityofboston/storybook-common/addons'); diff --git a/services-js/access-boston/.storybook/config.js b/services-js/access-boston/.storybook/config.js deleted file mode 100644 index 6de578683..000000000 --- a/services-js/access-boston/.storybook/config.js +++ /dev/null @@ -1,21 +0,0 @@ -import { configure, addDecorator, addParameters } from '@storybook/react'; -import { setConfig } from 'next/config'; - -import { loadStories, storybookOptions } from '@cityofboston/storybook-common'; - -import './addons'; - -const req = require.context('../src', true, /\.stories\.(jsx?|tsx?)$/); - -addDecorator(story => { - setConfig({ - publicRuntimeConfig: {}, - serverRuntimeConfig: {}, - }); - - return story(); -}); - -addParameters(storybookOptions('access-boston')); - -configure(() => loadStories(req), module); diff --git a/services-js/access-boston/.storybook/manager-head.html b/services-js/access-boston/.storybook/manager-head.html deleted file mode 100644 index 9191c39c0..000000000 --- a/services-js/access-boston/.storybook/manager-head.html +++ /dev/null @@ -1,10 +0,0 @@ - - diff --git a/services-js/access-boston/.storybook/preview-head.html b/services-js/access-boston/.storybook/preview-head.html deleted file mode 100644 index 41348c942..000000000 --- a/services-js/access-boston/.storybook/preview-head.html +++ /dev/null @@ -1,7 +0,0 @@ - - - - - \ No newline at end of file diff --git a/services-js/access-boston/.storybook/webpack.config.js b/services-js/access-boston/.storybook/webpack.config.js deleted file mode 100644 index 63e0af29c..000000000 --- a/services-js/access-boston/.storybook/webpack.config.js +++ /dev/null @@ -1,3 +0,0 @@ -const { webpackConfig } = require('@cityofboston/storybook-common'); - -module.exports = ({ config }) => webpackConfig(config); diff --git a/services-js/access-boston/README.md b/services-js/access-boston/README.md deleted file mode 100644 index e802963b3..000000000 --- a/services-js/access-boston/README.md +++ /dev/null @@ -1,136 +0,0 @@ -# Access Boston - -A web portal to point employees at enterprise applications they have access to. -Also contains the UI for change password, forgot password, and new account -setup. - -## Development - -### Adding a new application to the portal -[./fixtures/apps.yaml](./fixtures/apps.yaml) provides documentation of the portal configuration, and should be updated to mirror what is in ECS. - -1. update `dev` and `test` on AppsStaging ([guide](https://app.gitbook.com/@boston/s/digital/guides/amazon-web-services/service-configuration/editing-a-projects-.env-using-cyberduck)) -2. restart the service ([guide](https://app.gitbook.com/@boston/s/digital/guides/amazon-web-services/service-configuration/restarting-an-ecs-service)) -3. once the restart is complete and the `service access-boston has reached a steady state.` event occurs, go ahead and update the config on AppsProd. - - -### Test authentication - -By default, the dev server does not attempt to use real SAML for authentication. -(You can change this by setting `SAML_IN_DEV=true` in `.env`) You can log in -with any user ID through the fake login form. - -**SPECIAL VALUES** - - * Log in with an account ID that starts with "NEW" to get to the registration - flow (See `SamlAuthFake.ts`) - * Try to use "wrong-password" as your existing password to get a password - creation failure. (See `IdentityIqFake.ts`) - -### TestCafe - -This service has [TestCafé](http://devexpress.github.io/testcafe/) tests to -validate important integrations and login behavior. - -These run with a headless Chrome browser as part of the `test` script. These -tests are run against a pre-compiled Next.js app with no environment variables -set (beyond `NODE_ENV=testcafe`). - -To debug the tests, start your dev server with `yarn dev` and then run `yarn -testcafe:dev` in another terminal window. You will need to comment out lines -in `.env` that configure external SAML or IdentityIq services. (You could also -run with `NODE_ENV` set to `testcafe` but you’ll need to pre-compile with `yarn -build` since `testcafe` doesn’t run Next in watch mode.) - -When `testcafe:dev` is running, go to the URL it prints out using your favorite -browser. - -_Note:_ The TestCafe `integrations` tests are excluded from `tsconfig.json` so -that their definitions of `test` don’t conflict with Jest’s. The `testcafe` -binary does not use `tsconfig.json`, so this doesn’t affect running the tests. - -## Staging - -Unlike other projects in the digital repo, there are **two** staging instances -that can be pushed to, “dev” and “test”. Pushing a branch to staging looks like -this: - -``` -$ git push --force --no-verify origin HEAD:staging/access-boston@dev -``` -https://access-boston-dev.digital-staging.boston.gov/ - -or -``` -$ git push --force --no-verify origin HEAD:staging/access-boston@test -``` -https://access-boston-test.digital-staging.boston.gov/ - -## Deployment - -### Generating metadata - -The Ping identity provider needs us to generate a metadata file in order to -register as a service provider. We need two, actually: one for the normal app -and one for the forgot password flow (this is because they have separate MFA -requirements). - -We also need to generate keys. - - 1) `../../scripts/generate-ssl-key.sh . service-provider` - 1) `../../scripts/generate-ssl-key.sh . service-provider-forgot` - 1) In .env: - * Set `SAML_IN_DEV=true` - * Set `PUBLIC_HOST` to the user-visible hostname (_e.g._ `access-boston-dev.digital-staging.boston.gov`) - 1) `yarn dev` - 1) Download `http://localhost:3000/metadata.xml` and - `http://localhost:3000/metadata-forgot.xml` and send them to the IAM team. - - -## URLs - -### Localhost -http://localhost:3000/ - -### Staging -DEV: https://access-boston.dev.digital-staging.boston.gov/ -TEST: http://access-test.boston.gov/ - -### PROD -https://access-boston.boston.gov/ - -### Tech Debt: -- 2021.10.22: Bumping node, next, typescript, etc module up - - Removing the following files, TODO: Resolve issues later - - services-js/access-boston/src/client/group-management/ConfirmationView.stories.tsx - - services-js/access-boston/src/client/group-management/ReviewChangesView.stories.tsx - -### Deploys - -- 2020.05.27: New App entry, PHIRE -- 2020.07.10: Adding group to Building Maintenance Form Link -- 2020.07.14: Reverting Building Maintenance new group -- 2020.07.14: Test empty deploy, checking if failed deploy is related to expired build start -- 2020.08.04: New App Deploy (PROD) - RISKMASTER -- 2020.08.07: New App Deploy (PROD) - XYBION -- 2020.08.24: New App Deploy (PROD) - Gov QA -- 2020.08.24: App Deploy (PROD) - Gov QA, Group Name Fix (All Uppercase) -- 2020.08.28: Issue 602 Test -- 2021.03.09: Testing Security Patch and upgrades to Node version usuage -- 2021.03.26: Post Security Patch test deploys -- 2021.03.29: PROD deploy - ScerIS tile -- 2021.03.29: PROD deploy - ScerIS tile 2nd try -- 2021.03.29: PROD deploy - ScerIS tile 3rd try -- 2021.04.14: PROD deploy - Boston Gives Back (United Way) -- 2021.04.15: PROD deploy - ScerIS tile fix, uppercase groups -- 2021.05.20: PROD deploy - BAIS FN Tile link update -- 2021.05.20: PROD deploy - BAIS FN Tile link update, second attempt -- 2021.08.26: PROD deploy - ServiceNow (new dashboard App) -- 2021.08.30: PROD deploy - Covid Compliance -- 2021.09.20: PROD deploy - Hyperion, update app URL -- 2021.10.29: PROD deploy - V3 tile -- 2021.11.23: PROD deploy - Update IIQ URL -- 2021.12.13: PROD deploy - Beacon tile and ServiceNow Rebranding -- 2022.02.26: PROD deploy - Remove 'Boston Gives Back' icon/link from Dashboard -- 2022.02.28: PROD deploy - New Tile, CyberArk -- 2022.08.11: PROD deploy - Push-through Docker Image Sizing Fix diff --git a/services-js/access-boston/deploy/Dockerfile b/services-js/access-boston/deploy/Dockerfile deleted file mode 100644 index 03105d460..000000000 --- a/services-js/access-boston/deploy/Dockerfile +++ /dev/null @@ -1,74 +0,0 @@ -FROM node:14.19.1-alpine as build_phase - -ENV WORKSPACE=access-boston - -WORKDIR /app - -# Install python/pip -ENV PYTHONUNBUFFERED=1 -RUN apk add --no-cache git openssl bash \ - && apk add --update --no-cache python3 curl unzip \ - && ln -sf python3 /usr/bin/python \ - && python3 -m ensurepip \ - && pip3 install --no-cache --upgrade pip setuptools \ - && cd /tmp \ - && curl "https://s3.amazonaws.com/aws-cli/awscli-bundle.zip" -o "awscli-bundle.zip" \ - && unzip awscli-bundle.zip \ - && ./awscli-bundle/install -i /usr/local/aws -b /usr/local/bin/aws \ - && rm awscli-bundle.zip \ - && rm -rf awscli-bundle - -# To prevent “Error: could not get uid/gid” -RUN npm config set unsafe-perm true - -# Need to upgrade yarn to at least 1.6 -RUN yarn global add yarn@^1.6.0 - -# This is the tar'd up collection of package.json files created by -# build-service-container.sh. Working with it and the lockfiles means we can -# cache the yarn install across builds when there are no dependency changes. -ADD package-json.tar /app/ -ADD yarn.lock lerna.json .yarnrc /app/ - -# We don’t run the scripts because they will try to build our custom packages, -# which will fail because we don’t have the source code at this point. -# -# TODO(finh): Scope this down to $WORKSPACE when yarn has that capability. -RUN yarn install --frozen-lockfile --ignore-scripts - -ADD . /app/ - -RUN /app/scripts/generate-ssl-key.sh /app/services-js/$WORKSPACE - -# This does the building of our repo’s packages, which couldn’t happen during -# the initial install because we didn’t have source. The scope keeps us from -# building unnecessary packages. -RUN npx lerna run --stream --include-filtered-dependencies --scope services-js.$WORKSPACE prepare -RUN npx lerna run --stream --include-filtered-dependencies --scope services-js.$WORKSPACE prepare-deploy - -FROM node:14.19.1-alpine as deploy_phase - -EXPOSE 3000 - -ENV WORKSPACE=access-boston -ENV NODE_ENV=production -ENV USE_SSL=1 - -WORKDIR /app/services-js/$WORKSPACE - -RUN apk add --no-cache git openssl \ - && apk add --update --no-cache python3 curl unzip \ - && ln -sf python3 /usr/bin/python \ - && python3 -m ensurepip \ - && pip3 install --no-cache --upgrade pip setuptools \ - && cd /tmp \ - && curl "https://s3.amazonaws.com/aws-cli/awscli-bundle.zip" -o "awscli-bundle.zip" \ - && unzip awscli-bundle.zip \ - && ./awscli-bundle/install -i /usr/local/aws -b /usr/local/bin/aws \ - && rm awscli-bundle.zip \ - && rm -rf awscli-bundle - -COPY --from=build_phase /app /app - -ENTRYPOINT ["/app/scripts/service-entrypoint.sh"] -CMD ["yarn", "start"] diff --git a/services-js/access-boston/fixtures/apps.yaml b/services-js/access-boston/fixtures/apps.yaml deleted file mode 100644 index 4e08dc07c..000000000 --- a/services-js/access-boston/fixtures/apps.yaml +++ /dev/null @@ -1,176 +0,0 @@ -notice: - label: 'Notice' - pretext: '' - text: 'Make your Access Boston account even more secure by setting up the PingID app. It is faster and easier to use than getting codes via text, email or phone! Directions are [here](https://www.boston.gov/access-boston-portal-help#pingid-app-instructions)' -categories: - - title: Applications - show_request_access_link: false - icons: true - apps: - - title: Beacon - url: https://cibostondev.service-now.com/cob - icon: https://assets.boston.gov/icons/accessboston/lighthouse_basic_icon.svg - groups: - - SG_AB_ESS - - title: Employee Self-Service - url: https://ess.boston.gov/ - icon: /assets/apps/ess.svg - groups: - - SG_AB_ESS - - title: The Hub - url: https://hub.boston.gov/ - icon: /assets/apps/hub.svg - - title: AgilePoint - url: https://boston.nxone.com - icon: https://assets.boston.gov/icons/accessboston/agilepoint.svg - groups: - - SG_AB_AGILEPOINT - - title: Security Awareness - url: https://boston.wombatsecurity.com - icon: https://assets.boston.gov/icons/accessboston/proofpoint-web-security.svg - groups: - - SG_AB_PROOFPOINT - - title: Current Job Openings - url: https://sso.boston.gov/idp/startSSO.ping?PartnerSpId=icims.com&TargetResource=https%3A//employeeconnect-boston.icims.com/r.jsp - icon: /assets/apps/job-openings.svg - groups: - - SG_AB_ICIMS - - title: Current Job Openings - icon: /assets/apps/job-openings.svg - url: https://careercenter-boston.icims.com/jobs/ - groups: - - SG_AB_ALLSPONSORED - - title: Career Center - url: https://sso.boston.gov/idp/startSSO.ping?PartnerSpId=icims.com - icon: /assets/apps/career-center.svg - groups: - - SG_AB_CAREERCTR - - title: BPS Job Listings - url: https://bostonpublicschools.tedk12.com/hire/index.aspx - icon: /assets/apps/talented.svg - - title: Employee Training - url: https://sso.boston.gov/idp/startSSO.ping?PartnerSpId=http%3A%2F%2Fwww.mylearningplan.com%2Fmvc%2Fsaml%2Fmetadata%2F13583 - icon: /assets/apps/employee-training.svg - groups: - - SG_AB_MLP - - title: Boston Maps - url: https://sso.boston.gov/idp/startSSO.ping?PartnerSpId=boston.maps.arcgis.com - icon: /assets/apps/boston-maps.svg - - title: BAIS FN - url: https://baisfn.cityhall.boston.cob/psp/prdfn/EMPLOYEE/ERP/h/?tab=DEFAULT - icon: /assets/apps/bais-fn.svg - groups: - - SG_AB_PSFN - - title: BAIS HCM - url: https://hcm.cityhall.boston.cob/psp/pshrpd3/EMPLOYEE/HRMS/h/?tab=DEFAULT - icon: /assets/apps/bais-hcm.svg - groups: - - SG_AB_PSHCM - - title: Hyperion - url: https://login.us2.oraclecloud.com/oam/server/obrareq.cgi?encquery%3Dv2GuCFxM8VxsxDZPK9HzUGyy9Q3kTEgTTNlLlAmtoZTwncih0HU8NAbYsRh9raKXKy8d7P4g6V6FY84QopZtihSoEOcPDIVU7qktPzNiMf87zkN0Hm9YU%2BT%2B3ytwl%2FKTMsgQpsowiFHZDm13DFTGawpK%2B%2Bt3RL%2F3YiNDK33gCZnp9AZw%2Fm5OFX8d9xf%2BP8q9mlMEN3SrirXdsgAq4Jzli5LbTH38nJTp9y0kYPR1cnwH8VP80yul1ZxpngYMTatK3Z613FGf4luKZdL0G9560UC5cdbxKo3Rqhwp76BvyRFaQ%2B%2F%2FhwI3PIWWdLcyR4scKux9x5p5H2GePV7xJSst0g%3D%3D%20agentid%3DPlanning_WG%20ver%3D1%20crmethod%3D2 - icon: /assets/apps/hyperion.svg - groups: - - SG_AB_HYPERION - - title: Civis - url: https://sso.boston.gov/idp/startSSO.ping?PartnerSpId=https%3A%2F%2Fplatform.civisanalytics.com%2Fusers%2Fsaml%2Fmetadata - icon: /assets/apps/civis.svg - groups: - - SG_AB_CIVIS - - title: IIQ - url: https://identity.boston.gov/identityiq/ - icon: /assets/apps/identityiq.svg - groups: - - SG_AB_IAM_TEAM - - title: E-Builder - url: https://sso.boston.gov/idp/startSSO.ping?PartnerSpId=sso.e-builder.net - icon: /assets/apps/e-builder.svg - groups: - - SG_AB_EBUILDER - - title: ServiceNow - Lightkeeper - url: https://sso-test.boston.gov/idp/startSSO.ping?PartnerSpId=https%3A%2F%2Fcibostontest.service-now.com - icon: https://assets.boston.gov/icons/accessboston/lantern.svg - groups: - - SG_AB_SNOWADMIN - - title: Tanium - url: https://boston.cloud.tanium.com - icon: https://assets.boston.gov/icons/experiential_icons/secure.svg - groups: - - SG_AB_TANIUM - - title: Confirm ID - url: /confirmid - target: _blank - icon: https://assets.boston.gov/icons/accessboston/identity_verification.svg - groups: - - SG_AB_CONFIRMID - - - title: Forms / Links - apps: - - title: Create Sponsored Account - url: https://identity.boston.gov/identityiq/createSponsor.jsf - groups: - - SG_AB_SPONSOR - - title: FleetHub - url: http://boston.getlocalmotion.com/ - agencies: - - CH - - title: International Travel - url: https://goo.gl/forms/Xobh9aZeGXSnKH2m2 - agencies: - - BPD - - BFD - - CH - - title: Building Maintenance - url: https://eamtst.cityhall.boston.cob:5443/WebMaint/ - agencies: - - CH - groups: - - SG_AB_BLDGMAINTREQ - - title: Request FN Access - url: https://identity.boston.gov/identityiq/requestPsfn.jsf - groups: - - SG_AB_FN_LIASONS_USERS - - - title: Account Tools - apps: - - title: Change My Password - url: /change-password - - title: Manage My Device(s) - url: https://desktop.pingone.com/boston/Selection?cmd=devices - mfa_device_required: true - - - title: Support Tools - apps: - - title: Identity Verification - url: https://identity.boston.gov/identityiq/identityVerification.jsf - groups: - - SG_AB_SERVICEDESK_USERS - - title: View User Information - url: https://identity.boston.gov/identityiq/viewUserIdentityonly.jsf - groups: - - SG_AB_SERVICEDESK_USERS - - title: Edit Group - url: '#' - groups: - - SG_AB_HCMSECURITY - - title: Group Management - url: /group-management - target: _blank - - - title: Manager Tools - apps: - - title: Set an Access Boston Delegate - url: https://identity.boston.gov/identityiq/dashboard/editPreferences.jsf - groups: - - SG_AB_MANAGER - - SG_AB_TRAINING_TEAM - - SG_AB_FN_LIASONS_USERS - - title: My Access Boston Approvals - url: https://identity.boston.gov/identityiq/manage/workItems/workItems.jsf?workItemType=Approval - groups: - - SG_AB_MANAGER - - SG_AB_TRAINING_TEAM - - SG_AB_FN_LIASONS_USERS - - title: Manage Sponsored Account - url: https://identity.boston.gov/identityiq/extendSponsor.jsf - groups: - - SG_AB_SPONSOR diff --git a/services-js/access-boston/fixtures/id-verification/test/101226.json b/services-js/access-boston/fixtures/id-verification/test/101226.json deleted file mode 100644 index c339c34ac..000000000 --- a/services-js/access-boston/fixtures/id-verification/test/101226.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "totalResults": 1, - "startIndex": 1, - "Resources": [ - { - "urn:ietf:params:scim:schemas:sailpoint:1.0:User": { - "identity_type": "EMPLOYEE" - }, - - "displayName": "101226", - "name": { - "formatted": "101226" - }, - "userName": "101226" - } - ] -} diff --git a/services-js/access-boston/fixtures/id-verification/test/123456.json b/services-js/access-boston/fixtures/id-verification/test/123456.json deleted file mode 100644 index 361bfd7e1..000000000 --- a/services-js/access-boston/fixtures/id-verification/test/123456.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "totalResults": 1, - "startIndex": 1, - "Resources": [ - { - "urn:ietf:params:scim:schemas:sailpoint:1.0:User": { - "ssn": "123456", - "identity_type": "EMPLOYEE" - }, - "displayName": "101226", - "name": { - "formatted": "101226" - }, - "userName": "123456" - } - ] -} diff --git a/services-js/access-boston/fixtures/id-verification/test/139562.json b/services-js/access-boston/fixtures/id-verification/test/139562.json deleted file mode 100644 index 336be23c7..000000000 --- a/services-js/access-boston/fixtures/id-verification/test/139562.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "totalResults": 1, - "startIndex": 1, - "Resources": [ - { - "urn:ietf:params:scim:schemas:sailpoint:1.0:User": { - "ssn": "0000", - "identity_type": "EMPLOYEE" - }, - "displayName": "Will Scott", - "name": { - "formatted": "Will Scott", - "familyName": "Scott", - "givenName": "Will" - }, - "userName": "139562" - } - ] -} diff --git a/services-js/access-boston/fixtures/id-verification/test/40000093.json b/services-js/access-boston/fixtures/id-verification/test/40000093.json deleted file mode 100644 index a038e6370..000000000 --- a/services-js/access-boston/fixtures/id-verification/test/40000093.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "totalResults": 1, - "startIndex": 1, - "Resources": [ - { - "urn:ietf:params:scim:schemas:sailpoint:1.0:User": { - "cobDob": "01/01/1989", - "identity_type": "CONTRACTOR" - }, - "displayName": "Manuel Webster Test", - "name": { - "formatted": "Manuel Webster Test", - "familyName": "Webster Test", - "givenName": "Manuel" - }, - "userName": "40000093" - } - ] -} diff --git a/services-js/access-boston/fixtures/identityiq/LaunchWorkflow-ChangePassword-failure.json b/services-js/access-boston/fixtures/identityiq/LaunchWorkflow-ChangePassword-failure.json deleted file mode 100644 index 9ba895f74..000000000 --- a/services-js/access-boston/fixtures/identityiq/LaunchWorkflow-ChangePassword-failure.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "partitioned": false, - "completed": "2018-08-21T09:53:42.181-04:00", - "type": "Workflow", - "launched": "2018-08-21T09:53:39.412-04:00", - "pendingSignoffs": 0, - "urn:ietf:params:scim:schemas:sailpoint:1.0:LaunchedWorkflow": { - "output": [ - { - "type": "application/xml", - "value": "\n", - "key": "workflowSummary" - } - ], - "input": [ - {} - ], - "workflowSummary": "\n", - "workflowName": "COB-RESTAPI-Workflow-ChangePassword - 6" - }, - "meta": { - "created": "2018-08-21T09:53:42.209-04:00", - "location": "http://id.boston.gov/identityiq/scim/v2/TaskResults/8a71efd26548f8c501655cc337415ef0", - "version": "W/\"1534859622209\"", - "resourceType": "LaunchedWorkflow" - }, - "schemas": [ - "urn:ietf:params:scim:schemas:sailpoint:1.0:LaunchedWorkflow", - "urn:ietf:params:scim:schemas:sailpoint:1.0:TaskResult" - ], - "name": "COB-RESTAPI-Workflow-ChangePassword - 6", - "messages": [ - "You have entered an invalid current password." - ], - "attributes": [ - { - "value": "\n", - "key": "workflowSummary" - } - ], - "id": "8a71efd26548f8c501655cc337415ef0", - "completionStatus": "Error", - "taskDefinition": "Workflow Launcher", - "terminated": false, - "launcher": "CON01234" -} \ No newline at end of file diff --git a/services-js/access-boston/fixtures/identityiq/LaunchWorkflow-ChangePassword-success.json b/services-js/access-boston/fixtures/identityiq/LaunchWorkflow-ChangePassword-success.json deleted file mode 100644 index 5354dd955..000000000 --- a/services-js/access-boston/fixtures/identityiq/LaunchWorkflow-ChangePassword-success.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "partitioned": false, - "completed": "2018-09-14T13:45:54.869-04:00", - "type": "Workflow", - "launched": "2018-09-14T13:45:50.440-04:00", - "pendingSignoffs": 0, - "urn:ietf:params:scim:schemas:sailpoint:1.0:LaunchedWorkflow": { - "output": [ - { - "type": "application/xml", - "value": "\n", - "key": "workflowSummary" - } - ], - "input": [ - {} - ], - "workflowSummary": "\n", - "workflowName": "COB-RESTAPI-Workflow-ChangePassword - 58" - }, - "meta": { - "created": "2018-09-14T13:45:54.899-04:00", - "location": "http://10.241.111.82:8086/identityiq/scim/v2/TaskResults/8a71efd2657325930165d9306fd303e7", - "version": "W/\"1536947154899\"", - "resourceType": "LaunchedWorkflow" - }, - "schemas": [ - "urn:ietf:params:scim:schemas:sailpoint:1.0:LaunchedWorkflow", - "urn:ietf:params:scim:schemas:sailpoint:1.0:TaskResult" - ], - "name": "COB-RESTAPI-Workflow-ChangePassword - 58", - "messages": [], - "attributes": [ - { - "value": "\n", - "key": "workflowSummary" - } - ], - "id": "8a71efd2657325930165d9306fd303e7", - "completionStatus": "Success", - "taskDefinition": "Workflow Launcher", - "terminated": false, - "launcher": "139789" -} \ No newline at end of file diff --git a/services-js/access-boston/fixtures/identityiq/LaunchWorkflow-ResetPassword-success.json b/services-js/access-boston/fixtures/identityiq/LaunchWorkflow-ResetPassword-success.json deleted file mode 100644 index 71493dd3d..000000000 --- a/services-js/access-boston/fixtures/identityiq/LaunchWorkflow-ResetPassword-success.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "partitioned": false, - "completed": "2018-09-14T13:44:48.564-04:00", - "type": "Workflow", - "launched": "2018-09-14T13:44:44.325-04:00", - "pendingSignoffs": 0, - "urn:ietf:params:scim:schemas:sailpoint:1.0:LaunchedWorkflow": { - "output": [ - { - "type": "application/xml", - "value": "\n", - "key": "workflowSummary" - } - ], - "input": [ - {} - ], - "workflowSummary": "\n", - "workflowName": "REST_API_TEST2_WORKFLOW - 5" - }, - "meta": { - "created": "2018-09-14T13:44:48.658-04:00", - "location": "http://10.241.111.82:8086/identityiq/scim/v2/TaskResults/8a71efd2657325930165d92f6d1203c5", - "version": "W/\"1536947088658\"", - "resourceType": "LaunchedWorkflow" - }, - "schemas": [ - "urn:ietf:params:scim:schemas:sailpoint:1.0:LaunchedWorkflow", - "urn:ietf:params:scim:schemas:sailpoint:1.0:TaskResult" - ], - "name": "REST_API_TEST2_WORKFLOW - 5", - "messages": [], - "attributes": [ - { - "value": "\n", - "key": "workflowSummary" - } - ], - "id": "8a71efd2657325930165d92f6d1203c5", - "completionStatus": "Success", - "taskDefinition": "Workflow Launcher", - "terminated": false, - "launcher": "139789" -} \ No newline at end of file diff --git a/services-js/access-boston/fixtures/pingid/adduser.json b/services-js/access-boston/fixtures/pingid/adduser.json deleted file mode 100644 index 2db993b82..000000000 --- a/services-js/access-boston/fixtures/pingid/adduser.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "activationCode": "260453805723", - "userDetails": { - "userName": "123456", - "userInBypass": false, - "userId": 0, - "email": "test@boston.gov", - "lname": "User", - "userEnabled": false, - "fname": "Test", - "picURL": "IZIWUVWM6ZZPI37PRVXTBMWXM4SKLUBTT5YBLQNPLSV2T6HBKAAKPFY=", - "spList": [], - "lastLogin": null, - "bypassExpiration": null, - "deviceDetails": null, - "lastTransactions": [], - "devicesDetails": null, - "status": "PENDING_ACTIVATION", - "role": "REGULAR" - }, - "errorId": 200, - "errorMsg": "ok", - "uniqueMsgId": "webs_h1PGC2qbi2uVujF1SWU4m2RI9Qw5vCL7dd7wZXvgrow", - "clientData": null -} diff --git a/services-js/access-boston/fixtures/pingid/getuserdetails-exists.json b/services-js/access-boston/fixtures/pingid/getuserdetails-exists.json deleted file mode 100644 index 279c6335c..000000000 --- a/services-js/access-boston/fixtures/pingid/getuserdetails-exists.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "responseBody": { - "userDetails": { - "userName": "123456", - "email": null, - "lname": "", - "userId": 0, - "userInBypass": false, - "userEnabled": true, - "fname": "", - "picURL": "IZIWUVWM6ZZPI37PRVXTBMWXM4SKLUBTT5YBLQNPLSV2T6HBKAAKPFY=", - "spList": [], - "lastLogin": 1537794525886, - "bypassExpiration": null, - "deviceDetails": [], - "lastTransactions": [], - "devicesDetails": [], - "status": "ACTIVE", - "role": "REGULAR" - }, - "sameDeviceUsersDetails": [], - "errorMsg": "ok", - "uniqueMsgId": "webs_omTAYBPZhxcjOJaLEhruSAdIMgdJqatjjOiliCIigtI", - "errorId": 200, - "clientData": null - } -} diff --git a/services-js/access-boston/fixtures/pingid/getuserdetails-notexists.json b/services-js/access-boston/fixtures/pingid/getuserdetails-notexists.json deleted file mode 100644 index f3cd1809b..000000000 --- a/services-js/access-boston/fixtures/pingid/getuserdetails-notexists.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "responseBody": { - "userDetails": null, - "sameDeviceUsersDetails": [], - "uniqueMsgId": "webs_wh3RS0Uobz-UOEiIgdCiicZlxGV7EXJDny2z4_fBgyw", - "errorMsg": "User not exists.", - "errorId": 10564, - "clientData": null - } -} diff --git a/services-js/access-boston/fixtures/saml-metadata.xml b/services-js/access-boston/fixtures/saml-metadata.xml deleted file mode 100644 index bdac8b1ea..000000000 --- a/services-js/access-boston/fixtures/saml-metadata.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - - oxeMrSpRl0C5KvRNUtSLaroPm8KlPh8Y463tyQ6xJbA= - - - -EO2NnslQMJxOhNLRKyaq5Z5ltPmMMIKbtRuKV8POGAWXyGJDL8qONTOlTr4vLvguZcqM3rLjDFxo ynVgrPmXqhwgfqF0Sg0+Va1Q86ozAo34z6dhIStwzh+LGhtkzi8NWPGERIgz9JlCYFlvweyAxfXF +ONxGqoGyOFv0QS9JF+sco6T1VxQ8hAw4yLU99JR8nfoTW2wsz6Ybl7TOvmVjGNAZhyqZjuv4XJW l+yn2Nyh5RJMrYMRYnZB9Ecl4Cjk9VbogqE8ENtv/8h/rTk2yTGTlmDxTdPDIwFik96KNb8z9B2I K+YfSLSPXit99ATDpTHQlEfQFBaOATmXNcLc0A== - - - - - - - MIIHuzCCBaOgAwIBAgITFgAAAO/Occ90ikDr+wAAAAAA7zANBgkqhkiG9w0BAQsFADBwMRMwEQYKCZImiZPyLGQBGRYDY29iMRYwFAYKCZImiZPyLGQBGRYGYm9zdG9uMRgwFgYKCZImiZPyLGQBGRYIY2l0eWhhbGwxJzAlBgNVBAMTHkNpdHlPZkJvc3Rvbi1FbnRlcnByaXNlLVN1Yi1DQTAeFw0xODAyMjYxOTAxMDFaFw0yMTAyMjUxOTAxMDFaMIGAMQswCQYDVQQGEwJVUzELMAkGA1UECBMCTUExDzANBgNVBAcTBkJvc3RvbjEXMBUGA1UEChMOQ2l0eSBPZiBCb3N0b24xEDAOBgNVBAsTB0lBTSBTU08xKDAmBgNVBAMTH3pkcGluZ2ZlZDAxLmNpdHloYWxsLmJvc3Rvbi5jb2IwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQChXwANkULq6nkAZNHNpXmEMbboxJ6ihFlXcmVoQEx3HwBqdfE6Fk5oRu744aDApbdUc0J4WK/Cv4xcjw2WRG1b7TUjHFzfQMbziuNlik/o65zDjEo0WKfJLKZ/P6CweWtSREMLwqbjkt+RbOBdFcu79FcwPef5jE6fpddipKxOWZq+QZ05Kesap/v7OPc/pKAAelbeIZa3gtJesOVzLHXcd3O1620hy3lVYCZjJgVroRVhOxO6S9w9Nylo2Tt1xVipwr3CCDNv97qp5KyFczrQ1UYlJu/iqTcP6YJtQvGZNDtS5ZHul+8JdDU4osrsYHE3Gkpt9jPD9Kxhcg5brdL3AgMBAAGjggM7MIIDNzAdBgNVHQ4EFgQU2ayFg7boLF++hn+cIDBP8AuBYL4wHwYDVR0jBBgwFoAUDIanuimnqw/y+njgpCNP4oCVxbYwggErBgNVHR8EggEiMIIBHjCCARqgggEWoIIBEoaBymxkYXA6Ly8vQ049Q2l0eU9mQm9zdG9uLUVudGVycHJpc2UtU3ViLUNBLENOPVpQQ09CU0NBQVBQMDEsQ049Q0RQLENOPVB1YmxpYyUyMEtleSUyMFNlcnZpY2VzLENOPVNlcnZpY2VzLENOPUNvbmZpZ3VyYXRpb24sREM9Ym9zdG9uLERDPWNvYj9jZXJ0aWZpY2F0ZVJldm9jYXRpb25MaXN0P2Jhc2U/b2JqZWN0Q2xhc3M9Y1JMRGlzdHJpYnV0aW9uUG9pbnSGQ2h0dHA6Ly9jcmwuYm9zdG9uLmNvYi9DZXJ0RW5yb2xsL0NpdHlPZkJvc3Rvbi1FbnRlcnByaXNlLVN1Yi1DQS5jcmwwggFEBggrBgEFBQcBAQSCATYwggEyMIG8BggrBgEFBQcwAoaBr2xkYXA6Ly8vQ049Q2l0eU9mQm9zdG9uLUVudGVycHJpc2UtU3ViLUNBLENOPUFJQSxDTj1QdWJsaWMlMjBLZXklMjBTZXJ2aWNlcyxDTj1TZXJ2aWNlcyxDTj1Db25maWd1cmF0aW9uLERDPWJvc3RvbixEQz1jb2I/Y0FDZXJ0aWZpY2F0ZT9iYXNlP29iamVjdENsYXNzPWNlcnRpZmljYXRpb25BdXRob3JpdHkwcQYIKwYBBQUHMAKGZWh0dHA6Ly9haWEuYm9zdG9uLmNvYi9DZXJ0RW5yb2xsL1pQQ09CU0NBQVBQMDEuY2l0eWhhbGwuYm9zdG9uLmNvYl9DaXR5T2ZCb3N0b24tRW50ZXJwcmlzZS1TdWItQ0EuY3J0MA4GA1UdDwEB/wQEAwIFoDA8BgkrBgEEAYI3FQcELzAtBiUrBgEEAYI3FQiG54BehtyeT4eZmyaF9OcShcyXUF+L62mH7PtvAgFkAgEKMBMGA1UdJQQMMAoGCCsGAQUFBwMBMBsGCSsGAQQBgjcVCgQOMAwwCgYIKwYBBQUHAwEwDQYJKoZIhvcNAQELBQADggIBAAwojfsctWBq3KeYXWzlMtIzB6c0xLOFvw/hcsKT+n7oQG1RbN4For/SJtbtdE7cswNjOc7ZKI9KiRzSJSBPtVmiRfyaCMeNLbkXqrYJ8/Ke/APOuVJhBtbm5WleNUovr0BscVC/5bOv8PNlmenS9DPjucvHoghX++91+od7zca2kzavvP5TZhmc0+ls//dFwjPrKQudb2Bo3y93z6sUh+zaadvMv3jwKMyLWY2oEmaUIjMRyuPMKKbOFb0U+rIfB49vsAc3HqVMFf5mx0O7fVot91V7zfp1hNGm7CXMyKuMh8MRuwHxPPL0GmRD0PRNs2KCWSd1fnkFM5u8gDIQyGO/j88Epc+zWMiWYyQVc+7qvCVuDZh2cWaeEiNeOrOiPbkrtftMf1IxO091IwqGKEfEo8cN+d4EDxMMR8hUcKnZ+wXoKwfzXaXX++7ARwBeEtVASqNw5r7ncY/3LEZPwDYyDPIMubjZtpZeSzL71G0AWKGXUcAU0TReZyLGGWTdojoiuIsVifaMZNpMVEFQ5+xWw49XmXIBGuZYyWLf1SO87baq8J8/w5EnMnJcbsB+oWtzUvu+td6UvRup48VRBabtr9FM3QtI4GkjwVoZWihHMYK92iThcZShm5mBSLvSDsoKSRYU/zOKy4ZQ473zGDDp3AsBC5tsnNmqYnaSDXIN - - - - urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified - - - - - diff --git a/services-js/access-boston/next.config.js b/services-js/access-boston/next.config.js deleted file mode 100644 index f68fd23d2..000000000 --- a/services-js/access-boston/next.config.js +++ /dev/null @@ -1,9 +0,0 @@ -const path = require('path'); -const withPolyfill = require('@cityofboston/next-client-common/with-polyfill')(); - -module.exports = withPolyfill({ - distDir: path.join('..', 'build', '.next'), - env: { - GROUP_MANAGEMENT_API_URL: process.env.GROUP_MANAGEMENT_API_URL, - }, -}); diff --git a/services-js/access-boston/package.json b/services-js/access-boston/package.json deleted file mode 100644 index 6c0845a6e..000000000 --- a/services-js/access-boston/package.json +++ /dev/null @@ -1,132 +0,0 @@ -{ - "name": "services-js.access-boston", - "version": "0.0.0", - "description": "Frontend for SSO-enabled apps list", - "private": true, - "license": "CC0-1.0", - "scripts": { - "prebuild": "rimraf build && yarn run generate-graphql-schema && yarn run generate-graphql-types", - "build": "concurrently \"yarn run build:server\" \"yarn run build:next\"", - "build:server": "tsc -p tsconfig.server.json", - "build:next": "next build src", - "dev": "yarn run prebuild && tsc-watch -p tsconfig.server.json --onSuccess \"yarn run start\"", - "storybook": "start-storybook -p 9001 -s static", - "watch-dependencies": "lerna run --parallel --scope access-boston --include-filtered-dependencies watch", - "prepare": "yarn run build", - "start": "node build/server/start", - "pretest": "yarn run prebuild && tsc --noEmit", - "test": "yarn run testcafe:local && jest", - "testcafe:local": "yarn run build && cross-env TEST_SERVER_URL=http://localhost:3032 PORT=3032 NODE_ENV=testcafe testcafe -a 'node build/server/start' --app-init-delay 3000 -S -s screenshots -c 2 chrome:headless src/**/*.testcafe.*", - "percy": "scripts/percy.js", - "generate-graphql-schema": "mkdir -p graphql && ts2gql src/server/graphql/schema.ts > graphql/schema.graphql", - "generate-graphql-types": "npx apollo codegen:generate --includes=\"src/client/graphql/**/*.ts\" --localSchemaFile=graphql/schema.graphql --target=typescript --outputFlat --no-addTypename src/client/graphql/queries.ts", - "prepare-deploy": "build-storybook -s static", - "codebuild-deploy": "npx -p ../../deploy-tools.tgz codebuild-service-deploy deploy/Dockerfile", - "postdeploy": "ts-node --project ./tsnode.config.json ./scripts/post-deploy && yarn run cacheBuster", - "update-storyshots": "jest -u", - "cacheBuster": "source /app/scripts/cacheBuster.sh access-boston" - }, - "jest": { - "testPathIgnorePatterns": [ - "/build/", - "/node_modules/", - "/scripts/" - ] - }, - "resolutions": { - "graphql": "0.13.2" - }, - "dependencies": { - "@babel/runtime": "^7.6.0", - "@cityofboston/deploy-tools": "^0.0.0", - "@cityofboston/form-common": "^0.0.0", - "@cityofboston/hapi-common": "^0.0.0", - "@cityofboston/hapi-next": "^0.0.0", - "@cityofboston/next-client-common": "^0.0.0", - "@cityofboston/percy-common": "^0.0.0", - "@cityofboston/srv-decrypt-env": "^0.0.0", - "@emotion/core": "^10.0.10", - "apollo-boost": "^0.4.4", - "apollo-server-hapi": "^2.5.0", - "boom": "^7.2.0", - "catbox-redis": "^4.2.2", - "core-js": "3.19.3", - "cross-fetch": "^3.1.5", - "crumb": "^7.1.0", - "date-fns": "^1.29.0", - "dotenv": "^5.0.1", - "emotion": "^10.0.9", - "emotion-server": "^10.0.9", - "formik": "^1.4.2", - "graphql": "^14.3.0", - "hapi": "^17.5.2", - "hapi-accept-language2": "^2.0.3", - "hapi-cors": "^1.0.3", - "hapi-dev-errors": "^3.0.1", - "inert": "^5.1.0", - "java-properties": "^0.2.10", - "js-yaml": "^3.12.0", - "jws": "^3.1.5", - "markdown-to-jsx": "7.1.7", - "moment-timezone": "^0.5.14", - "next": "^9.0.6", - "next-cookies": "^1.0.2", - "node-cleanup": "^2.1.2", - "node-fetch": "^2.2.0", - "react": "16.8.5", - "react-autosuggest": "^9.4.3", - "react-dom": "16.8.5", - "react-window": "^1.8.5", - "rollbar": "^2.5.1", - "saml2-js": "^2.0.3", - "velocityjs": "^1.1.3", - "xmldom": "^0.1.27", - "xpath": "^0.0.27", - "yar": "^9.0.1", - "yup": "0.27.0" - }, - "devDependencies": { - "@babel/cli": "^7.6.0", - "@babel/core": "^7.6.0", - "@cityofboston/config-babel": "^0.0.0", - "@cityofboston/config-typescript": "^0.0.0", - "@cityofboston/graphql-typescript": "^0.0.0", - "@cityofboston/react-fleet": "^0.0.0", - "@cityofboston/storybook-common": "^0.0.0", - "@percy/storybook": "^3.0.2", - "@storybook/addon-a11y": "^5.0.10", - "@storybook/addon-actions": "^5.0.10", - "@storybook/addon-knobs": "^5.0.10", - "@storybook/addon-storyshots": "^5.0.10", - "@storybook/addon-viewport": "^5.0.10", - "@storybook/addons": "^5.0.10", - "@storybook/react": "^5.0.10", - "@types/graphql": "^14.2.0", - "@types/jest": "24.x.x", - "@types/moment-timezone": "^0.5.12", - "@types/node": "^8.0.0", - "@types/react": "^16.8.5", - "@types/react-dom": "^16.8.4", - "@types/storybook__addon-actions": "^3.4.2", - "@types/storybook__addon-storyshots": "^3.4.8", - "@types/storybook__react": "^4.0.1", - "@types/yar": "^9.0.4", - "@types/yup": "^0.26.0", - "babel-core": "^7.0.0-0", - "babel-plugin-inline-import": "^2.0.6", - "concurrently": "^3.5.1", - "cross-env": "^5.2.0", - "eslint-plugin-graphql": "^3.0.3", - "faker": "^4.1.0", - "jest": "^24.8.0", - "prettier": "^1.17.0", - "raw-loader": "^0.5.1", - "react-test-renderer": "16.8.5", - "rimraf": "^2.6.2", - "testcafe": "^1.5.0", - "ts-node": "^6.0.5", - "ts2gql": "CityOfBoston/ts2gql#4f10fcce", - "tsc-watch": "^1.0.26", - "typescript": "^3.5.1" - } -} diff --git a/services-js/access-boston/scripts/percy.js b/services-js/access-boston/scripts/percy.js deleted file mode 100755 index 67b1f6d52..000000000 --- a/services-js/access-boston/scripts/percy.js +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env node - -const { travisSnapshot } = require('@cityofboston/percy-common'); - -travisSnapshot({ - referenceBranch: 'develop', - project: 'CityOfBoston/Access-Boston', - packageName: 'access-boston', - buildStorybookOptions: '-s static', -}); diff --git a/services-js/access-boston/scripts/post-deploy.ts b/services-js/access-boston/scripts/post-deploy.ts deleted file mode 100644 index eef74fb69..000000000 --- a/services-js/access-boston/scripts/post-deploy.ts +++ /dev/null @@ -1,80 +0,0 @@ -// This is a script in the service, rather than a binary in deploy-tools, -// because binaries from our packages are not available in the container because -// of the order we do package.json / yarn install in the Dockerfile. - -/* eslint no-console: 0 */ -import path from 'path'; - -import { - uploadToS3, - parseBranch, - uploadSourceMapsToRollbar, - reportRollbarDeploy, -} from '@cityofboston/deploy-tools'; - -// Important for getting ASSET_HOST and ROLLBAR_ACCESS_TOKEN configuration. This -// script is run in the container from the default entrypoint, which would have -// downloaded the config files. -require('dotenv').config(); - -const NEXT_SRC_PATH = path.join('build', '.next'); -const NEXT_DST_PATH = '_next'; - -const { environment, serviceName } = parseBranch(process.env.DEPLOY_BRANCH!); -const bucketEnvironment = environment === 'production' ? 'prod' : 'staging'; -const bucket = `cob-digital-apps-${bucketEnvironment}-static`; - -(async function() { - console.error(`🛫 Uploading service ${serviceName} to ${bucket}…`); - console.error(); - - // Because all of Next’s JS is versioned, we don’t need to separate out these - // uploads by variant. We can just push everything and anything that needs - // separate file names will have it. - await uploadToS3( - NEXT_SRC_PATH, - bucket, - `${serviceName}/${NEXT_DST_PATH}`, - // 1 year cache expiration because all of Next’s JS is versioned - 60 * 60 * 24 * 365 - ); - - console.error(); - - console.error(`💅 Successfully uploaded ${serviceName} to ${bucket}.`); - - console.error(); - - const externalAssetUrl = process.env.ASSET_HOST - ? `https://${process.env.ASSET_HOST}/${serviceName}` - : `https://${process.env.PUBLIC_HOST}`; - - const rollbarAccessToken = process.env.ROLLBAR_ACCESS_TOKEN; - if (rollbarAccessToken) { - console.error(`🗺 Uploading source maps to Rollbar.`); - console.error(); - - await uploadSourceMapsToRollbar({ - rollbarAccessToken, - // These are scoped to "static" so that we don't waste time uploading the - // server maps to Rollbar. - dir: path.resolve(path.join(NEXT_SRC_PATH, 'static')), - baseUrl: `${externalAssetUrl}/${NEXT_DST_PATH}/static`, - version: process.env.GIT_REVISION || '', - }); - - console.error(`🍾 Reporting deploy to Rollbar.`); - console.error(); - - await reportRollbarDeploy( - rollbarAccessToken, - process.env.GIT_REVISION || 'HEAD' - ); - - console.error(`📮 post-deploy script successful!`); - console.error(); - } -})().catch(e => { - console.error(e); - process.exit(-1); -}); diff --git a/services-js/access-boston/src/client/CrumbContext.ts b/services-js/access-boston/src/client/CrumbContext.ts deleted file mode 100644 index 3d7b94a0b..000000000 --- a/services-js/access-boston/src/client/CrumbContext.ts +++ /dev/null @@ -1,6 +0,0 @@ -import React from 'react'; - -/** - * Context for the "crumb" XSRF token. - */ -export default React.createContext(''); diff --git a/services-js/access-boston/src/client/PageLayout.tsx b/services-js/access-boston/src/client/PageLayout.tsx deleted file mode 100644 index 6956f7b55..000000000 --- a/services-js/access-boston/src/client/PageLayout.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import React from 'react'; - -import { AppLayout } from '@cityofboston/react-fleet'; - -const PageLayout = props => { - return {props.children}; -}; - -export default PageLayout; diff --git a/services-js/access-boston/src/client/PageWrapper.tsx b/services-js/access-boston/src/client/PageWrapper.tsx deleted file mode 100644 index 1641b6839..000000000 --- a/services-js/access-boston/src/client/PageWrapper.tsx +++ /dev/null @@ -1,80 +0,0 @@ -/** @jsx jsx */ - -import { jsx, css } from '@emotion/core'; - -import { Component, ReactNode } from 'react'; - -import { ProgressBar } from '@cityofboston/react-fleet'; - -// import PageLayout from './PageLayout'; - -export interface Progress { - totalSteps: number; - currentStep: number; - currentStepCompleted: boolean; - offset?: number | undefined; -} - -interface Props { - progress?: Progress; - footer?: ReactNode; - classString?: string; - mainHeadline?: string; - offset?: number; -} - -/** - * Convenience component for freestanding pages. - */ -export default class PageWrapper extends Component { - render() { - const { - progress, - footer, - children, - classString, - mainHeadline, - offset, - } = this.props; - const sec2ndClassStr = footer ? 'b-c--nbp' : ''; - const classStr = - classString && classString.length > 0 - ? `${classString}${sec2ndClassStr}` - : `b-c--hsm ${sec2ndClassStr}`; - - const $mainHeadline = - mainHeadline && mainHeadline.length > 0 - ? mainHeadline - : `Identity Verification Process`; - - let modProgress = Object.assign({}, progress); - if (offset && typeof offset === 'number') { - modProgress.offset = offset; - } - - return ( - <> -
- {mainHeadline && mainHeadline.length > 0 && ( -

{$mainHeadline}

- )} - - {progress && ( -
- -
- )} - - {children} -
- - {footer} - - ); - } -} - -const PROGRESSBAR_WRAPPER_STYLING = css({ - marginTop: '1.5em', - marginBottom: '2.0em', -}); diff --git a/services-js/access-boston/src/client/RedirectForm.tsx b/services-js/access-boston/src/client/RedirectForm.tsx deleted file mode 100644 index 156255635..000000000 --- a/services-js/access-boston/src/client/RedirectForm.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import React from 'react'; -import CrumbContext from '../client/CrumbContext'; - -interface Props { - path: string; -} - -/** - * Makes a form that can be used to redirect to a page with a CSRF-protected - * POST. - * - * Save off a ref to this, and call "redirect" when you’re ready to go. - * - * You can also provide children that will be rendered in the form. This is - * useful for putting in a submit button. - */ -export default class RedirectForm extends React.Component { - private readonly formRef = React.createRef(); - - redirect() { - const form = this.formRef.current; - if (form) { - form.submit(); - } - } - - render() { - const { path, children } = this.props; - - return ( - - {crumb => ( -
- - {children} -
- )} -
- ); - } -} diff --git a/services-js/access-boston/src/client/StatusModal.tsx b/services-js/access-boston/src/client/StatusModal.tsx deleted file mode 100644 index b35452137..000000000 --- a/services-js/access-boston/src/client/StatusModal.tsx +++ /dev/null @@ -1,26 +0,0 @@ -/** @jsx jsx */ - -import { css, jsx } from '@emotion/core'; -import { ReactNode, ReactNodeArray } from 'react'; - -interface Props { - children?: ReactNode | ReactNodeArray; -} - -export default function StatusModal({ children }: Props) { - return ( -
-
-
{children}
-
-
- ); -} - -const MODAL_STYLE = css({ - paddingTop: 0, - maxWidth: 500, - top: '15%', - marginRight: 'auto', - marginLeft: 'auto', -}); diff --git a/services-js/access-boston/src/client/auth-helpers.ts b/services-js/access-boston/src/client/auth-helpers.ts deleted file mode 100644 index fac9ae818..000000000 --- a/services-js/access-boston/src/client/auth-helpers.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Account } from './graphql/fetch-account'; - -/** - * Throw this type of error out of getInitialProps to have the App component do - * a redirect rather than render the page. Works both client- and server-side. - */ -export class RedirectError extends Error { - url: string; - - constructor(url: string) { - super(); - - this.url = url; - } -} - -/** - * Ensures that the account has been registered. If not, throws a - * a RedirectError to the registration flow. - * - * Expected to be called from getInitialProps in page components. - */ -export function requireRegistration(account: Account) { - if (account.needsMfaDevice || account.needsNewPassword) { - throw new RedirectError('/register'); - } -} diff --git a/services-js/access-boston/src/client/common/AccessBostonFooter.stories.tsx b/services-js/access-boston/src/client/common/AccessBostonFooter.stories.tsx deleted file mode 100644 index 224f0bef6..000000000 --- a/services-js/access-boston/src/client/common/AccessBostonFooter.stories.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import React from 'react'; -import { storiesOf } from '@storybook/react'; - -import AccessBostonFooter from './AccessBostonFooter'; - -storiesOf('AccessBostonFooter', module).add('default', () => ( - -)); diff --git a/services-js/access-boston/src/client/common/AccessBostonFooter.tsx b/services-js/access-boston/src/client/common/AccessBostonFooter.tsx deleted file mode 100644 index 78ec425a7..000000000 --- a/services-js/access-boston/src/client/common/AccessBostonFooter.tsx +++ /dev/null @@ -1,46 +0,0 @@ -/** @jsx jsx */ - -import { css, jsx } from '@emotion/core'; - -import { OPTIMISTIC_BLUE_LIGHT, SANS } from '@cityofboston/react-fleet'; - -export default function AccessBostonFooter() { - return ( -
-
-
-
-
- The Access Boston Portal, and the systems, data and other - resources that require Access Boston authentication for access are - only to be used for legitimate City of Boston purposes. Use may be - monitored, and unauthorized access or improper use of the - resources may be subject to civil and/or criminal penalties under - applicable federal, state and/or local law. -
-
- -
-
-
- ); -} - -const FOOTER_LINK_STYLE = css({ - color: OPTIMISTIC_BLUE_LIGHT, - fontFamily: SANS, - textTransform: 'uppercase', -}); - -const FOOTER_LEGAL_STYLE = css({ - color: 'white', - fontSize: '90%', -}); diff --git a/services-js/access-boston/src/client/common/AccessBostonHeader.stories.tsx b/services-js/access-boston/src/client/common/AccessBostonHeader.stories.tsx deleted file mode 100644 index 844785237..000000000 --- a/services-js/access-boston/src/client/common/AccessBostonHeader.stories.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import React from 'react'; -import { storiesOf } from '@storybook/react'; - -import AccessBostonHeader from './AccessBostonHeader'; -import { Account } from '../graphql/fetch-account'; - -const ACCOUNT: Account = { - employeeId: 'CON01234', - firstName: 'Jyn', - lastName: 'Doe', - needsMfaDevice: false, - needsNewPassword: false, - hasMfaDevice: false, - resetPasswordToken: '', - mfaRequiredDate: null, - groups: [''], - email: '', -}; - -storiesOf('AccessBostonHeader', module).add('default', () => ( - -)); diff --git a/services-js/access-boston/src/client/common/AccessBostonHeader.tsx b/services-js/access-boston/src/client/common/AccessBostonHeader.tsx deleted file mode 100644 index 64cc35643..000000000 --- a/services-js/access-boston/src/client/common/AccessBostonHeader.tsx +++ /dev/null @@ -1,86 +0,0 @@ -/** @jsx jsx */ - -import { css, jsx } from '@emotion/core'; -import { Component } from 'react'; - -import Link from 'next/link'; - -import { CHARLES_BLUE, SANS, WHITE } from '@cityofboston/react-fleet'; - -import { Account } from '../graphql/fetch-account'; -import RedirectForm from '../RedirectForm'; - -interface Props { - account?: Account; - noLinks?: boolean; -} - -export default class AccessBostonHeader extends Component { - render() { - const { account, noLinks } = this.props; - // console.log('header > account: ', account); - - return ( -
-

- {noLinks ? ( - 'Access Boston' - ) : ( - - Access Boston - - )} -

- {account && ( -
- - {getEmployeeName(account)} - - - {!noLinks && ( - - - - )} -
- )} -
- ); - } -} - -function getEmployeeName({ firstName, lastName, employeeId }: Account): string { - if (firstName || lastName) { - return `${firstName || ''} ${lastName || ''}`.trim(); - } else { - return employeeId; - } -} - -const HEADER_STYLE = css({ - display: 'flex', - position: 'fixed', - top: 0, - left: 0, - right: 0, - flexDirection: 'row', - alignItems: 'center', - backgroundColor: CHARLES_BLUE, - color: WHITE, - zIndex: 2, -}); - -const HEADER_RIGHT_STYLE = css({ - marginLeft: 'auto', - display: 'flex', - alignItems: 'center', -}); - -const ACCESS_BOSTON_TITLE_STYLE = css({ - fontFamily: SANS, - textTransform: 'uppercase', - fontSize: '1.25rem', - fontWeight: 'bold', -}); diff --git a/services-js/access-boston/src/client/common/AppWrapper.stories.tsx b/services-js/access-boston/src/client/common/AppWrapper.stories.tsx deleted file mode 100644 index 91aefef03..000000000 --- a/services-js/access-boston/src/client/common/AppWrapper.stories.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import React from 'react'; - -import { storiesOf } from '@storybook/react'; - -import { Account } from '../graphql/fetch-account'; - -import AppWrapper from './AppWrapper'; - -const ACCOUNT: Account = { - employeeId: 'CON01234', - firstName: 'Jyn', - lastName: 'Doe', - needsMfaDevice: false, - needsNewPassword: false, - hasMfaDevice: false, - resetPasswordToken: '', - mfaRequiredDate: null, - groups: [''], - email: '', -}; - -storiesOf('Common/AppWrapper', module).add('default', () => ( - hi -)); diff --git a/services-js/access-boston/src/client/common/AppWrapper.tsx b/services-js/access-boston/src/client/common/AppWrapper.tsx deleted file mode 100644 index 2c64ed4c1..000000000 --- a/services-js/access-boston/src/client/common/AppWrapper.tsx +++ /dev/null @@ -1,43 +0,0 @@ -/** @jsx jsx */ - -import { css, jsx } from '@emotion/core'; - -import { ReactNode } from 'react'; - -import { Account } from '../graphql/fetch-account'; - -import AccessBostonHeader from './AccessBostonHeader'; -import AccessBostonFooter from './AccessBostonFooter'; - -import { MAIN_CLASS } from '../styles'; - -interface Props { - children: ReactNode; - account?: Account; - noLinks?: boolean; -} - -export default function AppWrapper(props: Props) { - return ( -
- - -
{props.children}
- - -
- ); -} - -const WRAPPER_STYLING = css({ - minHeight: '100vh', - display: 'flex', - flexDirection: 'column', - - '> main': { - flexGrow: 1, - - display: 'flex', - flexDirection: 'column', - }, -}); diff --git a/services-js/access-boston/src/client/common/BackButton.tsx b/services-js/access-boston/src/client/common/BackButton.tsx deleted file mode 100644 index e7984aa16..000000000 --- a/services-js/access-boston/src/client/common/BackButton.tsx +++ /dev/null @@ -1,42 +0,0 @@ -/** @jsx jsx */ - -import { css, jsx } from '@emotion/core'; - -import { MouseEvent } from 'react'; - -interface Props { - handleClick: (ev: MouseEvent) => void; -} - -/** - * “Back” button that looks like a text link. Right padding added to increase - * touch target size. - */ -export default function BackButton(props: Props): JSX.Element { - return ( - - ); -} - -export function BackButtonContent(): JSX.Element { - return ( - - {' '} - Back - - ); -} - -const BACK_BUTTON_STYLING = css({ - padding: '1.25rem !important', - paddingLeft: '0 !important', -}); diff --git a/services-js/access-boston/src/client/common/HelpContactInfo.tsx b/services-js/access-boston/src/client/common/HelpContactInfo.tsx deleted file mode 100644 index f2cd36a6b..000000000 --- a/services-js/access-boston/src/client/common/HelpContactInfo.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react'; - -export default function HelpContactInfo() { - return ( - - ); -} diff --git a/services-js/access-boston/src/client/common/PasswordPolicy.stories.tsx b/services-js/access-boston/src/client/common/PasswordPolicy.stories.tsx deleted file mode 100644 index 82e8011e9..000000000 --- a/services-js/access-boston/src/client/common/PasswordPolicy.stories.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import React from 'react'; -import { storiesOf } from '@storybook/react'; - -import PasswordPolicy from './PasswordPolicy'; - -storiesOf('PasswordPolicy', module) - .add('blank', () => ) - .add('long enough', () => ) - .add('complex enough', () => ) - .add('spaces error', () => ) - .add('failed are errors', () => ( - - )); diff --git a/services-js/access-boston/src/client/common/PasswordPolicy.tsx b/services-js/access-boston/src/client/common/PasswordPolicy.tsx deleted file mode 100644 index ee2f23ce0..000000000 --- a/services-js/access-boston/src/client/common/PasswordPolicy.tsx +++ /dev/null @@ -1,108 +0,0 @@ -import React from 'react'; -import { css } from 'emotion'; -import { FREEDOM_RED_DARK, DEFAULT_TEXT } from '@cityofboston/react-fleet'; - -import { analyzePassword } from '../../lib/validation'; - -// This needs to be equal or higher priority to '.ul>li:before', so ':before' -// didn't cut it. -const OVERRIDE_LI_BEFORE_SELECTOR = '.ul>&:before'; - -const OK_PASSWORD_ROW = css({ - // We set the text to default to override in case the parent is in error and - // set its color to red. - color: DEFAULT_TEXT, - [OVERRIDE_LI_BEFORE_SELECTOR]: { - border: 'none', - content: 'url("https://patterns.boston.gov/images/public/icons/check.svg")', - width: '.44444rem', - height: '.6666666rem', - position: 'relative', - top: -5, - left: -8, - transform: 'scale(.9)', - }, -}); - -const ERROR_PASSWORD_ROW = css({ - color: FREEDOM_RED_DARK, - [OVERRIDE_LI_BEFORE_SELECTOR]: { - // borderColor: `transparent ${FREEDOM_RED}`, - border: 'none', - content: '"×"', - fontSize: '1.75rem', - fontWeight: 'bold', - width: '.44444rem', - height: '.6666666rem', - position: 'relative', - top: '-1.05rem', - left: -5, - }, -}); - -interface Props { - password: string; - showFailedAsErrors?: boolean; -} - -export default function PasswordPolicy({ - password, - showFailedAsErrors, -}: Props): React.ReactElement { - const { - longEnough, - complexEnough, - hasLowercase, - hasUppercase, - hasNumber, - hasSymbol, - hasSpaces, - tooLong, - } = analyzePassword(password); - - const failedClassName = showFailedAsErrors ? ERROR_PASSWORD_ROW : ''; - - return ( - <> -
- New passwords must: -
- -
    -
  • - Be at least 10 characters long -
  • - -
  • - Use at least 3 of these: -
      -
    • - A lowercase letter -
    • -
    • - An uppercase letter -
    • -
    • A number
    • -
    • - A special character -
    • -
    -
  • - -
  • - Not have spaces -
  • -
  • - Not be longer than 32 characters -
  • -
- -
- Don't use personal info, like your name, ID or address. If you use just - two consecutive characters from your name or ID in your password, it - will fail. Your new password will have to be different than your last 5 - passwords. -
- - ); -} diff --git a/services-js/access-boston/src/client/common/QuestionComponent.tsx b/services-js/access-boston/src/client/common/QuestionComponent.tsx deleted file mode 100644 index d38848cc1..000000000 --- a/services-js/access-boston/src/client/common/QuestionComponent.tsx +++ /dev/null @@ -1,155 +0,0 @@ -/** @jsx jsx */ - -import { css, jsx } from '@emotion/core'; - -import { MouseEvent, ReactNode } from 'react'; - -import { MEDIA_SMALL /*, MEDIA_LARGE*/ } from '@cityofboston/react-fleet'; - -import BackButton from './BackButton'; - -interface Props { - handleProceed?: (ev: MouseEvent) => void; - allowProceed?: boolean; - handleStepBack?: (ev: MouseEvent) => void; - handleReset?: (ev: MouseEvent) => void; - handleQuit?: (ev: MouseEvent) => void; - startOver?: boolean; - nextButtonText?: string; - children: ReactNode; - quitBtn?: boolean; - squareStyleBackBtn?: boolean; -} - -/** - * Container component to provide layout for a single question screen, - * as well as “back”, “start over”, and “next question” buttons if their - * related handlers are passed in as props to this component. - */ -export default function QuestionComponent(props: Props): JSX.Element { - const { - children, - handleReset, - handleProceed, - handleStepBack, - nextButtonText, - allowProceed, - startOver, - quitBtn, - squareStyleBackBtn, - handleQuit, - } = props; - - const useG8Css = squareStyleBackBtn && quitBtn ? `8` : `6`; - const proceedBtnCss = `g--${useG8Css} ta-r m-b500`; - - return ( -
-
{children}
- -
- {!quitBtn && !squareStyleBackBtn && ( -
- {handleStepBack && } -
- )} - - {quitBtn && handleQuit && ( -
- -
- )} - - {squareStyleBackBtn && ( -
- -
- )} - -
- {handleProceed && !startOver && ( - - )} - - {/* Button only appears if handler was passed in AND startOver is true. */} - {handleReset && startOver && ( - - )} -
-
-
- ); -} - -const CONTAINER_STYLING = css({ - display: 'flex', - flexDirection: 'column', - lineHeight: '1.5rem', - margin: 'auto', - width: '100%', -}); - -const HEADER_STYLING = css({ - 'fieldset + fieldset': { - marginTop: '2rem', - - [MEDIA_SMALL]: { - marginTop: '4rem', - - '.lnk': { - paddingLeft: 0, - }, - }, - }, -}); - -const BUTTON_CONTAINER_STYLING = css({ - width: '100%', - marginLeft: 'auto', - marginRight: 'auto', - position: 'relative', - - textAlign: 'center', - - [MEDIA_SMALL]: { - marginTop: '2.5rem', - textAlign: 'left', - - '> div': { - display: 'flex', - - '&.ta-r > button': { - marginLeft: 'auto', - }, - }, - - '.lnk': { - paddingLeft: 0, - }, - }, -}); diff --git a/services-js/access-boston/src/client/common/TextInput.stories.tsx b/services-js/access-boston/src/client/common/TextInput.stories.tsx deleted file mode 100644 index 32185d3e8..000000000 --- a/services-js/access-boston/src/client/common/TextInput.stories.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import React from 'react'; -import { storiesOf } from '@storybook/react'; - -import TextInput, { renderErrorNextToInput } from './TextInput'; - -storiesOf('TextInput', module).add('variants', () => ( - <> - - - - - - - - -)); diff --git a/services-js/access-boston/src/client/common/TextInput.tsx b/services-js/access-boston/src/client/common/TextInput.tsx deleted file mode 100644 index 5d21d0ede..000000000 --- a/services-js/access-boston/src/client/common/TextInput.tsx +++ /dev/null @@ -1,113 +0,0 @@ -import React, { InputHTMLAttributes, ReactNode } from 'react'; -import hash from 'string-hash'; -import { css } from 'emotion'; - -interface Props extends InputHTMLAttributes { - label: string; - error?: string | boolean; - info?: ReactNode; - requiredlabelasterisk?: boolean | null; - /** - * This function can be used to customize how the input element and error - * message are rendered. For example, putting the error message next to the - * input element or adding in a submit button in the same row. - * - * This is done inside of TextInput because we often want vertical centering - * relative directly to the input element, rather than including the label or - * info. - * - * If you chose to render the error from this function you should set the - * hideErrorMessage prop, otherwise the component will render it as well. - */ - renderInputFunc?: (arg: { - inputEl: ReactNode; - errorEl: ReactNode; - }) => ReactNode; - hideErrorMessage?: boolean; - - id?: string; - type?: string; -} - -/** - * Stateless component for rendering a text-like form element. (Type of "text", - * "tel", "password", &c.) - * - * Automatically generates an id for the element if one is not provided. Also - * automatically adds the "txt-f" class to the element, and the "txt-f--err" - * class if there's an error. - * - * All props besides "label" and "error" are passed along as props for the - * element. - * - * TODO(finh): Merge this with the react-fleet TextInput. - */ -export default function TextInput(props: Props) { - const { - error, - required, - label, - info, - hideErrorMessage, - requiredlabelasterisk, - } = props; - const id = props.id || `input-${hash(label)}`; - - const inputProps = Object.assign({ id, type: 'text' }, props, { - className: `${props.className || ''} txt-f ${error ? 'txt-f--err' : ''}`, - }); - - const renderInputFunc = props.renderInputFunc || (({ inputEl }) => inputEl); - - // These aren't used for elements - delete inputProps.error; - delete inputProps.label; - delete inputProps.info; - delete inputProps.hideErrorMessage; - delete inputProps.renderInputFunc; - delete inputProps.requiredlabelasterisk; - - const inputEl = ; - const errorEl = ( -
- {/* The   is to keep space for the error so the form doesn't jump when one is added. */} - {error && typeof error === 'string' ? error : <> } -
- ); - - return ( -
- - - {renderInputFunc({ inputEl, errorEl })} - - {info &&
{info}
} - - {!hideErrorMessage && errorEl} -
- ); -} - -const ERROR_NEXT_TO_INPUT_CELL_STYLE = css({ - display: 'flex', - alignItems: 'center', -}); - -export const renderErrorNextToInput = ({ inputEl, errorEl }) => ( - <> -
-
{inputEl}
-
{errorEl}
-
- -); diff --git a/services-js/access-boston/src/client/confirmid/README.md b/services-js/access-boston/src/client/confirmid/README.md deleted file mode 100644 index a64ca540e..000000000 --- a/services-js/access-boston/src/client/confirmid/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# ID Verification Service - -## Workflow - -### Environments -- DEV -- TEST - - No SSN && DOB - - ID: 101226 - - DOB - - ID: - - SSN - - ID: 139562 - - - -etc -// 'Postman-Token': `${idver.postman}`, -// 'Accept-Encoding': 'gzip, deflate, br', -// 'Cache-Control': 'no-cache', -// 'User-Agent': 'PostmanRuntime/7.29.0', \ No newline at end of file diff --git a/services-js/access-boston/src/client/confirmid/components/Section.tsx b/services-js/access-boston/src/client/confirmid/components/Section.tsx deleted file mode 100644 index 2519afcb8..000000000 --- a/services-js/access-boston/src/client/confirmid/components/Section.tsx +++ /dev/null @@ -1,53 +0,0 @@ -/** @jsx jsx */ - -import { css, jsx } from '@emotion/core'; - -import { ReactNode } from 'react'; - -import { GRAY_000 } from '@cityofboston/react-fleet'; - -interface Props { - isGray?: boolean; - ariaLabel?: string; - stretch?: boolean; - children: ReactNode; - css?: any; -} - -/** - * Utility component to provide full-width background color as needed. - */ -export default function Section(props: Props) { - const ariaLabel = props.ariaLabel ? { 'aria-label': props.ariaLabel } : null; - - const stretchAttributes = css({ - flexGrow: 1, - display: 'flex', - flexDirection: 'column', - }); - - const prepCss = [ - { - backgroundColor: props.isGray ? GRAY_000 : undefined, - }, - props.stretch ? stretchAttributes : {}, - ]; - - return ( -
-
- {props.children} -
-
- ); -} diff --git a/services-js/access-boston/src/client/confirmid/helpers.ts b/services-js/access-boston/src/client/confirmid/helpers.ts deleted file mode 100644 index a725332d6..000000000 --- a/services-js/access-boston/src/client/confirmid/helpers.ts +++ /dev/null @@ -1,20 +0,0 @@ -export const isDateObj = (dateObj: Date | null) => { - return ( - dateObj && - Object.prototype.toString.call(dateObj) === '[object Date]' && - isNaN(dateObj.getDate()) === false - ); -}; - -export const formatDate = (dateObj: Date) => { - return `${dateObj.getMonth() + - 1}/${dateObj.getDate()}/${dateObj.getFullYear()}`; -}; - -export const fixUTCDate = (dateObj: Date) => { - return new Date( - +dateObj.getUTCFullYear().toString(), - +dateObj.getUTCMonth().toString(), - +dateObj.getUTCDate().toString() - ); -}; diff --git a/services-js/access-boston/src/client/confirmid/state/app.ts b/services-js/access-boston/src/client/confirmid/state/app.ts deleted file mode 100644 index bf6df8e93..000000000 --- a/services-js/access-boston/src/client/confirmid/state/app.ts +++ /dev/null @@ -1,81 +0,0 @@ -/* eslint no-console: 0 */ - -import { - View, - CommonAttributes, - IdentityVerificationRequestInformation, -} from '../types'; -import { getViews } from '../../storage/IdentityVerificationRequest'; - -import { isDateObj, formatDate } from '../helpers'; - -export const AppTitle: string = 'Confirm ID'; - -export type ActionTypes = - | 'APP/CHANGE_VIEW' - | 'APP/RESET_STATE' - | 'USER/UPDATE_ID' - | 'USER/UPDATE_USER'; - -interface Action { - type: ActionTypes; - view: View; - payload: CommonAttributes; -} - -export const initialState = new IdentityVerificationRequestInformation(); -export const completedStates = { - enterId: false, - validate: false, - review: false, - success: false, -}; - -export const newInitState = { - ...initialState, - // ...completedStates, -}; - -export const reducer = (state: any, action: Partial) => { - //@ts-ignore todo - const startingState = newInitState; - const fetchedViews: Array = getViews(); - - switch (action.type) { - case 'APP/CHANGE_VIEW': - if (action.view) { - return { ...state, view: fetchedViews.indexOf(action.view) }; - } else { - return { ...state, view: 0 }; - } - case 'APP/RESET_STATE': - return startingState; - case 'USER/UPDATE_ID': - return { - ...state, - employeeId: - action.payload && action.payload.employeeId - ? action.payload.employeeId - : '', - }; - case 'USER/UPDATE_USER': - if (action.payload) { - const dob = new Date(action.payload.dob); - const dobStr = isDateObj(dob) ? formatDate(dob) : ''; - - return { - ...state, - employeeId: action.payload.employeeId, - fname: action.payload.fname, - lname: action.payload.lname, - employeeType: action.payload.employeeType, - ssn: action.payload.ssn, - dob: dobStr, - }; - } else { - return state; - } - default: - return state; - } -}; diff --git a/services-js/access-boston/src/client/confirmid/types.ts b/services-js/access-boston/src/client/confirmid/types.ts deleted file mode 100644 index c86edf3d2..000000000 --- a/services-js/access-boston/src/client/confirmid/types.ts +++ /dev/null @@ -1,50 +0,0 @@ -export type IdentityVerificationStep = - | 'enterId' - | 'validate' - | 'review' - | 'success'; - -export type View = - | 'enterId' - | 'validate' - | 'review' - | 'success' - | 'failure' - | 'quit'; - -export type Action = '' | 'new'; - -export type stateType = { - step: number | null; - view: number; - - employeeId: string; - fname: string; - lname: string; - ssn: string; - dob: string; - employeeType: string; -}; - -export interface CommonAttributes { - step: number | null; - view: number; - employeeId: string; - fname: string; - lname: string; - ssn: string; - dob: string; - employeeType: string; -} - -export class IdentityVerificationRequestInformation - implements CommonAttributes { - step: number | null = null; - view: number = 0; - employeeId: string = ''; - fname: string = ''; - lname: string = ''; - ssn: string = ''; - dob: string = ''; - employeeType: string = ''; -} diff --git a/services-js/access-boston/src/client/confirmid/views/Index.stories.tsx b/services-js/access-boston/src/client/confirmid/views/Index.stories.tsx deleted file mode 100644 index df9626305..000000000 --- a/services-js/access-boston/src/client/confirmid/views/Index.stories.tsx +++ /dev/null @@ -1,125 +0,0 @@ -import React from 'react'; -import { storiesOf } from '@storybook/react'; - -import PageWrapper from '../../PageWrapper'; -// import InitialView from './initialView/InitialView'; -import EnterIdView from './enterId/EnterIdView'; -import ValidataView from './validate/ValidateView'; -import ReviewView from './review/ReviewView'; -import SuccessView from './success/SuccessView'; -import FailureView from './failure/FailureView'; -import QuitView from './quit/QuitView'; - -import { AppTitle } from '../state/app'; - -storiesOf('Confirm-Identity', module) - .add('EnterID', () => ( - -
- {}} - resetState={() => {}} - appTitle={AppTitle} - handleQuit={() => {}} - /> -
-
- )) - .add('Validate', () => ( - -
- {}} - handleStepBack={() => {}} - resetState={() => {}} - handleQuit={() => {}} - state={{ - employeeId: '40000093', - employeeType: 'EMPLOYEEE', - fname: 'Steve', - lname: 'Martin', - ssn: '1111', - dob: '1/1/1989', - }} - ssn={'1111'} - dob={'1/1/1989'} - updateSnn={() => {}} - updateDob={() => {}} - appTitle={AppTitle} - /> -
-
- )) - .add('Review', () => ( - -
- {}} - handleStepBack={() => {}} - resetState={() => {}} - state={{ - employeeId: '40000093', - employeeType: 'EMPLOYEEE', - fname: 'Steve', - lname: 'Martin', - ssn: '1111', - dob: '1/1/1989', - }} - ssn={'1111'} - dob={'1/1/1989'} - handleQuit={() => {}} - appTitle={AppTitle} - /> -
-
- )) - .add('Success', () => ( - -
- {}} appTitle={AppTitle} /> -
-
- )) - .add('Failure', () => ( - - {}} appTitle={AppTitle} /> - - )) - .add('Quit', () => ( - - {}} - handleReset={() => {}} - handleQuit={() => {}} - appTitle={AppTitle} - /> - - )); diff --git a/services-js/access-boston/src/client/confirmid/views/Index.tsx b/services-js/access-boston/src/client/confirmid/views/Index.tsx deleted file mode 100644 index e1aa9a77c..000000000 --- a/services-js/access-boston/src/client/confirmid/views/Index.tsx +++ /dev/null @@ -1,235 +0,0 @@ -/** @jsx jsx */ - -import { jsx } from '@emotion/core'; -import { useReducer, useState /*, useEffect */ } from 'react'; - -// LAYOUT Components -import PageWrapper from '../../PageWrapper'; - -// VIEWS (Components) -import EnterIdView from './enterId/EnterIdView'; -import ValidateView from './validate/ValidateView'; -import ReviewView from './review/ReviewView'; -import SuccessView from './success/SuccessView'; -import FailureView from './failure/FailureView'; -import QuitView from './quit/QuitView'; - -import { getViews, getSteps } from '../../storage/IdentityVerificationRequest'; -import { reducer as stateReducer, newInitState, AppTitle } from '../state/app'; - -// interface Props { -// groups: Array | null; -// } - -export default function Index() { - // const { groups } = props; - const [state, dispatchState] = useReducer(stateReducer, newInitState); - const [ssn, updateSnn] = useState(''); - const [dob, updateDob] = useState(''); - const fetchedSteps: Array = getSteps(); - const fetchedViews: Array = getViews(); - - // useEffect(() => { - // // Update the document title using the browser API - // if ( - // !groups || - // typeof groups !== 'object' || - // groups.length < 0 || - // groups.indexOf('SG_AB_CONFIRMID') === -1 - // ) { - // if (window) window.location.href = '/'; - // } - // }); - - const closeTab = () => { - if (window) window.close(); - }; - - const changeView = (newView: any) => - dispatchState({ type: 'APP/CHANGE_VIEW', view: newView }); - - const updateUserState = (data: any) => - dispatchState({ type: 'USER/UPDATE_USER', payload: data }); - - const resetState = (): void => { - dispatchState({ type: 'APP/RESET_STATE' }); - }; - - const stepBack = (): void => { - const prevView = state.view - 1; - - if (prevView > -1) { - changeView(fetchedViews[prevView]); - } else { - changeView(fetchedViews[0]); - } - }; - - const parseSailPointData = (data: any) => { - const dataRoot = data.Resources[0]; - const userStr = 'urn:ietf:params:scim:schemas:sailpoint:1.0:User'; - - return { - fname: dataRoot.name.givenName ? dataRoot.name.givenName : '', - lname: dataRoot.name.familyName ? dataRoot.name.familyName : '', - employeeId: dataRoot.userName, - employeeType: dataRoot[userStr].identity_type, - ssn: dataRoot[userStr].ssn ? dataRoot[userStr].ssn : '', - dob: dataRoot[userStr].cobDob ? dataRoot[userStr].cobDob : '', - }; - }; - - const advanceOnFetchEmployee = async (id: string) => { - const userData = await fetch(`/id-verification?id=${id}` as string, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - }, - }) - .then(response => response.json()) - .then(response => response); - - if (userData.Resources && userData.Resources.length > 0) { - const parseData = parseSailPointData(userData); - updateUserState(parseData); - advanceQuestion(); - } else { - changeView(fetchedViews[fetchedViews.length - 2]); - } - }; - - const advanceQuestion = () => { - const nextView = state.view + 1; - - if (nextView < fetchedViews.length) { - changeView(fetchedViews[nextView]); - } else { - changeView(fetchedViews[0]); - } - }; - - const verifyUser = () => { - if (state.employeeType === 'EMPLOYEE') { - if (state.ssn === ssn) { - advanceQuestion(); - } else { - changeView(fetchedViews[fetchedViews.length - 2]); - } - } else { - if (state.dob === dob) { - advanceQuestion(); - } else { - changeView(fetchedViews[fetchedViews.length - 2]); - } - } - }; - - const defaultView = ( - -
- -
-
- ); - - switch (fetchedViews[state.view]) { - case 'enterId': - return defaultView; - case 'validate': - return ( - -
- -
-
- ); - case 'review': - return ( - -
- -
-
- ); - case 'success': - return ( - -
- -
-
- ); - case 'failure': - return ( - - - - ); - case 'quit': - return ( - - changeView(fetchedViews[1])} - handleReset={resetState} - handleQuit={closeTab} - appTitle={AppTitle} - /> - - ); - default: - return defaultView; - } -} diff --git a/services-js/access-boston/src/client/confirmid/views/enterId/EnterIdView.tsx b/services-js/access-boston/src/client/confirmid/views/enterId/EnterIdView.tsx deleted file mode 100644 index 0ae0e22c1..000000000 --- a/services-js/access-boston/src/client/confirmid/views/enterId/EnterIdView.tsx +++ /dev/null @@ -1,104 +0,0 @@ -/** @jsx jsx */ - -import { css, jsx } from '@emotion/core'; -import { useEffect, useState } from 'react'; - -import { SectionHeader } from '@cityofboston/react-fleet'; -import Section from '../../components/Section'; - -import QuestionComponent from '../../../common/QuestionComponent'; - -import TextInput from '../../../common/TextInput'; - -interface Props { - handleProceed: any; - resetState: () => void; - appTitle: string; - handleQuit: any; -} - -export default function EnterIdView(props: Props) { - const { handleProceed, handleQuit } = props; - const [query, setQuery] = useState(''); - const [value, setValue] = useState(''); - - useEffect(() => { - const timeOutId = setTimeout(() => setValue(query), 300); - return () => clearTimeout(timeOutId); - }, [query]); - - const isComplete = () => { - let retVal: boolean = false; - - if (value && value.length > 3) retVal = true; - - return retVal; - }; - - const handle_proceed = () => { - return handleProceed(value); - }; - - return ( - -
- -
Instructions
- -
- Please enter the Employee ID or User ID number of the person to be - verified. -
- -
- setQuery(e.target.value)} - /> -
-
-
- ); -} - -const SECTION_STYLING = css({ - color: 'black', -}); - -const SECTIONHEADER_STYLING = css({ - marginBottom: '3em', -}); - -const SUBHEADER_STYLING = css({ - color: 'black', - fontSize: '20px', - fontWeight: 'bold', - fontFamily: 'Montserrat', - textTransform: 'uppercase', -}); - -const INSTRUCTIONS_STYLING = css({ - fontSize: '15px', - fontFamily: 'Lora', - color: 'black', - marginBottom: '1.5em', -}); - -const TEXTINPUT_STYLING = css({ - color: 'red', - '.txt label span.t--req': { - color: '#fb4d42', - marginLeft: '1em', - }, -}); diff --git a/services-js/access-boston/src/client/confirmid/views/failure/FailureView.tsx b/services-js/access-boston/src/client/confirmid/views/failure/FailureView.tsx deleted file mode 100644 index 42583186b..000000000 --- a/services-js/access-boston/src/client/confirmid/views/failure/FailureView.tsx +++ /dev/null @@ -1,63 +0,0 @@ -/** @jsx jsx */ - -import { css, jsx } from '@emotion/core'; - -import Section from '../../components/Section'; -import { SectionHeader } from '@cityofboston/react-fleet'; -import QuestionComponent from '../../../../client/common/QuestionComponent'; - -interface Props { - handleProceed: () => void; - appTitle: string; -} - -export default function SuccessView(props: Props) { - return ( - -
- -

- The identity verification process was unsuccessful. -

- -

- If the person is an Employee, please ask - them to reach out to their Human Resources representative to confirm - that their data is correct. -

- -

- If the person has a Sponsored account, - please ask them to reach out to their Sponsor to confirm that their - data is correct. -

-
-
- ); -} - -const CONTENT_STYLING = css({ - p: { - fontSize: '1.5em', - color: 'red', - }, -}); - -const ERROR_CONTENT_STYLING = css({ - color: '#FB4D42', - fontWeight: 'bold', - margin: '0.5em 0 0', -}); - -const BOLD_TEXT = css({ - fontWeight: 'bold', - fontStyle: 'italic', -}); - -const PARAGRAPH_STYLING = css({ - margin: '1em 0 0', -}); diff --git a/services-js/access-boston/src/client/confirmid/views/initialView/InitialView.tsx b/services-js/access-boston/src/client/confirmid/views/initialView/InitialView.tsx deleted file mode 100644 index 5d118c04d..000000000 --- a/services-js/access-boston/src/client/confirmid/views/initialView/InitialView.tsx +++ /dev/null @@ -1,65 +0,0 @@ -/** @jsx jsx */ - -import { css, jsx } from '@emotion/core'; - -import { SectionHeader } from '@cityofboston/react-fleet'; -import Section from '../../components/Section'; -import QuestionComponent from '../../../../client/common/QuestionComponent'; - -interface Props { - handleProceed: () => void; - resetState: () => void; - appTitle: string; -} - -export default function InitialView(props: Props) { - return ( - {}} - allowProceed={true} - nextButtonText={'Begin Verification'} - > -
-
- Access Boston -
- - -

- Welcome to Access Boston, the City of Boston's ID Verification - Service. -

- -

- Please try to have the person go through the "Forgot Password" - function before performing the "Identity Verification" process. Using - this process means that not only will their password be reset, but any - devices they had registered for Multi-Factor Authentication will be - cleared from their account. At the end of this process, they will have - to re-do the full Access Boston registration process. -

-
-
- ); -} - -const LOGO_STYLING = css({ - display: 'flex', - justifyContent: 'center', - maxHeight: '150px', - marginBottom: '2.25em', - - img: { - height: '130px', - }, -}); - -const CONTENT_STYLING = css({ - p: { - fontSize: '1.5em', - }, -}); diff --git a/services-js/access-boston/src/client/confirmid/views/quit/QuitView.tsx b/services-js/access-boston/src/client/confirmid/views/quit/QuitView.tsx deleted file mode 100644 index 9afe5ea27..000000000 --- a/services-js/access-boston/src/client/confirmid/views/quit/QuitView.tsx +++ /dev/null @@ -1,73 +0,0 @@ -/** @jsx jsx */ - -import { css, jsx } from '@emotion/core'; - -// import { View } from '../types'; - -import { SectionHeader } from '@cityofboston/react-fleet'; -import Section from '../../components/Section'; - -import QuestionComponent from '../../../../client/common/QuestionComponent'; - -interface Props { - handleProceed: any; - handleReset: any; - handleQuit: any; - appTitle: string; -} - -export default function SuccessView(props: Props) { - const { handleProceed, handleQuit, handleReset } = props; - - return ( - -
-
- Access Boston -
- - -

- Are you sure you want to quit the Identify Verification Process? -

- -

- All unsaved changes will be lost and the process will have to be - restarted. -

-
-
- ); -} - -const LOGO_STYLING = css({ - display: 'flex', - justifyContent: 'center', - maxHeight: '150px', - marginBottom: '2.25em', - - img: { - height: '130px', - }, -}); - -const CONTENT_STYLING = css({ - p: { - fontSize: '1.5em', - color: 'red', - }, -}); - -const PARAGRAPH_STYLING = css({ - margin: '0.25em 0 0', -}); diff --git a/services-js/access-boston/src/client/confirmid/views/review/ReviewView.tsx b/services-js/access-boston/src/client/confirmid/views/review/ReviewView.tsx deleted file mode 100644 index d00500762..000000000 --- a/services-js/access-boston/src/client/confirmid/views/review/ReviewView.tsx +++ /dev/null @@ -1,128 +0,0 @@ -/** @jsx jsx */ - -import { css, jsx } from '@emotion/core'; - -import { SectionHeader, MemorableDateInput } from '@cityofboston/react-fleet'; -import Section from '../../components/Section'; - -import QuestionComponent from '../../../common/QuestionComponent'; - -import TextInput from '../../../common/TextInput'; - -interface Props { - handleProceed: () => void; - handleStepBack: () => void; - resetState: () => void; - handleQuit?: any; - state: any; - ssn: string; - dob: string; - appTitle: string; -} - -export default function ReviewView(props: Props) { - const { handleProceed, handleStepBack, handleQuit, state, ssn, dob } = props; - - const isComplete = () => { - let retVal: boolean = true; - - return retVal; - }; - - const employeeLabel = 'Last 4 Digits of the SSN'; - const contractorLabel = 'Data of Birth (MM/DD/YYYY)'; - const verifyInputLabel = - state.employeeType === 'EMPLOYEE' ? employeeLabel : contractorLabel; - - return ( - {}} - allowProceed={isComplete()} - nextButtonText={'Submit'} - squareStyleBackBtn={true} - handleQuit={handleQuit} - quitBtn={true} - > -
- -
Review Details
- -
- Please review the details below with the person to confirm. -
- -
- - - - - {/* --------------------------------- */} - {state.employeeType !== 'EMPLOYEE' && ( - {}} - disabled={true} - /> - )} - - {state.employeeType === 'EMPLOYEE' && ( - - )} -
-
-
- ); -} - -const SECTION_STYLING = css({ - color: 'black', -}); - -const SECTIONHEADER_STYLING = css({ - marginBottom: '3em', -}); - -const SUBHEADER_STYLING = css({ - color: 'black', - fontSize: '20px', - fontWeight: 'bold', - fontFamily: 'Montserrat', - textTransform: 'uppercase', -}); - -const VALIDATE_STYLING = css({ - fontSize: '15px', - fontFamily: 'Lora', - color: 'black', - marginBottom: '1.5em', -}); - -const TEXTINPUT_STYLING = css({ - color: 'red', - '.txt label span.t--req': { - color: '#fb4d42', - marginLeft: '1em', - }, - 'input:disabled': { - background: '#E9E9E9 0% 0% no-repeat padding-box', - }, -}); diff --git a/services-js/access-boston/src/client/confirmid/views/success/SuccessView.tsx b/services-js/access-boston/src/client/confirmid/views/success/SuccessView.tsx deleted file mode 100644 index 83cea3373..000000000 --- a/services-js/access-boston/src/client/confirmid/views/success/SuccessView.tsx +++ /dev/null @@ -1,44 +0,0 @@ -/** @jsx jsx */ - -import { css, jsx } from '@emotion/core'; - -import { SectionHeader } from '@cityofboston/react-fleet'; -import Section from '../../components/Section'; - -import QuestionComponent from '../../../../client/common/QuestionComponent'; - -interface Props { - handleProceed: () => void; - appTitle: string; -} - -export default function SuccessView(props: Props) { - return ( - -
- -

- The identity verification process was successful. -

- -

Please proceed with password reset activities.

-
-
- ); -} - -const CONTENT_STYLING = css({ - p: { - fontSize: '1.5em', - }, -}); - -const SUCCESS_CONTENT_STYLING = css({ - color: '#56A958', - fontWeight: 'bold', - margin: '0.5em 0 0', -}); diff --git a/services-js/access-boston/src/client/confirmid/views/validate/ValidateView.tsx b/services-js/access-boston/src/client/confirmid/views/validate/ValidateView.tsx deleted file mode 100644 index 413dccb3b..000000000 --- a/services-js/access-boston/src/client/confirmid/views/validate/ValidateView.tsx +++ /dev/null @@ -1,181 +0,0 @@ -/** @jsx jsx */ - -import { css, jsx } from '@emotion/core'; -import { useEffect, useState } from 'react'; - -// import { View } from '../types'; - -import { SectionHeader, MemorableDateInput } from '@cityofboston/react-fleet'; -import Section from '../../components/Section'; - -import QuestionComponent from '../../../common/QuestionComponent'; - -import TextInput from '../../../common/TextInput'; - -import { isDateObj, formatDate, fixUTCDate } from '../../helpers'; - -interface Props { - handleProceed: () => void; - handleStepBack: () => void; - resetState: () => void; - handleQuit: any; - state: any; - ssn: string; - dob: string; - updateSnn: any; - updateDob: any; - appTitle: string; -} - -export default function ValidateView(props: Props) { - const { - handleProceed, - handleStepBack, - state, - updateSnn, - updateDob, - handleQuit, - } = props; - const [query, setQuery] = useState(''); - const [value, setValue] = useState(''); - - useEffect(() => { - const timeOutId = setTimeout(() => setValue(query), 300); - return () => clearTimeout(timeOutId); - }, [query]); - - const handleDateChange = (newDate): void => { - const isDate = isDateObj(newDate); - - if (isDate) { - setValue(formatDate(fixUTCDate(newDate))); - } - }; - - const isComplete = () => { - let retVal: boolean = false; - - if (value && value.length > 3) retVal = true; - - if (state.employeeType === 'EMPLOYEE' && value && value.length === 4) { - retVal = true; - updateSnn(value); - } else { - if (isDateObj(new Date(value))) { - retVal = true; - updateDob(value); - } - } - - return retVal; - }; - - const employeeLabel = 'Last 4 Digits of the SSN'; - const contractorLabel = 'Data of Birth (MM/DD/YYYY)'; - const verifyInputLabel = - state.employeeType === 'EMPLOYEE' ? employeeLabel : contractorLabel; - const instructionsElem = () => { - if (state.employeeType === 'EMPLOYEE') { - return ( - <> - Confirm the name displayed matches the one the caller is providing, - then enter the last 4 digits of their SSN. - - ); - } else { - return ( - <> - Confirm the name displayed matches the one the caller is providing, - then enter their Date of Birth. - - ); - } - }; - - return ( - -
- -
Validate
- -
{instructionsElem()}
- -
- - - - - {/* --------------------------------- */} - {state.employeeType !== 'EMPLOYEE' && ( - - )} - - {state.employeeType === 'EMPLOYEE' && ( - setQuery(e.target.value)} - /> - )} -
-
-
- ); -} - -const SECTION_STYLING = css({ - color: 'black', -}); - -const SECTIONHEADER_STYLING = css({ - marginBottom: '3em', -}); - -const SUBHEADER_STYLING = css({ - color: 'black', - fontSize: '20px', - fontWeight: 'bold', - fontFamily: 'Montserrat', - textTransform: 'uppercase', -}); - -const VALIDATE_STYLING = css({ - fontSize: '15px', - fontFamily: 'Lora', - color: 'black', - marginBottom: '1.5em', -}); - -const TEXTINPUT_STYLING = css({ - color: 'red', - '.txt label span.t--req': { - color: '#fb4d42', - marginLeft: '1em', - }, - 'input:disabled': { - background: '#E9E9E9 0% 0% no-repeat padding-box', - }, -}); diff --git a/services-js/access-boston/src/client/device-verification/DeviceVerificationForm.stories.tsx b/services-js/access-boston/src/client/device-verification/DeviceVerificationForm.stories.tsx deleted file mode 100644 index f8d8c952d..000000000 --- a/services-js/access-boston/src/client/device-verification/DeviceVerificationForm.stories.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import React from 'react'; -import { storiesOf } from '@storybook/react'; -import DeviceVerificationForm, { FormValues } from './DeviceVerificationForm'; -import { action } from '@storybook/addon-actions'; - -const DEFAULT_VALUES: FormValues = { - phoneOrEmail: 'phone', - email: '', - phoneNumber: '', - smsOrVoice: 'sms', -}; - -const DEFAULT_PROPS = { - values: DEFAULT_VALUES, - errors: {}, - touched: {}, - handleSubmit: action('handleSubmit'), - handleChange: action('handleChange'), - handleBlur: action('handleBlur'), - isSubmitting: false, - isValid: true, - serverError: null, - phoneOrEmail: 'phone' as 'phone', -}; - -storiesOf('RegisterMfaPage/DeviceVerificationForm', module) - .add('phone', () => ) - .add('email with error', () => ( - - )) - .add('code sending error', () => ( - - )); diff --git a/services-js/access-boston/src/client/device-verification/DeviceVerificationForm.tsx b/services-js/access-boston/src/client/device-verification/DeviceVerificationForm.tsx deleted file mode 100644 index a9c4bbb2d..000000000 --- a/services-js/access-boston/src/client/device-verification/DeviceVerificationForm.tsx +++ /dev/null @@ -1,183 +0,0 @@ -import React from 'react'; -import { FormikProps } from 'formik'; -import Link from 'next/link'; - -import { SectionHeader, RadioGroup } from '@cityofboston/react-fleet'; - -import TextInput, { renderErrorNextToInput } from '../common/TextInput'; - -// Pull of only the FormikProps we actually use. This keeps us from needing to -// pass fake versions of everything in our Storybook stories. -type Props = Pick< - FormikProps, - | 'values' - | 'errors' - | 'touched' - | 'handleSubmit' - | 'handleChange' - | 'handleBlur' - | 'isValid' - | 'isSubmitting' -> & - AdditionalProps; - -interface AdditionalProps { - serverError: string | null; -} - -export interface FormValues { - // Even though this value is only changed by the query parameter to the mfa - // page, and not through a form field, we keep it in Formik so that it can be - // referenced by the validation rules. - phoneOrEmail: 'phone' | 'email'; - smsOrVoice: string; - email: string; - phoneNumber: string; -} - -/** - * Formik form component to render the UI for adding a phone or email address as - * an MFA device. - * - * Factored out of mfa.tsx page so that it’s easier to test all of its states by - * passing in props. - */ -export default function DeviceVerificationForm(props: Props) { - const { - values: { phoneOrEmail, smsOrVoice, email, phoneNumber }, - errors, - touched, - handleSubmit, - handleChange, - handleBlur, - isValid, - isSubmitting, - serverError, - } = props; - - return ( - <> -
- - -

- Access Boston will send you a security code when you log in on a new - computer, tablet, or phone. You’ll also need a code to reset your - password if you forget it. -

- -

- This is called multi-factor authentication. It keeps your account - secure even if someone steals your password. -

- -

- Use your cell phone number if you have one. This is the most secure - option. If you don’t have a phone, you can use a personal email - address instead. -

- -
- -
- {phoneOrEmail === 'phone' && ( - <> - - You should use your cell phone number if you have one. -
- Note: normal cell phone charges will apply. - - } - renderInputFunc={renderErrorNextToInput} - hideErrorMessage - /> - - - Text message — recommended - - ), - value: 'sms', - }, - { - label: 'Phone call', - value: 'voice', - }, - ]} - /> - - )} - - {phoneOrEmail === 'email' && ( - <> - - -
- Switch to{' '} - - get codes via phone call or text - -
- - )} - - {serverError && ( -
{serverError}
- )} - -
- -
- - {phoneOrEmail === 'phone' && ( -
- Don’t have access to a phone? -
- - Get codes via personal email - -
- )} - -
- - ); -} diff --git a/services-js/access-boston/src/client/device-verification/DeviceVerificationModal.stories.tsx b/services-js/access-boston/src/client/device-verification/DeviceVerificationModal.stories.tsx deleted file mode 100644 index ff4f0f769..000000000 --- a/services-js/access-boston/src/client/device-verification/DeviceVerificationModal.stories.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import React from 'react'; -import { storiesOf } from '@storybook/react'; - -import DeviceVerificationModal, { - VerificationStatus, -} from './DeviceVerificationModal'; -import { action } from '@storybook/addon-actions'; -import { VerificationType } from '../graphql/queries'; - -const resendVerification = action('resendVerification'); -const resetVerification = action('resetVerification'); - -const validateCode = (...args) => - action('validateCode')(args) || Promise.resolve(true); - -storiesOf('RegisterMfaPage/DeviceVerificationModal', module) - .add('sending', () => ( - - )) - .add('waiting for email code', () => ( - - )) - .add('waiting for SMS code', () => ( - - )) - .add('waiting for voice code', () => ( - - )) - .add('checking the code', () => ( - - )) - .add('incorrect code', () => ( - - )) - .add('other error', () => ( - - )); diff --git a/services-js/access-boston/src/client/device-verification/DeviceVerificationModal.tsx b/services-js/access-boston/src/client/device-verification/DeviceVerificationModal.tsx deleted file mode 100644 index 898e64cd1..000000000 --- a/services-js/access-boston/src/client/device-verification/DeviceVerificationModal.tsx +++ /dev/null @@ -1,229 +0,0 @@ -import React, { ReactNode } from 'react'; -import { css } from 'emotion'; -import { Formik, FormikProps } from 'formik'; -import * as yup from 'yup'; - -import { VerificationType } from '../graphql/queries'; -import StatusModal from '../StatusModal'; -import TextInput from '../common/TextInput'; -import { SANS } from '@cityofboston/react-fleet'; - -export enum VerificationStatus { - NONE, - SENDING, - SENT, - CHECKING, - INCORRECT_CODE, - OTHER_ERROR, - ALREADY_REGISTERED, -} - -interface Props { - status: VerificationStatus; - - type: VerificationType | null; - email: string | null; - phoneNumber: string | null; - - resendVerification: () => any; - resetVerification: () => any; - validateCode: (code: string) => any; - - testCode?: string; -} - -const VERIFICATION_CODE_ROW_STYLE = css({ - display: 'flex', - alignItems: 'center', -}); - -const VERIFICATION_CODE_INPUT_STYLE = css({ - fontSize: '30px', - letterSpacing: '3px', - fontFamily: SANS, - fontWeight: 'bold', -}); - -export default class DeviceVerificationModal extends React.Component { - render() { - const { status } = this.props; - - return ( - - {status !== VerificationStatus.SENDING && this.renderEnterCode()} - {status === VerificationStatus.SENDING && ( -
We’re sending you a security code…
- )} -
- ); - } - - renderEnterCode() { - const { - status, - type, - email, - phoneNumber, - validateCode, - testCode, - } = this.props; - - let header: ReactNode = null; - - if (status === VerificationStatus.OTHER_ERROR) { - return this.renderGenericError(); - } - - if (status === VerificationStatus.INCORRECT_CODE) { - header = ( - - That code didn’t seem right. Can you try again? - - ); - } else if (status === VerificationStatus.ALREADY_REGISTERED) { - header = ( - - Your account already has a device registered. - - ); - } else if (type === VerificationType.EMAIL) { - header = ( - <> - Check your inbox! We’ve sent an email to {email}. - - ); - } else if (type === VerificationType.SMS) { - header = ( - <> - Check your phone! We’ve sent a text to{' '} - {elidePhoneNumber(phoneNumber)}. - - ); - } else if (type === VerificationType.VOICE) { - header = ( - <> - Please pick up! We’re making a phone call to{' '} - {elidePhoneNumber(phoneNumber)}. - - ); - } - - return ( - <> -
{header}
- - { - await validateCode(code); - // Clears out the input values. Useful in case the code is incorrect. - resetForm(); - }} - render={this.renderFormContents} - /> - - ); - } - - renderGenericError() { - const { resendVerification, resetVerification } = this.props; - - return ( - <> -
- Something went wrong. -
- -
- We had a problem verifying that code. You can try to{' '} - {' '} - or{' '} - - . -
- -
- Please get in touch with the helpdesk if this keeps happening. -
- - ); - } - - renderFormContents = ({ - values: { code }, - handleChange, - handleSubmit, - isValid, - }: FormikProps<{ code: string }>) => { - const { status, resendVerification, resetVerification } = this.props; - - return ( -
-
- ( -
- {inputEl} - -
- )} - /> -
- -
- Didn’t get it?{' '} - {' '} - or{' '} - - . -
-
- ); - }; -} - -function elidePhoneNumber(phoneNumber: string | null): string { - const match = phoneNumber && phoneNumber.trim().match(/\d\d$/); - if (match) { - return `(xxx) xxx-xx${match[0]}`; - } else { - return ''; - } -} diff --git a/services-js/access-boston/src/client/graphql/add-mfa-device.ts b/services-js/access-boston/src/client/graphql/add-mfa-device.ts deleted file mode 100644 index 1ffc2b1ca..000000000 --- a/services-js/access-boston/src/client/graphql/add-mfa-device.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { FetchGraphql, gql } from '@cityofboston/next-client-common'; -import { - AddMfaDeviceVariables, - AddMfaDevice, - AddMfaDevice_addMfaDevice, -} from './queries'; - -const QUERY = gql` - mutation AddMfaDevice( - $phoneNumber: String - $email: String - $type: VerificationType! - ) { - addMfaDevice(phoneNumber: $phoneNumber, email: $email, type: $type) { - sessionId - error - } - } -`; - -// Renames just to not expose the GraphQL-generated type names. -export type AddMfaDeviceArgs = Required; -export interface AddMfaDeviceResult extends AddMfaDevice_addMfaDevice {} - -export default async function addMfaDevice( - fetchGraphql: FetchGraphql, - args: AddMfaDeviceArgs -): Promise { - return ((await fetchGraphql(QUERY, args)) as AddMfaDevice).addMfaDevice; -} diff --git a/services-js/access-boston/src/client/graphql/change-password.ts b/services-js/access-boston/src/client/graphql/change-password.ts deleted file mode 100644 index 0815b8ee9..000000000 --- a/services-js/access-boston/src/client/graphql/change-password.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { FetchGraphql, gql } from '@cityofboston/next-client-common'; -import { ChangePassword, ChangePasswordVariables } from './queries'; - -const QUERY = gql` - mutation ChangePassword( - $currentPassword: String! - $newPassword: String! - $confirmPassword: String! - ) { - changePassword( - currentPassword: $currentPassword - newPassword: $newPassword - confirmPassword: $confirmPassword - ) { - caseId - status - error - messages - } - } -`; - -export default async function changePassword( - fetchGraphql: FetchGraphql, - currentPassword, - newPassword, - confirmPassword -) { - const args: ChangePasswordVariables = { - currentPassword, - newPassword, - confirmPassword, - }; - - return ((await fetchGraphql(QUERY, args)) as ChangePassword).changePassword; -} diff --git a/services-js/access-boston/src/client/graphql/fetch-account-and-apps.ts b/services-js/access-boston/src/client/graphql/fetch-account-and-apps.ts deleted file mode 100644 index f151c51e0..000000000 --- a/services-js/access-boston/src/client/graphql/fetch-account-and-apps.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { gql, FetchGraphql } from '@cityofboston/next-client-common'; -import { - FetchAccountAndApps, - FetchAccountAndApps_apps_categories_apps, -} from './queries'; - -export type Account = FetchAccountAndApps['account']; -export type Apps = FetchAccountAndApps['apps']; -export type Notice = FetchAccountAndApps['notice']; -export type CategoryApps = Array; - -const QUERY = gql` - query FetchAccountAndApps { - account { - employeeId - firstName - lastName - needsMfaDevice - needsNewPassword - hasMfaDevice - resetPasswordToken - mfaRequiredDate - groups - email - } - - notice { - label - pretext - text - } - - apps { - categories { - title - showIcons - requestAccessUrl - apps { - title - url - iconUrl - description - target - } - } - } - } -`; - -// We have the req because we need to do cookie auth to get the account info. -export default async function fetchAccountAndApps( - fetchGraphql: FetchGraphql -): Promise { - return await fetchGraphql(QUERY); -} diff --git a/services-js/access-boston/src/client/graphql/fetch-account.ts b/services-js/access-boston/src/client/graphql/fetch-account.ts deleted file mode 100644 index b3221f26b..000000000 --- a/services-js/access-boston/src/client/graphql/fetch-account.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { gql, FetchGraphql } from '@cityofboston/next-client-common'; -import { FetchAccount } from './queries'; - -export type Account = FetchAccount['account']; - -const QUERY = gql` - query FetchAccount { - account { - employeeId - firstName - lastName - needsMfaDevice - needsNewPassword - hasMfaDevice - resetPasswordToken - mfaRequiredDate - groups - email - } - } -`; - -export default async function fetchAccount(fetchGraphql: FetchGraphql) { - return ((await fetchGraphql(QUERY)) as FetchAccount).account; -} diff --git a/services-js/access-boston/src/client/graphql/queries.ts b/services-js/access-boston/src/client/graphql/queries.ts deleted file mode 100644 index a7a7cd36c..000000000 --- a/services-js/access-boston/src/client/graphql/queries.ts +++ /dev/null @@ -1,210 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ -// @generated -// This file was automatically generated and should not be edited. - -// ==================================================== -// GraphQL mutation operation: AddMfaDevice -// ==================================================== - -export interface AddMfaDevice_addMfaDevice { - sessionId: string | null; - error: MfaError | null; -} - -export interface AddMfaDevice { - addMfaDevice: AddMfaDevice_addMfaDevice; -} - -export interface AddMfaDeviceVariables { - phoneNumber?: string | null; - email?: string | null; - type: VerificationType; -} - -/* tslint:disable */ -/* eslint-disable */ -// @generated -// This file was automatically generated and should not be edited. - -// ==================================================== -// GraphQL mutation operation: ChangePassword -// ==================================================== - -export interface ChangePassword_changePassword { - caseId: string | null; - status: WorkflowStatus; - error: string | null; - messages: string[]; -} - -export interface ChangePassword { - changePassword: ChangePassword_changePassword; -} - -export interface ChangePasswordVariables { - currentPassword: string; - newPassword: string; - confirmPassword: string; -} - -/* tslint:disable */ -/* eslint-disable */ -// @generated -// This file was automatically generated and should not be edited. - -// ==================================================== -// GraphQL query operation: FetchAccountAndApps -// ==================================================== - -export interface FetchAccountAndApps_account { - employeeId: string; - firstName: string | null; - lastName: string | null; - needsMfaDevice: boolean; - needsNewPassword: boolean; - hasMfaDevice: boolean; - resetPasswordToken: string; - mfaRequiredDate: string | null; - groups: string[] | null; - email: string; -} - -export interface FetchAccountAndApps_notice { - label: string; - pretext: string; - text: string; -} - -export interface FetchAccountAndApps_apps_categories_apps { - title: string; - url: string; - iconUrl: string | null; - description: string; - target: string | null; -} - -export interface FetchAccountAndApps_apps_categories { - title: string; - showIcons: boolean; - requestAccessUrl: string | null; - apps: FetchAccountAndApps_apps_categories_apps[]; -} - -export interface FetchAccountAndApps_apps { - categories: FetchAccountAndApps_apps_categories[]; -} - -export interface FetchAccountAndApps { - account: FetchAccountAndApps_account; - notice: FetchAccountAndApps_notice; - apps: FetchAccountAndApps_apps; -} - -/* tslint:disable */ -/* eslint-disable */ -// @generated -// This file was automatically generated and should not be edited. - -// ==================================================== -// GraphQL query operation: FetchAccount -// ==================================================== - -export interface FetchAccount_account { - employeeId: string; - firstName: string | null; - lastName: string | null; - needsMfaDevice: boolean; - needsNewPassword: boolean; - hasMfaDevice: boolean; - resetPasswordToken: string; - mfaRequiredDate: string | null; - groups: string[] | null; - email: string; -} - -export interface FetchAccount { - account: FetchAccount_account; -} - -/* tslint:disable */ -/* eslint-disable */ -// @generated -// This file was automatically generated and should not be edited. - -// ==================================================== -// GraphQL mutation operation: ResetPassword -// ==================================================== - -export interface ResetPassword_resetPassword { - caseId: string | null; - status: WorkflowStatus; - error: string | null; - messages: string[]; -} - -export interface ResetPassword { - resetPassword: ResetPassword_resetPassword; -} - -export interface ResetPasswordVariables { - newPassword: string; - confirmPassword: string; - token: string; -} - -/* tslint:disable */ -/* eslint-disable */ -// @generated -// This file was automatically generated and should not be edited. - -// ==================================================== -// GraphQL mutation operation: VerifyMfaDevice -// ==================================================== - -export interface VerifyMfaDevice_verifyMfaDevice { - success: boolean; - error: MfaError | null; -} - -export interface VerifyMfaDevice { - verifyMfaDevice: VerifyMfaDevice_verifyMfaDevice; -} - -export interface VerifyMfaDeviceVariables { - sessionId: string; - pairingCode: string; -} - -/* tslint:disable */ -/* eslint-disable */ -// @generated -// This file was automatically generated and should not be edited. - -//============================================================== -// START Enums and Input Objects -//============================================================== - -export enum MfaError { - ALREADY_REGISTERED = "ALREADY_REGISTERED", - INVALID_EMAIL = "INVALID_EMAIL", - INVALID_PHONE_NUMBER = "INVALID_PHONE_NUMBER", - WRONG_CODE = "WRONG_CODE", - WRONG_PASSWORD = "WRONG_PASSWORD", -} - -export enum VerificationType { - EMAIL = "EMAIL", - SMS = "SMS", - VOICE = "VOICE", -} - -export enum WorkflowStatus { - ERROR = "ERROR", - SUCCESS = "SUCCESS", - UNKNOWN = "UNKNOWN", -} - -//============================================================== -// END Enums and Input Objects -//============================================================== diff --git a/services-js/access-boston/src/client/graphql/reset-password.ts b/services-js/access-boston/src/client/graphql/reset-password.ts deleted file mode 100644 index 5b76d36e5..000000000 --- a/services-js/access-boston/src/client/graphql/reset-password.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { FetchGraphql, gql } from '@cityofboston/next-client-common'; -import { ResetPassword, ResetPasswordVariables } from './queries'; - -const QUERY = gql` - mutation ResetPassword( - $newPassword: String! - $confirmPassword: String! - $token: String! - ) { - resetPassword( - newPassword: $newPassword - confirmPassword: $confirmPassword - token: $token - ) { - caseId - status - error - messages - } - } -`; - -export default async function resetPassword( - fetchGraphql: FetchGraphql, - newPassword: string, - confirmPassword: string, - token: string -) { - const args: ResetPasswordVariables = { - newPassword, - confirmPassword, - token, - }; - - return ((await fetchGraphql(QUERY, args)) as ResetPassword).resetPassword; -} diff --git a/services-js/access-boston/src/client/graphql/verify-mfa-device.ts b/services-js/access-boston/src/client/graphql/verify-mfa-device.ts deleted file mode 100644 index ef53e1ed8..000000000 --- a/services-js/access-boston/src/client/graphql/verify-mfa-device.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { FetchGraphql, gql } from '@cityofboston/next-client-common'; -import { - VerifyMfaDevice_verifyMfaDevice, - VerifyMfaDeviceVariables, - VerifyMfaDevice, -} from './queries'; - -const QUERY = gql` - mutation VerifyMfaDevice($sessionId: String!, $pairingCode: String!) { - verifyMfaDevice(sessionId: $sessionId, pairingCode: $pairingCode) { - success - error - } - } -`; - -// Renames just to not expose the GraphQL-generated type names. -export interface VerifyMfaDeviceResult - extends VerifyMfaDevice_verifyMfaDevice {} - -export default async function verifyMfaDevice( - fetchGraphql: FetchGraphql, - sessionId: string, - pairingCode: string -): Promise { - const args: VerifyMfaDeviceVariables = { - sessionId, - pairingCode, - }; - - return ((await fetchGraphql(QUERY, args)) as VerifyMfaDevice).verifyMfaDevice; -} diff --git a/services-js/access-boston/src/client/group-management/Icon.tsx b/services-js/access-boston/src/client/group-management/Icon.tsx deleted file mode 100644 index 5b0f58586..000000000 --- a/services-js/access-boston/src/client/group-management/Icon.tsx +++ /dev/null @@ -1,73 +0,0 @@ -/** @jsx jsx */ - -import { css, jsx } from '@emotion/core'; - -import { BLACK, WHITE } from '@cityofboston/react-fleet'; - -interface Props { - type: 'group' | 'person'; - size: 'large' | 'small'; -} - -/** - * type: - * “group” - three figures - * “person” - single figure, centered - * - * size: - * “large” - larger, no border; to be displayed on InitialView - * “small” - circular border; to be displayed by SelectedComponent - */ -export default function Icon(props: Props) { - const dimensions = props.size === 'large' ? 200 : 70; - - return ( - - ); -} - -const STYLING = css({ - 'path, g > circle': { - fill: WHITE, - stroke: BLACK, - strokeLinecap: 'round', - strokeWidth: 3, - padding: '5rem', - }, -}); diff --git a/services-js/access-boston/src/client/group-management/Index.stories.tsx b/services-js/access-boston/src/client/group-management/Index.stories.tsx deleted file mode 100644 index cafb53322..000000000 --- a/services-js/access-boston/src/client/group-management/Index.stories.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import React from 'react'; -import { storiesOf } from '@storybook/react'; - -import Index from './Index'; - -storiesOf('GroupManagementPage/Index', module).add('default', () => ( - -)); diff --git a/services-js/access-boston/src/client/group-management/Index.tsx b/services-js/access-boston/src/client/group-management/Index.tsx deleted file mode 100644 index ae1bd2929..000000000 --- a/services-js/access-boston/src/client/group-management/Index.tsx +++ /dev/null @@ -1,357 +0,0 @@ -/** @jsx jsx */ - -import { jsx } from '@emotion/core'; - -import { useEffect, useReducer, useState } from 'react'; -import { Group, Mode, Person, View } from './types'; - -import { reducer as stateReducer, initialState } from './state/app'; -import { reducer as listReducer } from './state/list'; - -import InitialView from './InitialView'; -import ManagementView from './ManagementView'; -import ReviewChangesView from './ReviewChangesView'; -import ReviewConfirmationView from './ReviewConfirmationView'; - -import EditableList from './list-components/EditableList'; -import SearchComponent from './search-component/SearchComponent'; - -import { - fetchGroupSearch, - fetchGroupSearchRemaining, - fetchPersonsGroups, - fetchOurContainers, - fetchDataURL, - fetchMinimumUserGroups, -} from './data-fetching/fetch-group-data'; -import { - fetchGroupMembers, - fetchPersonSearch, - fetchPersonSearchRemaining, -} from './data-fetching/fetch-person-data'; -import { renameObjectKeys, chunkArray } from './fixtures/helpers'; -import { pageSize } from './types'; - -interface Props { - groups: any; -} - -/** - * Definitions: - * - user: the app owner currently logged in - * - group: item representing a LDAP group - * - person: item representing a City person - */ -export default function Index(props: Props) { - const { groups } = props; - const [state, dispatchState] = useReducer(stateReducer, initialState); - const [list, dispatchList] = useReducer(listReducer, []); - const [loading, setLoading] = useState(false); - - const changeView = (newView: View): void => - dispatchState({ type: 'APP/CHANGE_VIEW', view: newView }); - - const changeMode = (newMode: Mode): void => - dispatchState({ type: 'APP/CHANGE_MODE', mode: newMode }); - - const changeSelected = (selectedItem: Group | Person): void => { - dispatchState({ type: 'APP/SET_SELECTED', selected: selectedItem }); - }; - - const resetAll = (): void => { - dispatchState({ type: 'APP/RESET_STATE' }); - dispatchList({ type: 'LIST/CLEAR_LIST' }); - }; - - const changePage = (currentPage: number): void => { - dispatchState({ type: 'APP/CHANGE_PAGE', currentPage }); - const { mode, selected } = state; - if (mode === 'group') { - if ( - selected.cn && - selected.chunked[currentPage] && - selected.chunked[currentPage].length > 0 - ) - handleFetchGroupMembers(selected, groups, currentPage); - } else { - if ( - selected.cn && - selected.chunked[currentPage] && - selected.chunked[currentPage].length > 0 - ) - handleFetchPersonsGroups(selected, groups, currentPage); - } - }; - - const changePageCount = (pageCount: number): void => { - dispatchState({ type: 'APP/CHANGE_PAGECOUNT', pageCount }); - }; - - const handleInitialSelection = (selectedItem: any): void => { - changeSelected(selectedItem); - changeView('management'); - }; - - const handleClickListItem = (item: Group | Person): void => { - changeSelected(item); - changeMode(state.mode === 'group' ? 'person' : 'group'); - }; - - const handleAdminListItemClick = (item: any): void => { - changeSelected(item); - changeView('management'); - }; - - const setApiUrl = async () => { - const apiURL = - process.env.GROUP_MANAGEMENT_API_URL || (await fetchDataURL()); - if (state.api === '') { - dispatchState({ - type: 'APP/SET_API', - api: apiURL, - }); - } - }; - - const setOus = async () => { - fetchOurContainers(groups).then(result => { - dispatchState({ - type: 'APP/SET_OUS', - ous: result.convertOUsToContainers, - }); - }); - }; - - const getAdminMinGroups = async () => { - fetchMinimumUserGroups(groups).then(result => { - let ret = result.getMinimumUserGroups.map((entry: Group | Person) => { - let remappedObj = renameObjectKeys( - { uniquemember: 'members', memberof: 'members' }, - entry - ); - remappedObj['chunked'] = - remappedObj['members'] && remappedObj['members'].length > -1 - ? chunkArray(remappedObj['members'], pageSize) - : []; - remappedObj['isAvailable'] = true; - remappedObj['status'] = 'current'; - - return remappedObj; - }); - - dispatchState({ - type: 'APP/SET_ADMIN_MIN_GROUPS', - dns: ret, - }); - }); - }; - - const handleFetchGroupMembers = ( - selected: Group, - dns: String[] = [], - _currentPage: number = 0 - ): void => { - const { members, chunked } = selected; - const mask_chunked = chunked ? chunked : []; - changePageCount(mask_chunked.length); - - if (members && members.length > 0) { - setLoading(true); - fetchGroupMembers(selected, dns, _currentPage).then(result => { - dispatchList({ - type: 'LIST/LOAD_LIST', - list: result, - }); - - setLoading(false); - }); - } - }; - - const handleFetchPersonsGroups = ( - selected: Person, - dns: string[] = [], - _currentPage: number = 0 - ): void => { - const { groups } = selected; - - if (groups && groups.length > 0) { - setLoading(true); - fetchPersonsGroups(selected, [], dns, state.ous, _currentPage).then( - result => { - dispatchList({ - type: 'LIST/LOAD_LIST', - list: result, - }); - setLoading(false); - } - ); - } - }; - - useEffect(() => { - const { mode, selected } = state; - if (mode === 'group') { - if (selected.cn) handleFetchGroupMembers(selected, groups); - } else { - if (selected.cn) handleFetchPersonsGroups(selected, groups); - } - - // Update the document title using the browser API - if ( - !groups || - typeof groups !== 'object' || - groups.length < 0 || - groups.filter((str: string) => str.includes('_GRPMGMT_')) < 1 - ) { - if (window) window.location.href = '/'; - } else { - if (!state.ous || state.ous.length === 0) { - setOus(); - } - - if (!state.adminMinGroups || state.adminMinGroups.length === 0) { - getAdminMinGroups(); - } - - // Once a selection is made, populate the list and update suggestions. - setApiUrl(); - } - }, [state.selected]); - - const handleToggleItem = (item: Group | Person) => { - if (item.action && item.action === 'new') { - dispatchList({ type: 'LIST/DELETE_ITEM', item }); - } else { - dispatchList({ type: 'LIST/TOGGLE_ITEM_STATUS', item }); - } - }; - - const handleAddToList = (item: Group | Person) => - dispatchList({ type: 'LIST/ADD_ITEM', item }); - - // Convenience method. - const inverseMode = (): Mode => { - if (state.mode === 'person') { - return 'group'; - } else { - return 'person'; - } - }; - - let cnEntries = []; - if (state.selected.members || state.selected.groups) { - cnEntries = state.selected.members - ? state.selected.members - : state.selected.groups; - } - - switch (state.view) { - case 'management': - return ( -
- - } - editableList={ - - } - resetAll={resetAll} - /> -
- ); - - case 'review': - return ( -
- -
- ); - - case 'confirmation': - return ( -
- -
- ); - - default: - return ( -
- - } - /> -
- ); - } -} - -const CONTAINER_STYLING: any = { - flexGrow: 1, - display: 'flex', - flexDirection: 'column', -}; - -const CONFIRMATION_CONTAINER_STYLING: any = { - display: 'flex', - flexDirection: 'column', -}; diff --git a/services-js/access-boston/src/client/group-management/InitialView.tsx b/services-js/access-boston/src/client/group-management/InitialView.tsx deleted file mode 100644 index 6348d4e2e..000000000 --- a/services-js/access-boston/src/client/group-management/InitialView.tsx +++ /dev/null @@ -1,182 +0,0 @@ -/** @jsx jsx */ - -import { css, jsx } from '@emotion/core'; - -import { ChangeEvent, ReactNode } from 'react'; -// import * as fetch from 'node-fetch'; - -import { - OPTIMISTIC_BLUE_DARK, - VISUALLY_HIDDEN, - WHITE, - SectionHeader, -} from '@cityofboston/react-fleet'; - -import { capitalize } from '../utility'; - -import { Mode } from './types'; - -import Section from './Section'; - -import Icon from './Icon'; -// import { Group, Person } from '../group-management/types'; -import MinGroupDisplay from './MinGroupDisplay'; - -interface Props { - mode: Mode; - changeMode: (mode: Mode) => void; - searchComponent: ReactNode; - adminMinGroups?: []; - handleAdminGroupClick: (item: any) => void; -} - -/** - * The initial view of the Manage Groups application; provides user with - * the option to perform an initial search by person, or by group. - */ -export default function InitialView(props: Props) { - const { mode, changeMode, adminMinGroups } = props; - const handleModeChange = (event: ChangeEvent) => { - changeMode(event.target.value as Mode); - }; - const admin_groups: [] = adminMinGroups ? adminMinGroups : []; - const showMinGroupDisplay = - adminMinGroups && - adminMinGroups.length > 0 && - adminMinGroups.length < 4 && - mode === 'group'; - - return ( - <> -
- - -
- - - - - - - - - - - -
-
- - {showMinGroupDisplay ? ( - - ) : ( - <>{props.searchComponent} - )} - - ); -} - -export function SearchTypeOption({ - searchTypeName, - currentSelection, - handleChange, - children, -}) { - return ( - - ); -} - -const OPACITY_STYLING = { - DEFAULT: { - opacity: 0.5, - - transition: 'opacity 0.15s', - }, - - SELECTED: { - opacity: 1, - }, -}; - -const SELECTION_OPTION_STYLING = css({ - margin: '1rem', - cursor: 'pointer', - - 'input[type="radio"]': { - '&:focus + figure': { - '> div': { - outline: `2px solid ${OPTIMISTIC_BLUE_DARK}`, - outlineOffset: '1px', - }, - }, - '&:checked + figure': { - '> div > *, figcaption': { - ...OPACITY_STYLING.SELECTED, - }, - }, - }, -}); - -const FIGURE_STYLING = css({ - margin: 0, - textAlign: 'center', - - figcaption: { - marginTop: '1rem', - fontStyle: 'italic', - fontSize: '1.25rem', - color: OPTIMISTIC_BLUE_DARK, - ...OPACITY_STYLING.DEFAULT, - }, - - '> div': { - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - - height: '150px', - width: '150px', - backgroundColor: WHITE, - - '> *': { - ...OPACITY_STYLING.DEFAULT, - }, - }, -}); - -const SELECTION_CONTAINER_STYLING = css({ - display: 'flex', - alignContent: 'center', - justifyContent: 'center', - border: 0, - - input: VISUALLY_HIDDEN, - label: SELECTION_OPTION_STYLING, -}); diff --git a/services-js/access-boston/src/client/group-management/InititalView.stories.tsx b/services-js/access-boston/src/client/group-management/InititalView.stories.tsx deleted file mode 100644 index 09582dd55..000000000 --- a/services-js/access-boston/src/client/group-management/InititalView.stories.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import React, { useReducer } from 'react'; - -import { storiesOf } from '@storybook/react'; - -import { reducer, initialState } from './state/app'; - -import { fetchPersonSearch } from './fixtures/mock-fetch-person-data'; -import { fetchGroupSearch } from './fixtures/mock-fetch-group-data'; - -import InitialView from './InitialView'; -import SearchComponent from './search-component/SearchComponent'; - -function Wrapper() { - const [state, dispatch] = useReducer(reducer, initialState); - - const toggleMode = () => { - if (state.mode === 'person') { - dispatch({ type: 'APP/CHANGE_MODE', mode: 'group' }); - } else { - dispatch({ type: 'APP/CHANGE_MODE', mode: 'person' }); - } - }; - - return ( - {}} - mode={state.mode} - changeMode={toggleMode} - searchComponent={ - {}} - dns={[]} - /> - } - /> - ); -} - -storiesOf('GroupManagementPage/InitialView', module).add('default', () => ( - -)); diff --git a/services-js/access-boston/src/client/group-management/ManagementView.stories.tsx b/services-js/access-boston/src/client/group-management/ManagementView.stories.tsx deleted file mode 100644 index 3be72f5ac..000000000 --- a/services-js/access-boston/src/client/group-management/ManagementView.stories.tsx +++ /dev/null @@ -1,98 +0,0 @@ -import React, { useEffect, useReducer } from 'react'; - -import { storiesOf } from '@storybook/react'; - -import { reducer } from './state/list'; - -import { - fetchGroupSearchRemaining, - fetchPersonsGroups, -} from './fixtures/mock-fetch-group-data'; -import { - fetchGroupMembers, - fetchPersonSearchRemaining, -} from './fixtures/mock-fetch-person-data'; - -import { toGroup, toPerson } from './state/data-helpers'; - -import { Group, Person } from './types'; - -import allGroups from './fixtures/groups.json'; -import allPeople from './fixtures/people.json'; - -import ManagementView from './ManagementView'; -import SearchComponent from './search-component/SearchComponent'; -import EditableList from './list-components/EditableList'; - -const groups = allGroups.map(group => toGroup(group)); -const people = allPeople.map(person => toPerson(person)); - -export function Wrapper(props) { - const [list, dispatch] = useReducer(reducer, []); - - const { loading, mode, selected } = props; - - const searchMode = mode === 'person' ? 'group' : 'person'; - - const toggleItem = (item: Group | Person) => { - dispatch({ type: 'LIST/TOGGLE_ITEM_STATUS', cn: item.cn }); - }; - - const loadList = selected => { - if (searchMode === 'group') { - fetchPersonsGroups(selected, []).then(groups => - dispatch({ type: 'LIST/LOAD_LIST', list: groups }) - ); - } else if (searchMode === 'person') { - fetchGroupMembers(selected).then(people => - dispatch({ type: 'LIST/LOAD_LIST', list: people }) - ); - } - }; - - useEffect(() => { - loadList(selected); - }, []); - - return ( -
- {}} - list={list} - loading={loading} - searchComponent={ - {}} - selectedItem={selected} - dns={[]} - /> - } - editableList={ - - } - resetAll={() => {}} - /> -
- ); -} - -storiesOf('GroupManagementPage/ManagementView', module) - .add('person view', () => ) - .add('group view', () => ) - .add('loading view', () => ( - - )); diff --git a/services-js/access-boston/src/client/group-management/ManagementView.tsx b/services-js/access-boston/src/client/group-management/ManagementView.tsx deleted file mode 100644 index e34abbdcc..000000000 --- a/services-js/access-boston/src/client/group-management/ManagementView.tsx +++ /dev/null @@ -1,80 +0,0 @@ -/** @jsx jsx */ - -import { css, jsx } from '@emotion/core'; - -import { ReactNode } from 'react'; - -import { SectionHeader } from '@cityofboston/react-fleet'; - -import { Group, Mode, Person, View } from './types'; - -import Section from './Section'; -import SelectedComponent from './SelectedComponent'; - -interface Props { - list: Array; - mode: Mode; - selected: Group | Person; - loading?: boolean; - changeView: (view: View) => void; - searchComponent: ReactNode; - editableList: ReactNode; - resetAll: () => void; -} - -/** - * View to see and edit the groups an person belongs to, or a group’s membership. - * - * should fetch relevant members or groups from itemDetails object on mount and store as currentList - * - */ -export default function ManagementView(props: Props) { - const { mode, selected, list } = props; - const addedItems = - list.filter(item => item.status === 'add' || item.action === 'new') || []; - const removedItems = - list.filter(item => item.status === 'remove' && item.action === '') || []; - const canProceed: boolean = addedItems.length > 0 || removedItems.length > 0; - - return ( - <> - - - {props.searchComponent} - -
- - {props.editableList} -
- - {!props.loading && list.length > 0 && ( - - )} -
-
- - ); -} - -const BUTTON_CONTAINER_STYLING = css({ - display: 'flex', - justifyContent: 'space-between', -}); diff --git a/services-js/access-boston/src/client/group-management/MinGroupDisplay.tsx b/services-js/access-boston/src/client/group-management/MinGroupDisplay.tsx deleted file mode 100644 index 6fb688ad9..000000000 --- a/services-js/access-boston/src/client/group-management/MinGroupDisplay.tsx +++ /dev/null @@ -1,41 +0,0 @@ -/** @jsx jsx */ - -import { css, jsx } from '@emotion/core'; -import { SectionHeader } from '@cityofboston/react-fleet'; -import Section from './Section'; -import ListLinksComponent from './list-components/ListLinksComponent'; -import { Group } from './types'; - -interface Props { - groups: []; - handleAdminGroupClick: (item: any) => void; -} - -/** - * View for Admin Users with a limited amount of groups they can manage - * Less than 3 - */ -export default function MinGroupDisplay(props: Props) { - const { groups, handleAdminGroupClick } = props; - return ( - <> -
- -
    - {groups.map((item: Group) => ( - - ))} -
-
- - ); -} - -const LIST_STYLING = css({ - textDecoration: 'none', - padding: '0', -}); diff --git a/services-js/access-boston/src/client/group-management/ReviewChangesView.tsx b/services-js/access-boston/src/client/group-management/ReviewChangesView.tsx deleted file mode 100644 index 34a5360fe..000000000 --- a/services-js/access-boston/src/client/group-management/ReviewChangesView.tsx +++ /dev/null @@ -1,124 +0,0 @@ -/** @jsx jsx */ - -import { css, jsx } from '@emotion/core'; - -import { useState } from 'react'; - -import { SANS } from '@cityofboston/react-fleet'; - -import { Group, Person, Mode, View } from './types'; - -import StatusModal from '../StatusModal'; - -import Section from './Section'; -import SelectedComponent from './SelectedComponent'; -import ReviewList from './list-components/ReviewList'; -import { updateGroup } from './data-fetching/fetch-group-data'; - -interface Props { - mode: Mode; - selected: Group | Person; - changeView: (view: View) => void; - resetAll: () => void; - items: Array; - submitting?: boolean; - dns?: [String]; - getAdminMinGroups?: () => void | {}; -} - -export default function ReviewChangesView(props: Props) { - const [submitting, setSubmitting] = useState( - props.submitting || false - ); - const { items, mode, selected, dns, getAdminMinGroups } = props; - const internalMode = mode === 'person' ? 'group' : 'person'; - const addedItems = items.filter(item => item.status === 'add') || []; - const removedItems = items.filter(item => item.status === 'remove') || []; - const renderSubmitting = () => { - return ( - -
Submitting changes...
-
- ); - }; - - const getAdmin_MinGroups = () => { - getAdminMinGroups ? getAdminMinGroups() : () => {}; - }; - - const handleSubmit = async () => { - setSubmitting(true); - const changesArr = [...addedItems, ...removedItems]; - try { - const promises = changesArr.map(entry => { - const _mode = mode === 'person'; - const updateParams: any = { - dn: _mode ? entry.dn : selected.dn, - cn: _mode ? selected.dn : entry.dn, - }; - const operation = - entry.status === 'current' || entry.status === 'remove' - ? 'delete' - : entry.status; - return updateGroup(updateParams.dn, operation, updateParams.cn, dns); - }); - await Promise.all(promises).then(() => { - getAdmin_MinGroups(); - setTimeout(() => { - setSubmitting(false); - props.changeView('confirmation'); - }, 1500); - }); - } catch (error) { - // eslint-disable-next-line no-console - console.log('Submit Changes (Error): ', error); - } - - // todo: handle error state (print error to modal?) - }; - - return ( - <> - - -
- {addedItems.length > 0 && ( - - )} - - {removedItems.length > 0 && ( - - )} - -
- - - - - {submitting && renderSubmitting()} -
-
- - ); -} - -const BUTTON_CONTAINER_STYLING = css({ - display: 'flex', - justifyContent: 'space-between', -}); - -const MODAL_STYLING = css({ - fontFamily: SANS, -}); diff --git a/services-js/access-boston/src/client/group-management/ReviewConfirmationView.tsx b/services-js/access-boston/src/client/group-management/ReviewConfirmationView.tsx deleted file mode 100644 index 99750070a..000000000 --- a/services-js/access-boston/src/client/group-management/ReviewConfirmationView.tsx +++ /dev/null @@ -1,81 +0,0 @@ -/** @jsx jsx */ - -import { css, jsx } from '@emotion/core'; - -// import { useState } from 'react'; - -// import { SANS } from '@cityofboston/react-fleet'; - -import { Group, Person, Mode, View } from './types'; - -// import StatusModal from '../StatusModal'; - -import Section from './Section'; -import SelectedComponent from './SelectedComponent'; -import ReviewList from './list-components/ReviewList'; -import { SectionHeader } from '@cityofboston/react-fleet'; - -interface Props { - mode: Mode; - selected: Group | Person; - changeView: (view: View) => void; - resetAll: () => void; - items: Array; -} - -export default function ReviewConfirmationView(props: Props) { - // const [submitting, setSubmitting] = useState( - // props.submitting || false - // ); - const { items, mode, selected } = props; - const internalMode = mode === 'person' ? 'group' : 'person'; - - const addedItems = items.filter(item => item.status === 'add') || []; - const removedItems = items.filter(item => item.status === 'remove') || []; - const handleSubmit = async () => { - props.resetAll(); - }; - - return ( - <> - - -
- - - {addedItems.length > 0 && ( - - )} - - {removedItems.length > 0 && ( - - )} - -
- -
-
- - ); -} - -const BUTTON_CONTAINER_STYLING = css({ - display: 'flex', - justifyContent: 'space-between', -}); - -const MAIN_HEADER_STYLING = css({ - marginBottom: '2em', -}); diff --git a/services-js/access-boston/src/client/group-management/Section.tsx b/services-js/access-boston/src/client/group-management/Section.tsx deleted file mode 100644 index 026a9dcf9..000000000 --- a/services-js/access-boston/src/client/group-management/Section.tsx +++ /dev/null @@ -1,51 +0,0 @@ -/** @jsx jsx */ - -import { css, jsx } from '@emotion/core'; - -import { ReactNode } from 'react'; - -import { GRAY_000 } from '@cityofboston/react-fleet'; - -interface Props { - isGray?: boolean; - ariaLabel?: string; - stretch?: boolean; - children: ReactNode; -} - -/** - * Utility component to provide full-width background color as needed. - */ -export default function Section(props: Props) { - const ariaLabel = props.ariaLabel ? { 'aria-label': props.ariaLabel } : null; - - const stretchAttributes = css({ - flexGrow: 1, - display: 'flex', - flexDirection: 'column', - }); - - return ( -
-
- {props.children} -
-
- ); -} diff --git a/services-js/access-boston/src/client/group-management/SelectedComponent.stories.tsx b/services-js/access-boston/src/client/group-management/SelectedComponent.stories.tsx deleted file mode 100644 index ab73b5e79..000000000 --- a/services-js/access-boston/src/client/group-management/SelectedComponent.stories.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import React from 'react'; -import { storiesOf } from '@storybook/react'; - -import { toGroup, toPerson } from './state/data-helpers'; - -import allPeople from './fixtures/people.json'; -import allGroups from './fixtures/groups.json'; - -import SelectedComponent from './SelectedComponent'; - -const mockPerson = toPerson(allPeople[1]); -const mockGroup = toGroup(allGroups[0]); - -storiesOf('GroupManagementPage/SelectedComponent', module) - .add('person view', () => ( - - )) - .add('groups view', () => ( - - )); diff --git a/services-js/access-boston/src/client/group-management/SelectedComponent.tsx b/services-js/access-boston/src/client/group-management/SelectedComponent.tsx deleted file mode 100644 index ab96ffb96..000000000 --- a/services-js/access-boston/src/client/group-management/SelectedComponent.tsx +++ /dev/null @@ -1,50 +0,0 @@ -/** @jsx jsx */ - -import { css, jsx } from '@emotion/core'; -import { Group, Mode, Person } from './types'; - -import Section from './Section'; -import Icon from './Icon'; - -interface Props { - mode: Mode; - selected: Group | Person; -} - -export default function SelectedComponent(props: Props) { - const { - mode, - //@ts-ignore - selected: { cn, displayName, mail }, - } = props; - - const displayText = displayName || cn; - - return ( -
-
- - -
-

- {displayText} {mode === 'group' && 'group'} -

- - {mode === 'person' && mail && ( -
- {mail} -
- )} -
-
-
- ); -} - -const LAYOUT_STYLING = css({ - display: 'flex', - alignItems: 'center', - '> div': { - marginLeft: '1rem', - }, -}); diff --git a/services-js/access-boston/src/client/group-management/Spinner.tsx b/services-js/access-boston/src/client/group-management/Spinner.tsx deleted file mode 100644 index 16f16b456..000000000 --- a/services-js/access-boston/src/client/group-management/Spinner.tsx +++ /dev/null @@ -1,82 +0,0 @@ -/** @jsx jsx */ - -import { css, jsx, keyframes } from '@emotion/core'; - -/** - * “Loading” spinner. - * - * @param {string} size - */ -export default function spinner({ size }) { - return ( - - - - ); -} - -const DURATION = '1.4s'; -const OFFSET = '187px'; - -const SPIN_KEYFRAMES = keyframes` - 0% { - transform: rotate(0deg); - } - - 100% { - transform: rotate(270deg); - } -`; - -const DASH_KEYFRAMES = keyframes` - 0% { - stroke-dashoffset: ${OFFSET}; -} - - 50% { - stroke-dashoffset: calc(${OFFSET} / 4); - transform: rotate(135deg); -} - - 100% { - stroke-dashoffset: ${OFFSET}; - transform: rotate(450deg); - } -`; - -const SPINNER_STYLING = css({ - position: 'relative', - marginRight: '0.5em', - - '& > svg': { - position: 'absolute', - - animation: `${SPIN_KEYFRAMES} ${DURATION} linear infinite`, - }, - - '& .path': { - stroke: 'currentColor', - strokeDasharray: OFFSET, - strokeDashoffset: 0, - transformOrigin: 'center', - - animation: `${DASH_KEYFRAMES} ${DURATION} ease-in-out infinite`, - }, -}); diff --git a/services-js/access-boston/src/client/group-management/data-fetching/fetch-group-data.ts b/services-js/access-boston/src/client/group-management/data-fetching/fetch-group-data.ts deleted file mode 100644 index 2a5d5caf7..000000000 --- a/services-js/access-boston/src/client/group-management/data-fetching/fetch-group-data.ts +++ /dev/null @@ -1,180 +0,0 @@ -import { fetchGraphql } from './fetchGraphql'; -import { toGroup } from '../state/data-helpers'; -import { Group, Person } from '../types'; - -const GROUP_DATA = ` - dn - cn - displayname - uniquemember -`; - -const FETCH_GROUP = ` - query getGroup($cn: String!) { - group(cn: $cn) { - ${GROUP_DATA} - } - } -`; - -const SEARCH_GROUPS = ` - query searchGroups($term: String! $dns: [String!]!) { - groupSearch(term: $term dns: $dns) { - ${GROUP_DATA} - } - } -`; - -const OU_MINIMUM_GROUPS = ` - query ouContainers($dns: [String]!) { - getMinimumUserGroups(dns: $dns) { - ${GROUP_DATA} - } - } -`; - -const OU_CONTAINERS = ` - query ouContainers($ous: [String]!) { - convertOUsToContainers(ous: $ous) - } -`; - -const UPDATE_GROUP = ` - mutation updateGroup( - $dn: String! - $operation: String! - $uniquemember: String! - $dns: [String] - ) { - updateGroupMembers( - dn: $dn - operation: $operation - uniquemember: $uniquemember - dns: $dns - ) {code message} - } -`; - -/** - * Updates a Group object with a single entry. - */ -export async function updateGroup( - dn: string, - operation: string, - uniquemember: string, - dns: String[] = [] -): Promise { - return await fetchGraphql(UPDATE_GROUP, { - dn, - operation, - uniquemember, - dns, - }); -} - -/** - * Returns GRAPH QL API. - */ -export async function fetchDataURL(): Promise { - return await fetch('/warptime' as string, { method: 'GET' }) - .then(response => response.text()) - .then(response => response); -} - -/** - * Returns an array of OU containers. - */ -export async function fetchOurContainers( - ous: string[], - _api: any = undefined -): Promise { - const retVal = await fetchGraphql(OU_CONTAINERS, { ous }); - return retVal; -} - -/** - * Returns an array of OU containers. - */ -export async function fetchMinimumUserGroups(dns: string[]): Promise { - const results = await fetchGraphql(OU_MINIMUM_GROUPS, { dns }); - return results; -} - -/** - * Returns a single Group object. - */ -export async function fetchGroup( - cn: string, - _dns: String[] = [] -): Promise { - return await fetchGraphql(FETCH_GROUP, { cn }); -} - -/** - * Returns a promise resolving to an array of Group objects. - */ -export async function fetchGroupSearch( - term: string, - _selectedItem: any, - dns: String[] = [] -): Promise { - if (!dns) { - dns = []; - } - return await fetchGraphql(SEARCH_GROUPS, { term, dns }).then(response => - response.groupSearch.map(group => toGroup(group)) - ); -} - -/** - * Returns a promise resolving to an array of all groups a specific person - * is a member of. - * - * Groups the current user cannot modify WILL be included in these results. - */ -export async function fetchPersonsGroups( - person: Person, - _currentUserAllowedGroups: string[], - dns: string[], - ous: string[], - _currentPage: number = 0 -): Promise { - return await Promise.all( - person.chunked[_currentPage].map(groupCn => - fetchGroup(groupCn, dns).then(response => { - try { - if (response.group[0]) { - const retGroup = toGroup(response.group[0], dns, ous); - return retGroup; - } - } catch (error) { - // eslint-disable-next-line no-console - console.log('error: ', error); - } - const retgroup: Group = { - dn: '', - cn: '', - displayName: '', - members: [], - status: 'current', - action: '', - }; - return retgroup; - }) - ) - ); -} - -/** - * Returns a promise resolving to an array of Group objects, excluding groups - * the given person is already a member of. Used by SearchComponent when in - * “management” view. - */ -export async function fetchGroupSearchRemaining( - term: string, - person: Person, - dns: String[] -): Promise { - const groups = await fetchGroupSearch(term, [], dns); - return groups.filter(group => !person.groups.includes(group.cn)); -} diff --git a/services-js/access-boston/src/client/group-management/data-fetching/fetch-person-data.ts b/services-js/access-boston/src/client/group-management/data-fetching/fetch-person-data.ts deleted file mode 100644 index f88112c62..000000000 --- a/services-js/access-boston/src/client/group-management/data-fetching/fetch-person-data.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { fetchGraphql } from './fetchGraphql'; -import { toPerson } from '../state/data-helpers'; -import { Group, Person } from '../types'; - -const PERSON_DATA = ` - dn - cn - displayname - givenname - sn - mail - ismemberof -`; - -const FETCH_PERSON = ` - query getPerson($cn: String!) { - person(cn: $cn) { - ${PERSON_DATA} - } - } -`; - -const SEARCH_PEOPLE = ` - query searchPeople($term: String!) { - personSearch(term: $term) { - ${PERSON_DATA} - } - } -`; - -/** - * Returns a single Person object. - */ -export async function fetchPerson( - cn: string, - _dns: String[] = [] -): Promise { - return await fetchGraphql(FETCH_PERSON, { cn }); -} - -/** - * Returns a promise resolving to an array of People representing all - * members that match the search term provided. - */ -export async function fetchPersonSearch( - term: string, - _selectedItem: any, - _dns: String[] = [] -): Promise { - if (!_dns) { - _dns = []; - } - return await fetchGraphql(SEARCH_PEOPLE, { term }).then(response => - response.personSearch.map(person => toPerson(person)) - ); -} - -/** - * Returns a promise resolving to an array of People representing all - * members of a specific group. - * - * Inactive employees WILL be included in these results; see line 63. - */ -export async function fetchGroupMembers( - group: Group, - dns: String[] = [], - _currentPage: number = 0 -): Promise { - return await Promise.all( - group.chunked[_currentPage].map( - personCn => { - // todo: remove .replace() when api data no longer includes cn= - const cn = personCn.replace('cn=', ''); - - return fetchPerson(cn, dns) - .then(response => { - return toPerson(response.person[0]); - }) - .catch(() => toPerson({ cn, inactive: true })); - }, - [] as Person[] - ) - ); -} - -/** - * Returns a promise resolving to an array of all people that match the value, - * excluding a given group’s existing members. Used by SearchComponent when in - * “management” view. - */ -export async function fetchPersonSearchRemaining( - term: string, - group: Group, - dns: String[] -): Promise { - const people = await fetchPersonSearch(term, dns); - - return people.filter(person => !group.members.includes(person.cn)); -} diff --git a/services-js/access-boston/src/client/group-management/data-fetching/fetchGraphql.ts b/services-js/access-boston/src/client/group-management/data-fetching/fetchGraphql.ts deleted file mode 100644 index 7435921b2..000000000 --- a/services-js/access-boston/src/client/group-management/data-fetching/fetchGraphql.ts +++ /dev/null @@ -1,21 +0,0 @@ -import fetch from 'node-fetch'; - -export async function fetchGraphql( - query: string, - variables: any, - _api: any = undefined -) { - const retFascade = await fetch('/fetchGraphql', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - query, - variables, - }), - }) - .then(response => response.json()) - .then(response => response.data); - return retFascade; -} diff --git a/services-js/access-boston/src/client/group-management/fixtures/groups.json b/services-js/access-boston/src/client/group-management/fixtures/groups.json deleted file mode 100644 index edf6d05cd..000000000 --- a/services-js/access-boston/src/client/group-management/fixtures/groups.json +++ /dev/null @@ -1,738 +0,0 @@ -[ - { - "dn": "cn=ANML02_LostFound,cn=Lagan_Groups,cn=Groups,dc=boston,dc=cob", - "uniquemember": ["132367", "Ignis Scientia", "Laguna Loire"], - "cn": "ANML02_LostFound", - "displayname": "ANML02_LostFound" - }, - { - "dn": "cn=BPD_Districts,cn=Lagan_Groups,cn=Groups,dc=boston,dc=cob", - "uniquemember": ["Laguna Loire", "000296", "Ignis Scientia"], - "cn": "BPD_Districts", - "displayname": "BPD_Districts", - "canModify": false - }, - { - "dn": "cn=BPD_Administrative,cn=Lagan_Groups,cn=Groups,dc=boston,dc=cob", - "uniquemember": ["132367", "Ignis Scientia"], - "cn": "BPD_Administrative", - "displayname": "BPD_Administrative" - }, - - - - - - { - "dn": "cn=SG_AB_PSHCM,cn=PSHCM,cn=Groups,dc=boston,dc=cob", - "cn": "SG_AB_PSHCM", - "displayname": "", - "uniquemember": [ - "cn=010537", - "cn=010613", - "cn=010650", - "cn=010688", - "cn=010719", - "cn=010841", - "cn=008227", - "cn=008784", - "cn=008837", - "cn=008954", - "cn=009339", - "cn=009397", - "cn=009450", - "cn=010166", - "cn=010309", - "cn=011212", - "cn=011256", - "cn=011280", - "cn=011283", - "cn=011347", - "cn=011354", - "cn=011483", - "cn=011501", - "cn=011594", - "cn=011756", - "cn=011826", - "cn=011846", - "cn=012123", - "cn=012274", - "cn=012295", - "cn=017900", - "cn=018395", - "cn=018955", - "cn=021328", - "cn=022276", - "cn=024447", - "cn=024457", - "cn=024482", - "cn=024618", - "cn=024649", - "cn=024693", - "cn=024877", - "cn=025020", - "cn=025358", - "cn=025398", - "cn=025423", - "cn=025430", - "cn=025444", - "cn=025518", - "cn=025620", - "cn=025837", - "cn=025954", - "cn=025978", - "cn=026147", - "cn=026176", - "cn=026931", - "cn=027106", - "cn=027232", - "cn=027414", - "cn=027678", - "cn=027754", - "cn=027760", - "cn=027800", - "cn=028292", - "cn=028383", - "cn=028387", - "cn=028426", - "cn=028618", - "cn=028661", - "cn=028694", - "cn=028758", - "cn=028787", - "cn=028980", - "cn=029015", - "cn=029016", - "cn=029042", - "cn=029276", - "cn=029277", - "cn=029386", - "cn=029582", - "cn=029943", - "cn=030151", - "cn=030174", - "cn=030442", - "cn=030487", - "cn=030654", - "cn=030669", - "cn=030686", - "cn=030689", - "cn=030705", - "cn=030748", - "cn=030936", - "cn=030945", - "cn=030974", - "cn=031072", - "cn=031319", - "cn=031384", - "cn=031695", - "cn=031761", - "cn=031858", - "cn=031875", - "cn=031888", - "cn=032013", - "cn=032030", - "cn=032139", - "cn=032233", - "cn=032521", - "cn=033053", - "cn=033195", - "cn=033290", - "cn=033319", - "cn=033383", - "cn=033441", - "cn=033444", - "cn=033463", - "cn=033485", - "cn=033626", - "cn=033664", - "cn=033757", - "cn=033910", - "cn=033923", - "cn=033958", - "cn=034076", - "cn=034080", - "cn=034103", - "cn=034241", - "cn=034641", - "cn=034675", - "cn=034684", - "cn=034782", - "cn=034796", - "cn=034832", - "cn=034883", - "cn=034918", - "cn=035003", - "cn=035114", - "cn=035171", - "cn=035263", - "cn=035462", - "cn=035470", - "cn=035518", - "cn=035558", - "cn=035819", - "cn=035992", - "cn=035999", - "cn=036027", - "cn=036092", - "cn=036182", - "cn=036379", - "cn=036593", - "cn=036602", - "cn=036614", - "cn=036677", - "cn=036746", - "cn=037255", - "cn=037311", - "cn=037371", - "cn=037451", - "cn=037751", - "cn=037752", - "cn=037965", - "cn=037974", - "cn=038257", - "cn=038482", - "cn=038531", - "cn=038539", - "cn=038589", - "cn=038846", - "cn=038977", - "cn=038982", - "cn=039004", - "cn=039273", - "cn=039348", - "cn=039356", - "cn=039370", - "cn=039424", - "cn=039467", - "cn=039585", - "cn=039649", - "cn=039717", - "cn=039735", - "cn=039822", - "cn=040090", - "cn=040168", - "cn=040239", - "cn=040258", - "cn=040439", - "cn=040470", - "cn=040770", - "cn=040811", - "cn=041034", - "cn=041523", - "cn=041742", - "cn=041849", - "cn=041977", - "cn=042065", - "cn=042276", - "cn=042279", - "cn=042510", - "cn=042541", - "cn=042584", - "cn=042599", - "cn=042616", - "cn=042887", - "cn=042978", - "cn=043094", - "cn=043133", - "cn=043215", - "cn=043235", - "cn=043398", - "cn=043675", - "cn=043809", - "cn=043907", - "cn=043918", - "cn=043972", - "cn=044005", - "cn=044040", - "cn=044084", - "cn=044114", - "cn=044543", - "cn=044551", - "cn=044703", - "cn=045098", - "cn=045175", - "cn=045206", - "cn=045316", - "cn=045321", - "cn=045432", - "cn=045526", - "cn=045740", - "cn=045963", - "cn=046208", - "cn=046369", - "cn=046390", - "cn=046483", - "cn=046503", - "cn=046556", - "cn=046640", - "cn=046654", - "cn=046658", - "cn=046661", - "cn=046670", - "cn=046694", - "cn=046971", - "cn=047024", - "cn=047257", - "cn=047427", - "cn=047445", - "cn=047546", - "cn=047662", - "cn=047669", - "cn=047676", - "cn=047737", - "cn=047911", - "cn=047954", - "cn=048002", - "cn=048042", - "cn=048085", - "cn=048094", - "cn=048192", - "cn=048344", - "cn=048359", - "cn=048375", - "cn=048454", - "cn=048545", - "cn=048571", - "cn=048576", - "cn=048637", - "cn=048798", - "cn=048800", - "cn=048835", - "cn=049074", - "cn=049077", - "cn=049482", - "cn=049486", - "cn=049524", - "cn=049648", - "cn=049847", - "cn=049874", - "cn=049927", - "cn=050002", - "cn=050029", - "cn=050070", - "cn=050072", - "cn=050091", - "cn=050114", - "cn=050128", - "cn=050132", - "cn=050140", - "cn=050150", - "cn=050286", - "cn=050292", - "cn=050295", - "cn=050298", - "cn=050317", - "cn=050324", - "cn=050376", - "cn=050392", - "cn=050428", - "cn=050458", - "cn=050482", - "cn=050495", - "cn=050520", - "cn=050564", - "cn=050579", - "cn=050585", - "cn=050593", - "cn=050600", - "cn=050604", - "cn=050660", - "cn=050665", - "cn=050761", - "cn=050769", - "cn=050805", - "cn=050831", - "cn=050841", - "cn=050850", - "cn=050947", - "cn=050951", - "cn=050981", - "cn=050983", - "cn=050994", - "cn=051220", - "cn=051288", - "cn=051311", - "cn=051321", - "cn=051328", - "cn=051530", - "cn=051581", - "cn=051624", - "cn=051651", - "cn=051713", - "cn=051720", - "cn=051739", - "cn=051745", - "cn=051842", - "cn=051843", - "cn=051903", - "cn=051919", - "cn=051965", - "cn=051993", - "cn=051997", - "cn=052063", - "cn=052064", - "cn=052174", - "cn=052185", - "cn=052186", - "cn=052274", - "cn=052277", - "cn=052279", - "cn=052297", - "cn=052361", - "cn=052449", - "cn=052509", - "cn=052541", - "cn=052567", - "cn=052582", - "cn=052587", - "cn=052636", - "cn=052676", - "cn=052715", - "cn=052916", - "cn=053114", - "cn=053233", - "cn=053509", - "cn=053729", - "cn=053752", - "cn=053978", - "cn=054129", - "cn=055302", - "cn=055324", - "cn=055567", - "cn=055695", - "cn=055818", - "cn=055934", - "cn=056117", - "cn=056450", - "cn=056552", - "cn=056673", - "cn=056710", - "cn=057602", - "cn=071507", - "cn=072523", - "cn=072573", - "cn=073488", - "cn=073523", - "cn=073539", - "cn=074133", - "cn=074259", - "cn=074495", - "cn=074499", - "cn=074633", - "cn=074711", - "cn=074840", - "cn=074909", - "cn=074962", - "cn=075035", - "cn=075251", - "cn=075270", - "cn=075369", - "cn=075571", - "cn=075632", - "cn=075715", - "cn=075745", - "cn=075750", - "cn=075767", - "cn=075842", - "cn=076360", - "cn=076509", - "cn=076673", - "cn=076795", - "cn=076867", - "cn=077233", - "cn=077598", - "cn=077667", - "cn=077738", - "cn=077764", - "cn=077828", - "cn=077862", - "cn=077878", - "cn=078198", - "cn=078290", - "cn=078411", - "cn=079348", - "cn=080274", - "cn=080307", - "cn=080416", - "cn=080510", - "cn=080596", - "cn=080671", - "cn=080783", - "cn=080912", - "cn=081056", - "cn=081133", - "cn=081202", - "cn=081303", - "cn=081318", - "cn=081498", - "cn=082094", - "cn=082099", - "cn=082193", - "cn=082199", - "cn=082212", - "cn=082241", - "cn=082372", - "cn=082445", - "cn=082450", - "cn=082581", - "cn=082616", - "cn=082661", - "cn=082890", - "cn=082974", - "cn=083095", - "cn=083177", - "cn=083180", - "cn=083194", - "cn=083688", - "cn=084338", - "cn=084466", - "cn=086408", - "cn=087028", - "cn=087304", - "cn=087321", - "cn=087336", - "cn=087417", - "cn=087431", - "cn=087472", - "cn=087529", - "cn=087554", - "cn=087581", - "cn=087670", - "cn=087695", - "cn=087771", - "cn=087830", - "cn=088006", - "cn=088230", - "cn=088981", - "cn=089011", - "cn=089015", - "cn=089446", - "cn=089465", - "cn=089559", - "cn=089671", - "cn=089797", - "cn=090341", - "cn=090705", - "cn=091185", - "cn=091342", - "cn=091468", - "cn=091674", - "cn=091679", - "cn=091931", - "cn=092033", - "cn=092202", - "cn=092216", - "cn=092448", - "cn=092972", - "cn=093133", - "cn=093636", - "cn=093710", - "cn=093782", - "cn=094069", - "cn=094082", - "cn=094094", - "cn=094096", - "cn=094171", - "cn=094175", - "cn=094319", - "cn=094372", - "cn=094615", - "cn=094696", - "cn=094820", - "cn=094857", - "cn=094953", - "cn=095409", - "cn=095896", - "cn=095977", - "cn=096041", - "cn=096216", - "cn=096377", - "cn=096869", - "cn=096990", - "cn=097004", - "cn=097152", - "cn=097210", - "cn=097218", - "cn=097240", - "cn=097268", - "cn=097304", - "cn=097377", - "cn=097408", - "cn=097478", - "cn=097481", - "cn=097485", - "cn=097521", - "cn=097567", - "cn=097640", - "cn=097653", - "cn=097664", - "cn=097688", - "cn=097738", - "cn=097913", - "cn=097938", - "cn=097967", - "cn=098339", - "cn=098373", - "cn=098613", - "cn=098769", - "cn=098785", - "cn=099161", - "cn=099185", - "cn=099262", - "cn=099497", - "cn=099572", - "cn=099861", - "cn=099890", - "cn=099990", - "cn=100222", - "cn=100251", - "cn=100663", - "cn=100732", - "cn=100760", - "cn=100861", - "cn=100873", - "cn=101046", - "cn=101049", - "cn=101066", - "cn=101429", - "cn=101548", - "cn=101783", - "cn=101806", - "cn=101884", - "cn=102286", - "cn=102360", - "cn=102634", - "cn=102783", - "cn=102969", - "cn=103029", - "cn=103683", - "cn=104058", - "cn=104266", - "cn=104276", - "cn=104521", - "cn=104564", - "cn=104622", - "cn=104703", - "cn=104749", - "cn=104862", - "cn=104977", - "cn=105066", - "cn=105422", - "cn=105716", - "cn=105833", - "cn=106136", - "cn=106648", - "cn=106788", - "cn=106862", - "cn=106976", - "cn=107317", - "cn=107477", - "cn=107639", - "cn=108078", - "cn=108980", - "cn=109108", - "cn=109531", - "cn=109590", - "cn=109715", - "cn=110048", - "cn=110095", - "cn=110126", - "cn=110184", - "cn=110675", - "cn=110855", - "cn=110896", - "cn=111042", - "cn=111063", - "cn=111105", - "cn=114243", - "cn=114540", - "cn=114742", - "cn=115484", - "cn=115522", - "cn=116011", - "cn=116095", - "cn=116333", - "cn=116578", - "cn=117018", - "cn=119105", - "cn=119344", - "cn=119455", - "cn=120224", - "cn=121479", - "cn=121498", - "cn=124194", - "cn=124455", - "cn=125311", - "cn=125894", - "cn=126302", - "cn=126358", - "cn=126436", - "cn=126543", - "cn=126633", - "cn=126804", - "cn=127382", - "cn=127575", - "cn=127846", - "cn=128041", - "cn=129182", - "cn=129550", - "cn=130114", - "cn=130331", - "cn=130817", - "cn=131287", - "cn=131579", - "cn=131771", - "cn=131830", - "cn=132016", - "cn=132179", - "cn=132183", - "cn=132256", - "cn=132311", - "cn=132447", - "cn=133416", - "cn=133417", - "cn=133419", - "cn=133428", - "cn=134036", - "cn=134394", - "cn=134645", - "cn=134796", - "cn=134813", - "cn=135144", - "cn=135389", - "cn=135701", - "cn=135989", - "cn=136016", - "cn=136441", - "cn=136442", - "cn=136460", - "cn=136720", - "cn=136933", - "cn=136945", - "cn=137053", - "cn=137076", - "cn=137166", - "cn=137269", - "cn=138919", - "cn=138965", - "cn=139025", - "cn=139240", - "cn=139262", - "cn=139562", - "cn=139647", - "cn=140128", - "cn=140172", - "cn=140264", - "cn=140694", - "cn=140732", - "cn=140839", - "cn=140882", - "cn=141320", - "cn=141389", - "cn=141545", - "cn=141860", - "cn=155765" - ] - } -] diff --git a/services-js/access-boston/src/client/group-management/fixtures/helpers.ts b/services-js/access-boston/src/client/group-management/fixtures/helpers.ts deleted file mode 100644 index b7265b454..000000000 --- a/services-js/access-boston/src/client/group-management/fixtures/helpers.ts +++ /dev/null @@ -1,90 +0,0 @@ -/** - * Filter through several fields to try and match user input; - * returns true if input matches any value for the given fields. - */ -export function findMatch(item: any, value: string): any | null { - const fields = ['givenName', 'sn', 'displayName', 'cn']; - const inputValue = value.trim().toLowerCase(); - - return fields - .map(field => item[field]) - .some( - value => - value && value.toLowerCase().slice(0, inputValue.length) === inputValue - ); -} - -export function getItemObject(list: any[], cn: string): any { - return list.find(item => item.cn === cn); -} - -/** - * - * @param {object} arr - Array to chunk into an array of arrays - * @param {object} size - Interger value to chunk array into - * @description Chunk Array into an (2D) array of smaller arrays - * @return {object} - Return an array(2D) of arrays of chunked values - * - * @example - * chunkArray( - * [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13], - * 5 - * ); - * return [ - * [ 1, 2, 3, 4, 5 ], - * [ 6, 7, 8, 9, 10 ], - * [ 11, 12, 13 ] - * ]; - */ -export const chunkArray = (arr: Array = [], size: Number): object => { - let result: any = []; - let castSize: any = size; - - if (arr.length > 0) { - for (let i = 0; i < arr.length; i += castSize) { - result.push(arr.slice(i, i + castSize)); - } - } - return result; -}; - -/** - * @param {object} keysMap - Object, key/value Object with renamed keys - * @param {object} targetObj - Object whose keys you want to rename - * @description Rename object keys using the keyMap object - * - * @example - * renameObjectKeys( - * { - * dn: 'cn', - * CN: 'cn', - * isMemberof: 'ismemberof', - * nsAccountLock: 'nsaccountlock', - * }, - * { - * dn: '', - * cn: '100992', - * ismemberof: [], - * nsaccountlock: 'FALSE', - * isSponsor: 'TRUE', - * }, - * ); - * returns { - * dn: '', - * cn: '100992', - * ismemberof: [], - * nsaccountlock: 'FALSE', - * isSponsor: 'TRUE', - * } - */ -export const renameObjectKeys = (keysMap: object, obj) => { - const retObj: object = Object.keys(obj).reduce( - (acc, key) => ({ - ...acc, - ...{ [keysMap[key] || key]: obj[key] }, - }), - {} - ); - - return retObj; -}; diff --git a/services-js/access-boston/src/client/group-management/fixtures/mock-fetch-group-data.ts b/services-js/access-boston/src/client/group-management/fixtures/mock-fetch-group-data.ts deleted file mode 100644 index 815915afa..000000000 --- a/services-js/access-boston/src/client/group-management/fixtures/mock-fetch-group-data.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { toGroup } from '../state/data-helpers'; -import { findMatch, getItemObject } from './helpers'; - -import { Group, Person } from '../types'; - -import allGroups from './groups.json'; - -/** - * Returns a promise resolving to an array of Group objects. - */ -export async function fetchGroupSearch(term: string): Promise { - const value = term.trim().toLowerCase(); - - // const groups = await Promise.resolve(allGroups); - - return await new Promise(() => - allGroups - .map(group => toGroup(group)) - // .filter(group => group.isAvailable) - .filter(item => findMatch(item, value)) - ); -} - -/** - * Returns a promise resolving to an array of all groups a specific person - * is a member of. - * - * Groups the current user cannot modify WILL be included in these results. - */ -export function fetchPersonsGroups( - person: Person, - _currentUserAllowedGroups: string[] -): Promise { - return new Promise(resolve => - resolve( - person.groups.map(groupCn => toGroup(getItemObject(allGroups, groupCn))) - ) - ); -} - -/** - * Returns a promise resolving to an array of Group objects, excluding groups - * the given person is already a member of. Used by SearchComponent when in - * “management” view. - */ -export async function fetchGroupSearchRemaining( - term: string, - person: Person -): Promise { - const result = await fetchGroupSearch(term); - - return result.filter(item => !person.groups.includes(item.cn)); -} diff --git a/services-js/access-boston/src/client/group-management/fixtures/mock-fetch-person-data.ts b/services-js/access-boston/src/client/group-management/fixtures/mock-fetch-person-data.ts deleted file mode 100644 index 91b84f32e..000000000 --- a/services-js/access-boston/src/client/group-management/fixtures/mock-fetch-person-data.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { toPerson } from '../state/data-helpers'; -import { findMatch, getItemObject } from './helpers'; - -import { Group, Person } from '../types'; - -import allPeople from './people.json'; - -/** - * Returns a promise resolving to an array of People representing all - * members that match the search term provided. - */ -export async function fetchPersonSearch(term: string): Promise { - const value = term.trim().toLowerCase(); - - const people = await Promise.resolve(allPeople); - - return people - .map(person => toPerson(person)) - .filter(item => item.isAvailable) - .filter(item => findMatch(item, value)); -} - -/** - * Returns a promise resolving to an array of People representing all - * members of a specific group. - * - * Inactive employees WILL be included in these results. - */ -export async function fetchGroupMembers(group: Group): Promise { - return new Promise(resolve => - resolve( - group.members.map(personCn => - toPerson(getItemObject(allPeople, personCn)) - ) - ) - ); -} - -/** - * Returns a promise resolving to an array of all people that match the value, - * excluding a given group’s existing members. Used by SearchComponent when in - * “management” view. - */ -export async function fetchPersonSearchRemaining( - term: string, - group: Group -): Promise { - const result = await fetchPersonSearch(term); - - return result.filter(item => !group.members.includes(item.cn)); -} diff --git a/services-js/access-boston/src/client/group-management/fixtures/people.json b/services-js/access-boston/src/client/group-management/fixtures/people.json deleted file mode 100644 index 2638fa46e..000000000 --- a/services-js/access-boston/src/client/group-management/fixtures/people.json +++ /dev/null @@ -1,194 +0,0 @@ -[ - { - "dn": "cn=000296,cn=Internal Users,dc=boston,dc=cob", - "ismemberof": ["BPD_Districts"], - "sn": "Howard", - "givenname": "Terra", - "displayname": "Terra Howard", - "mail": "terra.howard@boston.gov", - "cn": "000296", - "nsaccountlock": "TRUE" - }, - { - "dn": "cn=132367,cn=Internal Users,dc=boston,dc=cob", - "ismemberof": [ - "ANML02_LostFound", - "BPD_Administrative", - "SG_AB_PSHCM", - "BPD_Districts" - ], - "sn": "Howell", - "mail": "cloud.howell@cityofboston.gov", - "givenname": "Cloud", - "displayname": "Cloud Howell", - "cn": "132367", - "nsaccountlock": "TRUE" - }, - { - "dn": "cn=Ignis Scientia,cn=Internal Users,dc=boston,dc=cob", - "ismemberof": ["ANML02_LostFound", "BPD_Districts", "BPD_Administrative"], - "sn": "Scientia", - "mail": "ignis.scientia@cityofboston.gov", - "givenname": "Ignis", - "displayname": "Ignis Scientia", - "cn": "Ignis Scientia", - "nsaccountlock": "TRUE" - }, - { - "dn": "cn=Laguna Loire,cn=Internal Users,dc=boston,dc=cob", - "ismemberof": ["ANML02_LostFound", "BPD_Districts"], - "sn": "Loire", - "mail": "laguna.loire@cityofboston.gov", - "givenname": "Laguna", - "displayname": "Laguna Loire", - "cn": "Laguna Loire", - "nsaccountlock": "TRUE" - }, - { - "dn": "cn=132367,cn=Internal Users,dc=boston,dc=cob", - "ismemberof": [], - "sn": "Smith", - "mail": "sam.smith@cityofboston.gov", - "givenname": "Sam", - "displayname": "Sam Smith", - "cn": "515151", - "nsaccountlock": "TRUE" - }, - - - - - - - - - - - - { - "dn": "cn=010613,cn=Internal Users,dc=boston,dc=cob", - "cn": "010613", - "givenname": "John", - "sn": "Brennan", - "displayname": "Brennan,John D", - "uid": "010613", - "mail": "BrennanJ.bpd@ci.boston.ma.us", - "ismemberof": [ - "sg_ab_icims", - "sg_ab_ess", - "sg_ab_sponsor", - "sg_ab_manager", - "sg_ab_mlp", - "SG_AB_PSHCM", - "SG_AB_AGILEPOINT", - "SG_AB_Proofpoint" - ], - "nsaccountlock": "FALSE" - }, - - { - "dn": "cn=010650,cn=Internal Users,dc=boston,dc=cob", - "cn": "010650", - "givenname": "Kathleen", - "sn": "Clancy", - "displayname": "Clancy,Kathleen E.", - "uid": "010650", - "mail": "Kathleen.Clancy@boston.gov", - "ismemberof": [ - "BO_A_PowerUsers", - "BO_C_EDW_FN_PowerUsers", - "sg_ab_icims", - "sg_ab_ess", - "sg_ab_sponsor", - "sg_ab_mlp", - "SG_AB_PSHCM", - "SG_AB_AGILEPOINT", - "SG_AB_Proofpoint" - ], - "nsaccountlock": "FALSE" - }, - - { - "dn": "cn=010688,cn=Internal Users,dc=boston,dc=cob", - "cn": "010688", - "givenname": "Michael", - "sn": "Gavin", - "displayname": "Gavin,Michael J", - "uid": "010688", - "mail": "GavinM.bpd@ci.boston.ma.us", - "ismemberof": [ - "sg_ab_icims", - "sg_ab_ess", - "sg_ab_sponsor", - "sg_ab_manager", - "sg_ab_mlp", - "SG_AB_PSFN", - "SG_AB_PSHCM", - "SG_AB_AGILEPOINT", - "SG_AB_Proofpoint" - ], - "nsaccountlock": "FALSE" - }, - - { - "dn": "cn=010719,cn=Internal Users,dc=boston,dc=cob", - "cn": "010719", - "givenname": "William", - "sn": "Knecht", - "displayname": "Knecht,William G", - "uid": "010719", - "mail": "KnechtW.bpd@ci.boston.ma.us", - "ismemberof": [ - "sg_ab_icims", - "sg_ab_ess", - "sg_ab_sponsor", - "sg_ab_manager", - "sg_ab_mlp", - "SG_AB_PSFN", - "SG_AB_PSHCM", - "SG_AB_AGILEPOINT", - "SG_AB_Proofpoint" - ], - "nsaccountlock": "FALSE" - }, - - { - "cn": "097210", - "displayname": "Hill,Frank L.", - "dn": "cn=097210,cn=Internal Users,dc=boston,dc=cob", - "givenname": "Frank", - "ismemberof": ["sg_ab_icims", "sg_ab_ess", "sg_ab_manager", "sg_ab_mlp", "SG_AB_PSFN", "SG_AB_PSHCM"], - "mail": "097210@boston.k12.ma.us", - "sn": "Hill" - }, - - { - "cn": "010537", - "displayname": "Gallarelli,Joseph A", - "dn": "cn=010537,cn=Internal Users,dc=boston,dc=cob", - "givenname": "Joseph", - "ismemberof": [ - "sg_ab_icims", - "sg_ab_ess", - "sg_ab_sponsor", - "sg_ab_manager", - "sg_ab_mlp", - "SG_AB_PSHCM", - "SG_AB_AGILEPOINT", - "SG_AB_Proofpoint" - ], - "mail": "GallarelliJ.bpd@ci.boston.ma.us", - "sn": "Gallarelli" - }, - - { - "cn": "141860", - "displayname": "Dierdre Connelly", - "dn": "cn=141860,cn=Internal Users,dc=boston,dc=cob", - "givenname": "Dierdre", - "ismemberof": ["AP OID Default", "sg_ab_icims", "sg_ab_ess", "sg_ab_sponsor", "sg_ab_mlp", "SG_AB_PSHCM"], - "mail": "dierdre.connelly2@pd.boston.gov", - "sn": "Connelly" - } - -] diff --git a/services-js/access-boston/src/client/group-management/list-components/EditableList.stories.tsx b/services-js/access-boston/src/client/group-management/list-components/EditableList.stories.tsx deleted file mode 100644 index 49b80f502..000000000 --- a/services-js/access-boston/src/client/group-management/list-components/EditableList.stories.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import React, { useEffect, useReducer } from 'react'; - -import { storiesOf } from '@storybook/react'; - -import { Group, Person } from '../types'; - -import { reducer } from '../state/list'; - -import { fetchGroupMembers } from '../fixtures/mock-fetch-person-data'; -import { fetchPersonsGroups } from '../fixtures/mock-fetch-group-data'; - -import EditableList from './EditableList'; - -export function EditableListWrapper(props) { - const [list, dispatch] = useReducer(reducer, []); - - const toggleItem = (item: Group | Person) => { - dispatch({ type: 'LIST/TOGGLE_ITEM_STATUS', cn: item.cn }); - }; - - useEffect(() => { - if (props.mode === 'person') { - fetchGroupMembers({ - members: ['Laguna Loire', '000296', 'Ignis Scientia'], - } as Group).then(people => - dispatch({ type: 'LIST/LOAD_LIST', list: people }) - ); - } else if (props.mode === 'group') { - fetchPersonsGroups( - { - groups: ['ANML02_LostFound', 'BPD_Districts', 'BPD_Administrative'], - } as Person, - [] - ).then(groups => dispatch({ type: 'LIST/LOAD_LIST', list: groups })); - } - }, []); - - return ( - - ); -} - -storiesOf('GroupManagementPage/ListComponents/EditableList', module) - .add('editable group view', () => ) - .add('editable person view', () => ) - .add('loading view', () => ( - - )) - .add('no results, person view', () => ( - {}} - handleClick={() => {}} - currentPage={0} - pageCount={1} - pageSize={100} - changePage={() => {}} - /> - )) - .add('no results, group view', () => ( - {}} - handleClick={() => {}} - currentPage={0} - pageCount={1} - pageSize={100} - changePage={() => {}} - /> - )); diff --git a/services-js/access-boston/src/client/group-management/list-components/EditableList.tsx b/services-js/access-boston/src/client/group-management/list-components/EditableList.tsx deleted file mode 100644 index 76efa72bd..000000000 --- a/services-js/access-boston/src/client/group-management/list-components/EditableList.tsx +++ /dev/null @@ -1,124 +0,0 @@ -/** @jsx jsx */ - -import { css, jsx } from '@emotion/core'; - -import { - FREEDOM_RED_DARK, - OPTIMISTIC_BLUE_DARK, - SANS, -} from '@cityofboston/react-fleet'; - -import { Group, Mode, Person } from '../types'; - -import Spinner from '../Spinner'; -import ListItemComponent from './ListItemComponent'; -import Pagination from '../pagination-components/Pagination'; - -import { LIST_STYLING } from './styling'; - -interface Props { - mode: Mode; - items: Array; - loading?: boolean; - handleChange: (item: Group | Person) => void; - handleClick: (item: Group | Person) => void; - dns?: [String]; - currentPage: number; - pageCount: number; - pageSize: number; - changePage: (currentPage: number) => any; -} - -/** - * Component to display an editable list of groups, or list of people - * (referred to as “items”). Items will contain clickable display text, and - * a checked box. - * - * A checked checkbox indicates an item is a member of the given list. If an - * item is displayed that represents a value the user does not have permission - * to change, the checked box is disabled. Unchecking the box will remove that - * item from the list. - * - * editableList reflects the current state of the user’s intended edits. - */ -export default function EditableList(props: Props) { - const { pageCount, currentPage, changePage, pageSize } = props; - const handleClick = (item: Group | Person): void => { - if (props.handleClick) props.handleClick(item); - }; - - const handlePageNumClick = (pageNum, changePage) => { - changePage(pageNum); - }; - const handleNextPage = (currentPage, pageCount, changePage) => { - if (currentPage < pageCount - 1) { - changePage(currentPage + 1); - } - }; - const handlePrevPage = (currentPage, changePage) => { - if (currentPage > 0) { - changePage(currentPage - 1); - } - }; - - const noResultsText = - props.mode === 'group' - ? 'This group has no members' - : 'This person hasn’t been added to any groups'; - - if (props.loading === true) { - return ( -
- -
- ); - } else { - return ( - <> -
    - {props.items && props.items.length > 0 ? ( - props.items.map(item => ( - props.handleChange(item)} - handleClick={handleClick} - isChecked={item.status !== 'remove'} - item={item} - /> - )) - ) : ( -
    {noResultsText}
    - )} -
- - {props.pageCount > 1 && ( - <> - - - )} - - ); - } -} - -const LOADER_STYLING = css({ - display: 'flex', - justifyContent: 'center', - marginTop: '3rem', - color: OPTIMISTIC_BLUE_DARK, -}); - -const NO_RESULTS_STYLING = css({ - fontFamily: SANS, - color: FREEDOM_RED_DARK, -}); diff --git a/services-js/access-boston/src/client/group-management/list-components/ListItemComponent.stories.tsx b/services-js/access-boston/src/client/group-management/list-components/ListItemComponent.stories.tsx deleted file mode 100644 index 90144bc70..000000000 --- a/services-js/access-boston/src/client/group-management/list-components/ListItemComponent.stories.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import React, { useState } from 'react'; -import { storiesOf } from '@storybook/react'; - -import { Person } from '../types'; - -import ListItemComponent from './ListItemComponent'; - -const person: Person = { - displayName: 'Bob Roberts', - cn: '010', - dn: '', - status: 'current', - givenName: 'Bob', - sn: 'Roberts', - mail: 'bob.roberts@boton.gov', - groups: [], - action: '', -}; - -function Wrapper(props) { - const [checked, setChecked] = useState(true); - - return ( - setChecked(!checked)} - isChecked={checked} - /> - ); -} - -storiesOf('GroupManagementPage/ListComponents/ListItemComponent', module) - .add('default', () => ) - .add('no link', () => ) - .add('not modifiable', () => ) - .add('newly added', () => ); diff --git a/services-js/access-boston/src/client/group-management/list-components/ListItemComponent.tsx b/services-js/access-boston/src/client/group-management/list-components/ListItemComponent.tsx deleted file mode 100644 index a0af0cb57..000000000 --- a/services-js/access-boston/src/client/group-management/list-components/ListItemComponent.tsx +++ /dev/null @@ -1,178 +0,0 @@ -/** @jsx jsx */ - -import { css, jsx } from '@emotion/core'; - -import { - BLACK, - CLEAR_DEFAULT_STYLING, - GRAY_000, - GRAY_100, - GRAY_200, - OPTIMISTIC_BLUE_DARK, - OPTIMISTIC_BLUE_LIGHT, - WHITE, -} from '@cityofboston/react-fleet'; - -import { Group, Person, View } from '../types'; - -import { LIST_ITEM_STYLING } from './styling'; - -interface Props { - item: Group | Person; - view: View; - isChecked?: boolean; - handleChange?: () => void; - handleClick?: (item: Group | Person) => void; -} - -/** - * A ListItem represents a group, or an person. If editable, it will also - * have a checkbox. Checkbox may be disabled via canModify prop. - */ -export default function ListItemComponent(props: Props) { - const { - handleChange, - handleClick, - isChecked, - item: { cn, displayName, isAvailable, status }, - view, - } = props; - - const displayText = displayName || cn; - const clickHandler = handleClick - ? { onClick: () => handleClick(props.item) } - : null; - - const displayElement = () => { - if (isAvailable && view === 'management') { - return ( - - - - ); - } else { - return {displayText}; - } - }; - - if (handleChange) { - return ( -
  • - - -
  • - ); - } else { - return ( -
  • - {displayElement()} -
  • - ); - } -} - -const BUTTON_STYLING = css({ - color: OPTIMISTIC_BLUE_DARK, - textDecoration: 'none', - cursor: 'pointer', -}); - -const REVIEW_LIST_ITEM_STYLING = css({ - backgroundColor: GRAY_000, -}); - -const EDITABLE_LIST_ITEM_STYLING = css({ - backgroundColor: WHITE, - transition: 'background-color 0.2s', - - label: { - position: 'relative', - display: 'flex', - alignItems: 'center', - justifyContent: 'space-between', - - cursor: 'pointer', - - '&::before, &::after': { - content: '""', - display: 'block', - position: 'absolute', - right: 0, - height: '1rem', - width: '1rem', - }, - - '&::before': { - border: `2px solid ${BLACK}`, - backgroundColor: WHITE, - }, - - '&::after': { - opacity: 0, - backgroundImage: - 'url(https://patterns.boston.gov/images/public/icons/check.svg)', - backgroundRepeat: 'no-repeat', - backgroundPosition: '0% 50%', - backgroundSize: '0.8em', - }, - }, - - 'input[type="checkbox"]': { - opacity: 0, - position: 'absolute', - height: 0, - width: 0, - margin: 0, - - '&:focus + label::before': { - outline: `2px solid ${OPTIMISTIC_BLUE_DARK}`, - }, - - '&:checked + label::after': { - opacity: 1, - }, - - '&:disabled + label': { - cursor: 'default', - - '&::before, &::after': { - opacity: 0.5, - cursor: 'not-allowed', - }, - - '&::before': { - backgroundColor: GRAY_200, - }, - }, - }, - - '&.unchecked': { - backgroundColor: GRAY_100, - }, -}); - -const ADDED_STYLING = css({ - outline: `2px solid ${OPTIMISTIC_BLUE_LIGHT}`, - outlineOffset: '-2px', -}); diff --git a/services-js/access-boston/src/client/group-management/list-components/ListLinksComponent.tsx b/services-js/access-boston/src/client/group-management/list-components/ListLinksComponent.tsx deleted file mode 100644 index 25d3ed6ce..000000000 --- a/services-js/access-boston/src/client/group-management/list-components/ListLinksComponent.tsx +++ /dev/null @@ -1,49 +0,0 @@ -/** @jsx jsx */ - -import { css, jsx } from '@emotion/core'; -import { - GRAY_000, - // BLACK, - OPTIMISTIC_BLUE_DARK, -} from '@cityofboston/react-fleet'; -import { LIST_ITEM_STYLING } from './styling'; - -interface Props { - item: any; - handleClick: (item: any) => void; -} - -/** - * A ListItem represents a group, or an person. If editable, it will also - * have a checkbox. Checkbox may be disabled via canModify prop. - */ -export default function ListLinksComponent(props: Props) { - const { - handleClick, - item: { cn, displayName }, - item, - } = props; - - const displayText = displayName || cn; - const clickHandler = () => handleClick(item); - - return ( -
  • - -
  • - ); -} - -const LIST_LINK_SPAN_STYLING = css({ - cursor: 'pointer', -}); - -const LIST_LINK_STYLING = css({ - color: OPTIMISTIC_BLUE_DARK, -}); - -const REVIEW_LIST_ITEM_STYLING = css({ - backgroundColor: GRAY_000, -}); diff --git a/services-js/access-boston/src/client/group-management/list-components/ReviewList.stories.tsx b/services-js/access-boston/src/client/group-management/list-components/ReviewList.stories.tsx deleted file mode 100644 index 27f51b8e0..000000000 --- a/services-js/access-boston/src/client/group-management/list-components/ReviewList.stories.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import React, { useEffect, useReducer } from 'react'; - -import { storiesOf } from '@storybook/react'; - -import { reducer } from '../state/list'; - -import allPeople from '../fixtures/people.json'; -import allGroups from '../fixtures/groups.json'; - -import ReviewList from './ReviewList'; -import { toGroup, toPerson } from '../state/data-helpers'; - -const mockPeople = allPeople.map(person => toPerson(person)); -const mockGroups = allGroups.map(group => toGroup(group)); - -export function Wrapper(props) { - const [list, dispatch] = useReducer(reducer, []); - - useEffect(() => dispatch({ type: 'LIST/LOAD_LIST', list: props.items }), []); - - return ; -} - -storiesOf('GroupManagementPage/ListComponents/ReviewList', module) - .add('groups added', () => ) - .add('members added', () => ); diff --git a/services-js/access-boston/src/client/group-management/list-components/ReviewList.tsx b/services-js/access-boston/src/client/group-management/list-components/ReviewList.tsx deleted file mode 100644 index ffd7d2a5c..000000000 --- a/services-js/access-boston/src/client/group-management/list-components/ReviewList.tsx +++ /dev/null @@ -1,54 +0,0 @@ -/** @jsx jsx */ - -import { jsx } from '@emotion/core'; - -import { SectionHeader } from '@cityofboston/react-fleet'; - -import { capitalize } from '../../utility'; - -import { Group, ItemStatus, Mode, Person, ShowLabel } from '../types'; - -import { LIST_STYLING } from './styling'; - -import ListItemComponent from './ListItemComponent'; - -interface Props { - mode: Mode; - status: ItemStatus; - items: Array; - showLabel?: ShowLabel; - subheader?: Boolean; -} - -/** - * Displays a list of added or removed items; utilized by ReviewChangesView. - */ -export default function ReviewList(props: Props) { - const { mode, status, showLabel, subheader } = props; - - const internalMode = mode === 'person' ? 'group' : 'person'; - const titleText = mode === 'person' ? 'members' : 'groups'; - const statusText = status === 'add' ? 'added' : 'removed'; - - const id = `review-${internalMode}-${status}`; - const ShowLabel = typeof showLabel === 'undefined' ? true : showLabel; - const sub_header = subheader && subheader === true ? true : false; - - return ( - <> - {ShowLabel && ( - - )} - -
      - {props.items.map(item => ( - - ))} -
    - - ); -} diff --git a/services-js/access-boston/src/client/group-management/list-components/styling.ts b/services-js/access-boston/src/client/group-management/list-components/styling.ts deleted file mode 100644 index 0bc1882c8..000000000 --- a/services-js/access-boston/src/client/group-management/list-components/styling.ts +++ /dev/null @@ -1,12 +0,0 @@ -export const LIST_STYLING = { - listStyle: 'none', - paddingLeft: 0, - paddingBottom: '0.75rem', - flexGrow: 1, -}; - -export const LIST_ITEM_STYLING = { - listStyle: 'none', - padding: '0.5em 1em', - marginBottom: '0.75rem', -}; diff --git a/services-js/access-boston/src/client/group-management/pagination-components/Pagination.tsx b/services-js/access-boston/src/client/group-management/pagination-components/Pagination.tsx deleted file mode 100644 index 233135412..000000000 --- a/services-js/access-boston/src/client/group-management/pagination-components/Pagination.tsx +++ /dev/null @@ -1,198 +0,0 @@ -/** @jsx jsx */ - -import { jsx } from '@emotion/core'; -import { - HOVER_STYLES, - NORM_HOVER, - PAGINATION, -} from '../pagination-components/styling'; -import { Group, Person } from '../types'; - -interface Props { - items: Array; - currentPage: number; - pageCount: number; - pageSize: number; - changePage: (currentPage: number) => any; - handleNextPage: ( - currentPage: number, - pageCount: number, - changePage: any - ) => any; - handlePrevPage: (currentPage: number, changePage: any) => any; - handlePageNumClick: (pageNum: number, changePage: any) => any; -} - -export default function Pagination(props: Props) { - const { - currentPage, - pageCount, - changePage, - handleNextPage, - handlePrevPage, - } = props; - - const goToPage = (pageNum: number) => { - changePage(pageNum); - }; - - const next = () => { - handleNextPage(currentPage, pageCount, changePage); - }; - - const prev = () => { - handlePrevPage(currentPage, changePage); - }; - - const lastPage = pageCount - 1; - const pageSpan = 3; - const prevPages: number = currentPage - pageSpan; - let prevPagesArr = - prevPages > 0 - ? Array.from(Array(prevPages), (_x, index) => currentPage - index) - : Array.from( - Array(prevPages + pageSpan), - (_x, index) => currentPage - index - ).sort((a, b) => a - b); - let nextPagesArr = Array.from( - Array(pageSpan - 1), - (_x, index) => currentPage + 1 + index - ); - - let prevList1: Array = []; - if (prevPagesArr.length < 1) { - let elem = ( -
  • - { - goToPage(1 - 1); - }} - > - 1 - -
  • - ); - prevList1.push(elem); - } else { - prevList1 = prevPagesArr.map((_val, _index) => { - const cs = 'pg-li-i pg-li-i--link'; - const active = currentPage === _index ? `${cs} pg-li-i--a` : `${cs}`; - return ( -
  • - { - goToPage(_index); - }} - > - {_index + 1} - -
  • - ); - }); - } - - nextPagesArr = nextPagesArr.filter(entry => entry < pageCount); - let nextList1: Array = nextPagesArr.map((_val, _index) => { - return ( -
  • - { - goToPage(_val); - }} - > - {_val + 1} - -
  • - ); - }); - return ( - - ); -} diff --git a/services-js/access-boston/src/client/group-management/pagination-components/styling.ts b/services-js/access-boston/src/client/group-management/pagination-components/styling.ts deleted file mode 100644 index 7d81e9daa..000000000 --- a/services-js/access-boston/src/client/group-management/pagination-components/styling.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { css } from '@emotion/core'; -import { - OPTIMISTIC_BLUE_DARK, - CHARLES_BLUE, - WHITE, - FREEDOM_RED_DARK, - DEFAULT_TEXT, - GRAY_300, -} from '@cityofboston/react-fleet'; - -export const PAGINATION = css({ - marginBottom: '2.5em', - fontWeight: 'normal', - - a: { - cursor: 'pointer', - }, - - '&:hover': { - color: WHITE, - }, - - 'li span': { - color: WHITE, - }, - - 'span.pg-li-i': { - borderColor: CHARLES_BLUE, - color: DEFAULT_TEXT, - }, - - 'a.pg-li-i--a': { - backgroundColor: OPTIMISTIC_BLUE_DARK, - color: WHITE, - }, - - '.last-li': { - borderColor: DEFAULT_TEXT, - }, - - 'a.last-link': { - cursor: 'initial', - color: GRAY_300, - borderWidth: '2px', - borderStyle: 'solid', - borderColor: GRAY_300, - borderLeftColor: CHARLES_BLUE, - }, - - '.prev-next:hover': { - backgroundColor: FREEDOM_RED_DARK, - }, - - '.prev-next a:hover': { - backgroundColor: FREEDOM_RED_DARK, - }, -}); - -export const NORM_HOVER = css({ - a: { - opacity: 1, - borderColor: CHARLES_BLUE, - }, -}); - -export const HOVER_STYLES = css({ - a: { - backgroundColor: OPTIMISTIC_BLUE_DARK, - color: WHITE, - }, - '&:hover': { - backgroundColor: FREEDOM_RED_DARK, - color: WHITE, - opacity: 1, - }, -}); diff --git a/services-js/access-boston/src/client/group-management/search-component/AutosuggestWrapper.tsx b/services-js/access-boston/src/client/group-management/search-component/AutosuggestWrapper.tsx deleted file mode 100644 index cc87a5aab..000000000 --- a/services-js/access-boston/src/client/group-management/search-component/AutosuggestWrapper.tsx +++ /dev/null @@ -1,182 +0,0 @@ -/** @jsx jsx */ - -import { css, jsx } from '@emotion/core'; - -import { Component } from 'react'; - -import Autosuggest from 'react-autosuggest'; - -import { - BLACK, - GRAY_100, - WHITE, - FREEDOM_RED_DARK, -} from '@cityofboston/react-fleet'; - -import { Group, Person } from '../types'; - -interface Props { - id: string; - placeholder?: string; - searchText: string; - searchResults: Array; - onChange: (value: string) => void; - onSuggestionSelected: ( - selection: Group | Person, - selectionValue: string - ) => void; - cnArray?: Array; - isSelectionDuplication?: ( - nArray, - cn - ) => { - duplication: any; - warningLabel: string; - }; -} - -/** - * Wrapper component for Autosuggest package; because we will be filtering - * at a service level, we are mostly using it for its functionality at this - * time. -jm 9/16/19 - * - * https://github.com/moroshko/react-autosuggest - */ -export default class AutosuggestWrapper extends Component { - // When suggestion is clicked, Autosuggest needs to populate the input - // based on the clicked suggestion. Teach Autosuggest how to calculate the - // input value for every given suggestion. - getSuggestionValue = (suggestion: Group | Person): string => { - const { cnArray, isSelectionDuplication } = this.props; - let alreadyAdded = ''; - - if (cnArray && cnArray.length > 0 && isSelectionDuplication) { - const isSelection_Duplication = isSelectionDuplication( - cnArray, - suggestion.cn - ); - if (isSelection_Duplication.duplication) { - alreadyAdded = ` - ${isSelection_Duplication.warningLabel}`; - } - } - - const retVal = `${suggestion.displayName || suggestion.cn}${alreadyAdded}`; - return `${retVal}`; - }; - - // input handler - onChange = (_event, { newValue }): void => { - this.props.onChange(newValue); - }; - - handleSuggestion = (_event, { suggestion, suggestionValue }): void => { - const { cnArray, isSelectionDuplication } = this.props; - const { cn } = suggestion; - - if ( - !cnArray || - cnArray.length === 0 || - !isSelectionDuplication || - !isSelectionDuplication(cnArray, cn).duplication - ) { - this.props.onSuggestionSelected(suggestion, suggestionValue); - } - }; - - renderSuggestion = suggestion => { - const { cn, displayName } = suggestion; - const { cnArray, isSelectionDuplication } = this.props; - let warningLabel = ''; - - if (cnArray && cnArray.length > 0 && isSelectionDuplication) { - const isSelection_Duplication = isSelectionDuplication(cnArray, cn); - warningLabel = isSelection_Duplication.warningLabel; - } - if (displayName) { - return ( -
    - {displayName} - {warningLabel} - {warningLabel === '' && displayName !== cn && {cn}} -
    - ); - } else { - return ( - <> - {cn} - {warningLabel} - - ); - } - }; - - render() { - const suggestions = this.props.searchResults || []; - - const inputProps = { - placeholder: this.props.placeholder, - value: this.props.searchText, - onChange: this.onChange, - }; - - return ( - {}} - onSuggestionsClearRequested={() => {}} - getSuggestionValue={this.getSuggestionValue} - renderSuggestion={this.renderSuggestion} - inputProps={inputProps} - theme={AUTOSUGGEST_STYLING} - /> - ); - } -} - -const RENDER_SUGGESTIONS_WARNING_LABEL = css({ - color: FREEDOM_RED_DARK, -}); - -const SUGGESTION_STYLING = css({ - display: 'flex', - justifyContent: 'space-between', -}); - -const AUTOSUGGEST_STYLING = { - input: 'txt-f', - container: { - position: 'relative', - }, - suggestionsContainer: { - zIndex: 1, - position: 'absolute', - minWidth: '50%', - width: '100%', - maxHeight: '300px', - overflowY: 'scroll', - marginTop: '7px', - overflowX: 'hidden', - }, - suggestionsList: { - position: 'relative', - top: '-1px', - left: '3px', - margin: 0, - padding: 0, - color: BLACK, - backgroundColor: WHITE, - boxShadow: '0 1px 3px rgba(0, 0, 0, 0.2)', - borderBottomLeftRadius: '0.2rem', - borderBottomRightRadius: '0.2rem', - }, - suggestion: { - listStyle: 'none', - padding: '0.5rem 1rem', - cursor: 'pointer', - }, - suggestionHighlighted: { - backgroundColor: GRAY_100, - }, -}; diff --git a/services-js/access-boston/src/client/group-management/search-component/SearchComponent.stories.tsx b/services-js/access-boston/src/client/group-management/search-component/SearchComponent.stories.tsx deleted file mode 100644 index 54c7af1be..000000000 --- a/services-js/access-boston/src/client/group-management/search-component/SearchComponent.stories.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import React from 'react'; - -import { storiesOf } from '@storybook/react'; - -import { - fetchPersonSearch, - fetchPersonSearchRemaining, -} from '../fixtures/mock-fetch-person-data'; -import { - fetchGroupSearch, - fetchGroupSearchRemaining, -} from '../fixtures/mock-fetch-group-data'; - -import SearchComponent from './SearchComponent'; - -const mockGroup = { - members: [], -}; -const mockPerson = { - groups: [], -}; - -storiesOf('GroupManagementPage/SearchComponent', module) - .add('person add', () => ( - {}} - //@ts-ignore - selectedItem={mockGroup} - dns={[]} - /> - )) - .add('group add', () => ( - {}} - //@ts-ignore - selectedItem={mockPerson} - dns={[]} - /> - )) - .add('person search', () => ( - {}} - dns={[]} - /> - )) - .add('group search', () => ( - {}} - dns={[]} - /> - )) - .add('searching', () => ( - {}} - currentStatus="searching" - dns={[]} - /> - )) - .add('no results', () => ( - {}} - currentStatus="noResults" - dns={[]} - /> - )) - .add('server error/could not reach server', () => ( - {}} - currentStatus="fetchError" - dns={[]} - /> - )); diff --git a/services-js/access-boston/src/client/group-management/search-component/SearchComponent.tsx b/services-js/access-boston/src/client/group-management/search-component/SearchComponent.tsx deleted file mode 100644 index aff02b3c4..000000000 --- a/services-js/access-boston/src/client/group-management/search-component/SearchComponent.tsx +++ /dev/null @@ -1,338 +0,0 @@ -/** @jsx jsx */ - -import { css, jsx } from '@emotion/core'; - -import { MouseEvent, ReactNode, useEffect, useReducer } from 'react'; - -import { - FREEDOM_RED_DARK, - OPTIMISTIC_BLUE_DARK, - SANS, - SectionHeader, -} from '@cityofboston/react-fleet'; - -import { useDebounce } from '../../utility'; - -import { initialState, reducer, Status } from '../state/search'; - -import { Group, Mode, Person, View } from '../types'; - -import Spinner from '../Spinner'; -import Section from '../Section'; - -import AutosuggestWrapper from './AutosuggestWrapper'; - -interface Props { - mode: Mode; - view: View; - currentUserAllowedGroups?: string[]; - selectedItem?: Group | Person; - handleFetch: ( - value: string, - item, //?: Group | Person; - dns - ) => Promise; - handleSelectClick: (selection: any) => void; - currentStatus?: Status; // solely for Storybook - dns: String[]; - cnArray?: Array; - currentlist?: Array; -} - -/** - * Autocomplete search component. It will display suggestions in two - * different situations: - * - * view: initial - * Suggestions will be derived from all groups or all people. - * - * view: management - * Suggestions are derived from all groups or people, excluding the - * currently-selected item’s list of members or groups. - * - */ -export default function SearchComponent(props: Props) { - const fetchDelay = 1000; - - const [state, dispatch] = useReducer(reducer, initialState); - - const { currentStatus, mode, view, dns, cnArray, currentlist } = props; - const { searchStatus, searchText, searchResults, selection } = state; - - // Associate label with search field. - const inputId = `search-${view}-${mode}`; - - const debouncedValue = useDebounce(searchText, fetchDelay); - - const updateSuggestions = (result: Array): void => { - dispatch({ type: 'SEARCH/UPDATE_SUGGESTIONS', searchResults: result }); - }; - - // Note: remember that this is also fired when a user uses the keyboard - // to make a selection and then hits “enter” - const handleClick = (event: MouseEvent): void => { - event.preventDefault(); - - if (cnArray && cnArray.length > 1 && state.searchStatus === 'duplicate') { - handleChange(''); - } else { - const new_currentlist = - currentlist && currentlist.length > 0 ? currentlist : [{ cn: '' }]; - const newArr = new_currentlist.map(entry => entry.cn); - const isDup = isSelectionDuplication(newArr, selection.cn); - if (!isDup.duplication) { - selection.action = 'new'; - props.handleSelectClick(selection); - dispatch({ type: 'SEARCH/SUBMIT_SELECTION' }); - } else { - handleChange(''); - } - } - }; - - const handleChange = (text: string): void => { - dispatch({ - type: 'SEARCH/UPDATE_SEARCH_TEXT', - searchText: text, - }); - }; - - const handleSelection = ( - selection: Group | Person, - selectionValue: string - ): void => { - // console.log('SearchComponent > handleSelection > handleSelection > selection: ', selectionValue, selection); - dispatch({ type: 'SEARCH/UPDATE_SELECTION', selection, selectionValue }); - }; - - const handleFetch = (): void => { - dispatch({ type: 'SEARCH/UPDATE_STATUS', searchStatus: 'searching' }); - - if (view === 'initial') { - props - .handleFetch(searchText, null, dns) - .then(result => updateSuggestions(result)) - .catch(() => - dispatch({ type: 'SEARCH/UPDATE_STATUS', searchStatus: 'fetchError' }) - ); - } else { - props - .handleFetch(searchText, props.selectedItem as Person | Group, dns) - .then(result => updateSuggestions(result)); - } - }; - - function statusIndicator(): ReactNode { - if (searchStatus === 'noResults') { - return No results found; - } else if (searchStatus === 'fetchError') { - return ( - Server error: failed to fetch - ); - } else if (searchStatus === 'searching') { - return ( - - Searching... - - ); - } - - return null; - } - - const isSelectionDuplication = (cnArray: Array, cn: string) => { - let warningLabel = ''; - let duplication = false; - - if (cnArray && cnArray.length > 0) { - const newArr = cnArray.map(entry => { - if (entry && entry.indexOf('=') > -1) { - return entry.split('=')[1]; - } else { - return entry; - } - }); - - if (newArr && newArr.indexOf(cn) > -1) { - warningLabel = 'Already Added'; - duplication = true; - } - } - - return { duplication, warningLabel }; - }; - - const buttonLabel = () => { - if (cnArray && cnArray.length > 0 && state.searchStatus === 'duplicate') { - return textCopy[view][mode].clear; - } else { - return textCopy[view][mode].button; - } - }; - - const buttonDisabled = () => { - if (cnArray && cnArray.length > 0 && state.searchStatus === 'duplicate') { - return false; - } else { - return !selection.cn; - } - }; - - const defaultSearchText = (_newText: any = 0) => { - if (_newText && typeof _newText === 'string') { - return _newText; - } else { - return searchText; - } - }; - - // used solely for Storybook; ensures specified status state is displayed. - useEffect(() => { - if (currentStatus) { - dispatch({ type: 'SEARCH/UPDATE_STATUS', searchStatus: currentStatus }); - } - }, []); - - // Clear typed input and suggestions if search type changes. - useEffect(() => { - if (searchText.length > 0) dispatch({ type: 'SEARCH/RESET_STATE' }); - }, [mode]); - - // Clear suggestions if search text has been removed. - useEffect(() => { - if (!searchText && !currentStatus) { - dispatch({ type: 'SEARCH/CLEAR_SUGGESTIONS' }); - } - }, [searchText]); - - // Debounce to minimize fetching from server; don’t trigger fetch until - // at least two characters have been input; prevent fetch if selection - // has been made. - useEffect(() => { - if ( - debouncedValue && - debouncedValue.length >= 2 && - searchText !== state.selectionValue - ) { - handleFetch(); - } - }, [debouncedValue]); - - return ( -
    - - -
    -
    - - -
    -
    - - - {statusIndicator()} -
    - - -
    -
    -
    -
    - ); -} - -const textCopy = { - initial: { - person: { - title: 'Person search', - label: 'Find a person', - button: 'Select', - clear: 'Clear', - }, - group: { - title: 'Group search', - label: 'Find a group', - button: 'Select', - clear: 'Clear', - }, - }, - management: { - person: { - title: 'Add a new member', - label: 'Find a person to add', - button: 'Add member', - clear: 'Clear', - }, - group: { - title: 'Add to a group', - label: 'Find a group to add', - button: 'Add group', - clear: 'Clear', - }, - }, -}; - -const FIELD_CONTAINER_STYLING = css({ - position: 'relative', - - '.status': { - position: 'absolute', - top: '50%', - transform: 'translateY(-50%)', - right: '1rem', - - display: 'flex', - - fontFamily: SANS, - }, - - '.no-results': { - color: FREEDOM_RED_DARK, - }, - - '.working': { - color: OPTIMISTIC_BLUE_DARK, - }, -}); - -const INPUTS_STYLING = css({ - display: 'flex', - - '> div': { - width: '100%', - }, - - button: { - marginLeft: '0.5em', - flexShrink: 0, - }, - - 'input[type="search"]': { - '&::-webkit-search-cancel-button': { - WebkitAppearance: 'none', - }, - '&::-webkit-calendar-picker-indicator': { - display: 'none', - }, - }, -}); diff --git a/services-js/access-boston/src/client/group-management/state/app.ts b/services-js/access-boston/src/client/group-management/state/app.ts deleted file mode 100644 index 3189bb9fb..000000000 --- a/services-js/access-boston/src/client/group-management/state/app.ts +++ /dev/null @@ -1,100 +0,0 @@ -/* eslint no-console: 0 */ -// todo - -import { - Group, - Mode, - Person, - View, - CurrentPage, - pageSize, - startPage, - currentPage, - pageCount, - PageCount, -} from '../types'; - -export type ActionTypes = - | 'APP/CHANGE_VIEW' - | 'APP/CHANGE_MODE' - | 'APP/RESET_STATE' - | 'APP/SET_SELECTED' - | 'APP/CLEAR_SELECTED' - | 'APP/SET_OUS' - | 'APP/SET_API' - | 'APP/CHANGE_PAGE' - | 'APP/CHANGE_PAGECOUNT' - | 'APP/SET_ADMIN_MIN_GROUPS'; - -interface Action { - type: ActionTypes; - view?: View; - mode?: Mode; - selected?: Group | Person; - ous?: [string]; - api?: string; - currentPage?: CurrentPage; - pageCount?: PageCount; - dns?: []; -} - -export const initialState = { - view: 'initial', - mode: 'group', - selected: {}, - ous: [], - api: '', - pageSize, - startPage, - currentPage, - pageCount, - adminMinGroups: [], -}; - -export const reducer = (state, action: Partial) => { - //@ts-ignore todo - // console.info(action.type); - - const currUserOUs = state.ous; - const currAdminGroup = state.adminMinGroups; - const newInitState = { - ...initialState, - adminMinGroups: currAdminGroup, - ous: currUserOUs, - }; - - switch (action.type) { - case 'APP/CHANGE_VIEW': - return { ...state, view: action.view }; - - case 'APP/CHANGE_MODE': - return { ...state, mode: action.mode }; - - case 'APP/SET_SELECTED': - return { ...state, selected: action.selected }; - - case 'APP/SET_OUS': - return { ...state, ous: action.ous }; - - case 'APP/SET_ADMIN_MIN_GROUPS': - return { ...state, adminMinGroups: action.dns }; - - case 'APP/SET_API': - return { ...state, api: action.api }; - - case 'APP/CLEAR_SELECTED': - return { ...state, selected: {} }; - - case 'APP/RESET_STATE': - return newInitState; - - case 'APP/CHANGE_PAGE': - return { ...state, currentPage: action.currentPage }; - - case 'APP/CHANGE_PAGECOUNT': - return { ...state, pageCount: action.pageCount }; - - default: - return state; - } -}; diff --git a/services-js/access-boston/src/client/group-management/state/data-helpers.test.ts b/services-js/access-boston/src/client/group-management/state/data-helpers.test.ts deleted file mode 100644 index 5673340b4..000000000 --- a/services-js/access-boston/src/client/group-management/state/data-helpers.test.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { getAllCns, getCommonName, toGroup, toPerson } from './data-helpers'; - -const uniqueMembers = [ - '', - 'cn=132367,cn=Internal Users,dc=boston,dc=cob', - 'cn=Ignis Scientia,cn=Internal Users,dc=boston,dc=cob', - 'cn=Laguna Loire,cn=Internal Users,dc=boston,dc=cob', -]; - -// const groupList = ['ANML02_LostFound', 'BPD_Districts']; - -import allPeople from '../fixtures/people.json'; -import allGroups from '../fixtures/groups.json'; - -describe('getCommonName', () => { - it('extracts a numerical cn', () => { - const result = getCommonName(uniqueMembers[1]); - - expect(result).toBe('132367'); - }); - - it('extracts an alphabetical cn', () => { - const result = getCommonName(uniqueMembers[3]); - - expect(result).toBe('Laguna Loire'); - }); -}); - -describe('toGroup', () => { - it('returns a Group object', () => { - const result = toGroup(allGroups[0]); - - expect(result).toMatchObject({ - isAvailable: true, - status: 'current', - cn: 'ANML02_LostFound', - members: ['132367', 'Ignis Scientia', 'Laguna Loire'], - }); - }); -}); - -describe('toPerson', () => { - it('returns a Person object', () => { - const result = toPerson(allPeople[0]); - - expect(result).toMatchObject({ - isAvailable: true, - status: 'current', - cn: '000296', - groups: ['BPD_Districts'], - givenName: 'Terra', - sn: 'Howard', - mail: 'terra.howard@boston.gov', - displayName: 'Terra Howard', - }); - }); -}); - -describe('getAllCns', () => { - it('returns an array of person cn strings', () => { - const result = getAllCns(uniqueMembers); - - expect(result).toEqual(['132367', 'Ignis Scientia', 'Laguna Loire']); - }); -}); diff --git a/services-js/access-boston/src/client/group-management/state/data-helpers.ts b/services-js/access-boston/src/client/group-management/state/data-helpers.ts deleted file mode 100644 index 970585285..000000000 --- a/services-js/access-boston/src/client/group-management/state/data-helpers.ts +++ /dev/null @@ -1,131 +0,0 @@ -import { - CommonAttributes, - Group, - ItemStatus, - Person, - pageSize, - Action, -} from '../types'; -import { chunkArray } from '../fixtures/helpers'; - -/** - * Individual members are stored in the uniqueMembers array of a group - * as a string: - * - * "cn=132367,cn=Internal Users,dc=boston,dc=cob", - * "cn=Laguna Loire,cn=Internal Users,dc=boston,dc=cob" - */ -export function getCommonName(text: string): string { - // starting at the beginning of the string, capture everything after “cn=”, - // up to the first comma. - const match = text.match(/^(?:cn=)([\w\s]*)/); - - if (match) { - return match[1]; - } - - return ''; -} - -/** - * Given an array of uniqueMembers, return array of person CN strings. - */ -export function getAllCns(uniqueMember: string[]): string[] { - return uniqueMember.reduce( - (acc, member) => { - const result = getCommonName(member); - - if (result.length > 0) { - return [...acc, result]; - } else { - return acc; - } - }, - [] as string[] - ); -} - -/** - * Accepts a group object from server response, and returns a usable - * Group object. - */ -export function toGroup( - dataObject, - _dns: Array = [], - _ous: Array = [] -): Group { - let isAvailable = - dataObject.canModify !== undefined ? dataObject.canModify : true; - let inDomain = true; - - if (_ous.length > 0) { - inDomain = isDomainNameInOUs(dataObject.dn, _ous); - if (!inDomain) { - isAvailable = false; - } - } - - const chunkedResults = - dataObject.uniquemember && dataObject.uniquemember.length > 0 - ? chunkArray(dataObject.uniquemember, pageSize) - : [[]]; - - const retObj = { - ...commonAttributes(dataObject), - members: dataObject.uniquemember || [], - isAvailable, - chunked: chunkedResults, - }; - // console.log('toGroup > retObj: ', retObj, '\n------------'); - - return retObj; -} - -/** - * Accepts a person object from server response, and returns a usable - * Person object. - */ -export function toPerson(dataObject): Person { - const chunkedResults = - dataObject.ismemberof && dataObject.ismemberof.length > 0 - ? chunkArray(dataObject.ismemberof, pageSize) - : [[]]; - // console.log('toPerson > dataObject: '); - // console.log('chunked: ', chunkedResults,'\n------------'); - const retObj = { - ...commonAttributes(dataObject), - groups: dataObject.ismemberof || [], - chunked: chunkedResults, - givenName: dataObject.givenname || '', - sn: dataObject.sn || '', - mail: dataObject.mail || '', - isAvailable: !dataObject.inactive, - }; - // console.log('toGroup > retObj: ', retObj, '\n------------'); - - return retObj; -} - -function commonAttributes(dataObject): CommonAttributes { - // console.log('CommonAttributes dataObj: ', dataObject); - return { - cn: dataObject.cn, - dn: dataObject.dn || '', - displayName: dataObject.displayname || dataObject.cn, - status: 'current' as ItemStatus, - action: '' as Action, - }; -} - -export const isDomainNameInOUs = (dn: string, dns: Array) => { - let splitDN: any = dn.split(','); - splitDN.shift(); - splitDN = splitDN - .toString() - .trim() - .toLowerCase(); - const retArr = dns.filter( - entry => entry.trim().toLocaleLowerCase() === splitDN - ); - return retArr.length > 0; -}; diff --git a/services-js/access-boston/src/client/group-management/state/list.ts b/services-js/access-boston/src/client/group-management/state/list.ts deleted file mode 100644 index 90a8c61b7..000000000 --- a/services-js/access-boston/src/client/group-management/state/list.ts +++ /dev/null @@ -1,90 +0,0 @@ -/* eslint no-console: 0 */ -// todo - -import { Group, Person } from '../types'; - -export type ActionTypes = - | 'LIST/LOAD_LIST' - | 'LIST/CLEAR_LIST' - | 'LIST/ADD_ITEM' - | 'LIST/REMOVE_ITEM' - | 'LIST/TOGGLE_ITEM_STATUS' - | 'LIST/RETURN_ITEM' - | 'LIST/DELETE_ITEM'; - -interface Action { - type: ActionTypes; - list?: Array; - item?: Group | Person; - cn?: string; -} - -export const reducer = (list, action: Partial) => { - //@ts-ignore todo - // console.info(action.type); - - switch (action.type) { - case 'LIST/LOAD_LIST': - if (action.list) { - return action.list.map(item => ({ - ...item, - status: 'current', - })); - } else { - return list; - } - - case 'LIST/CLEAR_LIST': - return []; - - case 'LIST/ADD_ITEM': - return [ - { - ...action.item, - status: 'add', - }, - ...list, - ]; - - case 'LIST/DELETE_ITEM': - return list.filter(item => { - const action_item: any = action.item; - if (item.cn !== action_item.cn) { - return { ...item }; - } - }); - - case 'LIST/REMOVE_ITEM': - return list.map(item => { - if (item.cn === action.cn) { - return { ...item, status: 'remove' }; - } else { - return item; - } - }); - - case 'LIST/TOGGLE_ITEM_STATUS': - return list.map(item => { - if (action.item && item.cn === action.item.cn) { - return { - ...item, - status: item.status === 'remove' ? 'current' : 'remove', - }; - } else { - return item; - } - }); - - case 'LIST/RETURN_ITEM': - return list.map(item => { - if (item.cn === action.cn) { - return { ...item, status: 'current' }; - } else { - return item; - } - }); - - default: - return list; - } -}; diff --git a/services-js/access-boston/src/client/group-management/state/search.ts b/services-js/access-boston/src/client/group-management/state/search.ts deleted file mode 100644 index a10ca8460..000000000 --- a/services-js/access-boston/src/client/group-management/state/search.ts +++ /dev/null @@ -1,89 +0,0 @@ -/* eslint no-console: 0 */ -// todo - -import { Group, Person } from '../types'; - -export type Status = - | 'searching' - | 'idle' - | 'noResults' - | 'fetchError' - | 'duplicate'; - -type ActionTypes = - | 'SEARCH/UPDATE_STATUS' - | 'SEARCH/UPDATE_SEARCH_TEXT' - | 'SEARCH/UPDATE_SUGGESTIONS' - | 'SEARCH/CLEAR_SUGGESTIONS' - | 'SEARCH/UPDATE_SELECTION' - | 'SEARCH/CLEAR_SELECTION' - | 'SEARCH/SUBMIT_SELECTION' - | 'SEARCH/RESET_STATE'; - -interface Action { - type: ActionTypes; - searchResults?: Array; - searchStatus?: Status; - searchText?: string; - selection?: Group | Person; - selectionValue?: string; -} - -export const initialState = { - searchResults: [], - searchStatus: 'idle', - searchText: '', - selection: {} as Person, - selectionValue: '', -}; - -export const reducer = (state, action: Partial) => { - //@ts-ignore todo - // console.info(action.type); - - switch (action.type) { - case 'SEARCH/UPDATE_STATUS': - return { ...state, searchStatus: action.searchStatus }; - - case 'SEARCH/UPDATE_SEARCH_TEXT': - return { ...state, searchText: action.searchText, searchStatus: 'idle' }; - - case 'SEARCH/UPDATE_SUGGESTIONS': - return { - ...state, - searchResults: action.searchResults, - searchStatus: - state.searchText.indexOf('Already Added') > -1 - ? 'duplicate' - : action.searchResults && action.searchResults.length > 0 - ? 'idle' - : 'noResults', - }; - - case 'SEARCH/CLEAR_SUGGESTIONS': - return { - ...state, - searchResults: [], - searchStatus: 'idle', - }; - - case 'SEARCH/UPDATE_SELECTION': - return { - ...state, - selection: action.selection, - selectionValue: action.selectionValue, - }; - - case 'SEARCH/CLEAR_SELECTION': - return { ...state, selection: {} as Person, selectionValue: '' }; - - case 'SEARCH/SUBMIT_SELECTION': - return { ...state, searchText: '', searchResults: [] }; - - case 'SEARCH/RESET_STATE': - return initialState; - - default: - return state; - } -}; diff --git a/services-js/access-boston/src/client/group-management/types.ts b/services-js/access-boston/src/client/group-management/types.ts deleted file mode 100644 index df2e8c995..000000000 --- a/services-js/access-boston/src/client/group-management/types.ts +++ /dev/null @@ -1,43 +0,0 @@ -export type View = 'initial' | 'management' | 'review' | 'confirmation'; -export type Mode = 'person' | 'group'; -export type ItemStatus = 'current' | 'add' | 'remove' | 'new'; -export type Action = '' | 'new'; -export type ShowLabel = true | false; -export type CurrentPage = number; -export type PageCount = number; -export type PagedResults = Array>; - -export const pageSize = 50; -export const startPage = 0; -export const currentPage = startPage; -export const pageCount = 1; - -export interface CommonAttributes { - dn: string; - cn: string; - displayName: string; - - status: ItemStatus; - action: Action; - - // A group is available if the user is an app owner and may add/remove users; - // a person is available if they are not set as “inactive”. - isAvailable?: boolean; -} - -// export interface ChunkedArray { -// chunkedArray: any; -// } - -export interface Group extends CommonAttributes { - members: string[]; - chunked?: any; -} - -export interface Person extends CommonAttributes { - groups: string[]; - chunked?: any; - givenName: string; - sn: string; - mail: string; -} diff --git a/services-js/access-boston/src/client/storage/IdentityVerificationRequest.ts b/services-js/access-boston/src/client/storage/IdentityVerificationRequest.ts deleted file mode 100644 index e14693e36..000000000 --- a/services-js/access-boston/src/client/storage/IdentityVerificationRequest.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { View, IdentityVerificationStep } from '../../client/confirmid/types'; - -const STEPS: IdentityVerificationStep[] = [ - 'enterId', - 'validate', - 'review', - 'success', -]; - -export const getSteps = () => { - return [...STEPS]; -}; - -const VIEWS: View[] = [ - 'enterId', - 'validate', - 'review', - 'success', - 'failure', - 'quit', -]; - -export const getViews = () => { - return [...VIEWS]; -}; diff --git a/services-js/access-boston/src/client/styles.ts b/services-js/access-boston/src/client/styles.ts deleted file mode 100644 index ac105f313..000000000 --- a/services-js/access-boston/src/client/styles.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { css } from 'emotion'; - -export const HEADER_HEIGHT = 47; - -const MAIN_STYLE = css({ - // accommodates our thinner-than-Boston.gov header - paddingTop: HEADER_HEIGHT, -}); - -export const MAIN_CLASS = `mn ${MAIN_STYLE}`; - -/** Useful HTML attributes for password fields. */ -export const DEFAULT_PASSWORD_ATTRIBUTES = { - type: 'password', - required: true, - spellCheck: false, - autoFocus: false, - autoCapitalize: 'off', - autoCorrect: 'off', -}; diff --git a/services-js/access-boston/src/client/utility.ts b/services-js/access-boston/src/client/utility.ts deleted file mode 100644 index db31a9ef3..000000000 --- a/services-js/access-boston/src/client/utility.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { useEffect, useState, useRef } from 'react'; - -/** - * Use previous props or state values. - */ -export function usePrevious(value: any) { - const ref = useRef(); - - useEffect(() => { - ref.current = value; - }); - - return ref.current; -} - -export function useDebounce(value, delay) { - const [debouncedValue, setDebouncedValue] = useState(value); - - useEffect(() => { - const handler = setTimeout(() => { - setDebouncedValue(value); - }, delay); - - return () => { - clearTimeout(handler); - }; - }, [value, delay]); - - return debouncedValue; -} - -/** - * Capitalize first character of a word or sentence. - * @param word string - */ -export function capitalize(word: string): string { - return word.slice(0, 1).toUpperCase() + word.slice(1); -} diff --git a/services-js/access-boston/src/integration/change-password.testcafe.ts b/services-js/access-boston/src/integration/change-password.testcafe.ts deleted file mode 100644 index 25f664142..000000000 --- a/services-js/access-boston/src/integration/change-password.testcafe.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { Selector } from 'testcafe'; - -import LoginFormModel from './models/LoginFormModel'; -import PasswordPageModel from './models/PasswordPageModel'; -import { fixtureUrl } from './testcafe-helpers'; - -fixture('Change password').page(fixtureUrl('/')); - -test('Change password', async t => { - const loginPage = new LoginFormModel(); - await loginPage.logIn(t, 'CON02141'); - - await t.click(Selector('a').withText('Change Password')); - - const passwordPage = new PasswordPageModel(); - - // First try is a "wrong password" to test the error case - await t - .typeText(passwordPage.currentPasswordField, 'wrong-password', { - replace: true, - }) - .typeText(passwordPage.newPasswordField, 'newPassword2018', { - replace: true, - }) - .typeText(passwordPage.confirmPasswordField, 'newPassword2018', { - replace: true, - }) - .click(passwordPage.submitButton); - - await t - .expect(Selector('body').innerText) - .contains('You have entered an invalid current password.'); - - // Corrects the password to see success - await t - .typeText(passwordPage.currentPasswordField, 'correct-password', { - replace: true, - }) - .click(passwordPage.submitButton); - - await t - .expect(Selector('body').innerText) - .contains('Your password has been changed!'); -}); diff --git a/services-js/access-boston/src/integration/forgot-password.testcafe.ts b/services-js/access-boston/src/integration/forgot-password.testcafe.ts deleted file mode 100644 index f8b51bf58..000000000 --- a/services-js/access-boston/src/integration/forgot-password.testcafe.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { Selector } from 'testcafe'; - -import LoginFormModel from './models/LoginFormModel'; -import PasswordPageModel from './models/PasswordPageModel'; -import { fixtureUrl } from './testcafe-helpers'; -import PageModel from './models/PageModel'; - -fixture('Forgot password').page(fixtureUrl('/forgot')); - -test('Login does not apply to regular site', async t => { - const loginPage = new LoginFormModel(); - await loginPage.logIn(t, 'CON02141'); - - const page = new PageModel(); - await t.expect(page.sectionHeader.withText('FORGOT PASSWORD').exists).ok(); - - // Even though we logged in through the "forgot" flow, we won't be logged in - // in the main app so going to the homepage should put us on the other login - // form. - await t.navigateTo('/'); - - // Match here is that this posts to "/assert", which is the login flow's SAML - // endpoint. The forgot password flow’s SAML endpoint is /assert-forgot - // - // This is our check to see that we're on the "normal" login page, which - // indicates that the forgot password session wasn't valid for the page we - // navigated to. - await t.expect(Selector('form').getAttribute('action')).match(/\/assert$/); -}); - -test('Reset password', async t => { - const loginPage = new LoginFormModel(); - await loginPage.logIn(t, 'CON02141'); - - const passwordPage = new PasswordPageModel(); - await t - .typeText(passwordPage.newPasswordField, 'newPassword2018', { - replace: true, - }) - .typeText(passwordPage.confirmPasswordField, 'newPassword2018', { - replace: true, - }) - .click(passwordPage.submitButton); - - await t.expect(Selector('body').withText('RESET SUCCESSFUL').exists).ok(); -}); diff --git a/services-js/access-boston/src/integration/models/DeviceRegistrationPageModel.ts b/services-js/access-boston/src/integration/models/DeviceRegistrationPageModel.ts deleted file mode 100644 index 5c183055b..000000000 --- a/services-js/access-boston/src/integration/models/DeviceRegistrationPageModel.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Selector } from 'testcafe'; -import PageModel from './PageModel'; - -/** - * Page model for registering an MFA device. - */ -export default class DeviceRegistrationPageModel extends PageModel { - form = Selector('.mn form'); - - phoneLink = this.form.find('a').withText('get codes via phone call or text'); - smsRadioButton = this.form.find('input[value=sms]'); - voiceRadioButton = this.form.find('input[value=voice]'); - phoneNumberField = this.form.find('input[name=phoneNumber]'); - - emailLink = this.form.find('a').withText('Get codes via personal email'); - emailField = this.form.find('input[name=email]'); - - submitButton = this.form.find('button[type=submit]'); - - modal = Selector('.md'); - codeField = this.modal.find('input[name=code]'); - codeSubmitButton = this.modal.find('button'); - - submitVerificationCode = (t: TestController, code: string) => - t.typeText(this.codeField, code).click(this.codeSubmitButton); -} diff --git a/services-js/access-boston/src/integration/models/LoginFormModel.ts b/services-js/access-boston/src/integration/models/LoginFormModel.ts deleted file mode 100644 index 5ebe82a32..000000000 --- a/services-js/access-boston/src/integration/models/LoginFormModel.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Selector } from 'testcafe'; -import PageModel from './PageModel'; -import { isReactRunning } from '../testcafe-helpers'; - -export default class LoginFormModel extends PageModel { - root = Selector('form'); - userIdField = this.root.find('input[name=userId]'); - submitButton = this.root.find('input[type=submit]'); - - logIn(t: TestController, userId: string): Promise { - return ( - t - .typeText(this.userIdField, userId, { - replace: true, - }) - .click(this.submitButton) - // Make sure the app is going after we’ve submitted - .expect(isReactRunning()) - .ok({ timeout: 30000 }) - ); - } -} diff --git a/services-js/access-boston/src/integration/models/PageModel.ts b/services-js/access-boston/src/integration/models/PageModel.ts deleted file mode 100644 index d72d23694..000000000 --- a/services-js/access-boston/src/integration/models/PageModel.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { Selector } from 'testcafe'; - -export default class PageModel { - sectionHeader = Selector('.sh'); -} diff --git a/services-js/access-boston/src/integration/models/PasswordPageModel.ts b/services-js/access-boston/src/integration/models/PasswordPageModel.ts deleted file mode 100644 index da3cb9637..000000000 --- a/services-js/access-boston/src/integration/models/PasswordPageModel.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Selector } from 'testcafe'; -import PageModel from './PageModel'; - -/** - * Page model object that can work for both change password and forgot password - * fields. - */ -export default class PasswordPageModel extends PageModel { - form = Selector('.mn form'); - - currentPasswordField = this.form.find('input[name=password]'); - newPasswordField = this.form.find('input[name=newPassword]'); - confirmPasswordField = this.form.find('input[name=confirmPassword]'); - - submitButton = this.form.find('button[type=submit]'); - - submit(t: TestController): Promise { - return t.click(this.submitButton); - } -} diff --git a/services-js/access-boston/src/integration/register-device.testcafe.ts b/services-js/access-boston/src/integration/register-device.testcafe.ts deleted file mode 100644 index 2564d2888..000000000 --- a/services-js/access-boston/src/integration/register-device.testcafe.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { Selector } from 'testcafe'; - -import LoginFormModel from './models/LoginFormModel'; -import DeviceRegistrationPageModel from './models/DeviceRegistrationPageModel'; -import PageModel from './models/PageModel'; - -import { - fixtureUrl, - makeGraphQlLogger, - requestBodyPredicate, - isReactRunning, -} from './testcafe-helpers'; - -const graphqlLogger: RequestLogger = makeGraphQlLogger(); - -// Starting at "/" will give us a redirect to "/register" once the frontend -// realizes that the user hasn’t been registered. -fixture('Device Registration') - .requestHooks(graphqlLogger) - .page(fixtureUrl('/')) - .beforeEach(async t => { - const loginPage = new LoginFormModel(); - // "NEW" makes the fake give us a user that needs to go through registation. - // Otherwise the mfa page might redirect us away. - await loginPage.logIn(t, 'NEW02141'); - await t - .navigateTo('/mfa') - .expect(isReactRunning()) - .ok({ timeout: 30000 }); - }); - -test.only('Device registration', async t => { - const mfaPage = new DeviceRegistrationPageModel(); - - await t - .click(mfaPage.emailLink) - .typeText(mfaPage.emailField, 'test@boston.gov') - .pressKey('tab') - .expect(mfaPage.form.innerText) - .contains( - 'Please use a personal email', - 'Email address validation appears on blur' - ); - - await t - .typeText(mfaPage.emailField, 'test@example.com', { replace: true }) - .click(mfaPage.submitButton) - .expect( - graphqlLogger.contains(requestBodyPredicate('mutation AddMfaDevice')) - ) - .ok('Submitting form makes server call'); - - // Clears out the previous call so we can test the resend case. - graphqlLogger.clear(); - - await t - .click(Selector('button').withText('Resend the code')) - .expect( - graphqlLogger.contains(requestBodyPredicate('mutation AddMfaDevice')) - ) - .ok('Resend link makes a second server call'); - - await t - .click(Selector('button').withText('try a different number or email')) - .click(mfaPage.phoneLink) - .typeText(mfaPage.phoneNumberField, '617 555-1212') - .click(mfaPage.voiceRadioButton) - .click(mfaPage.submitButton) - .expect(mfaPage.modal.innerText) - .contains('Please pick up!', 'Can switch to voice code') - .expect(mfaPage.modal.innerText) - .contains('(xxx) xxx-xx12'); - - await mfaPage - .submitVerificationCode(t, '999999') - .expect( - mfaPage.modal.withText('That code didn’t seem right. Can you try again?') - .exists - ) - .ok('Error message from 999999 code appears'); - - await mfaPage.submitVerificationCode(t, '123456'); - - const donePage = new PageModel(); - await t - .expect(donePage.sectionHeader.withText('YOU’RE ALL SET!').exists) - .ok('On done page'); - - // Now we go home to make sure that the session was destroyed. This should - // redirect to login. - await t.navigateTo('/'); - - const loginPage = new LoginFormModel(); - await t.expect(loginPage.userIdField.exists).ok('On login page'); -}); diff --git a/services-js/access-boston/src/integration/registration-flow.testcafe.ts b/services-js/access-boston/src/integration/registration-flow.testcafe.ts deleted file mode 100644 index a2e4a7712..000000000 --- a/services-js/access-boston/src/integration/registration-flow.testcafe.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { Selector } from 'testcafe'; - -import LoginFormModel from './models/LoginFormModel'; -import PasswordPageModel from './models/PasswordPageModel'; -import PageModel from './models/PageModel'; -import DeviceRegistrationPageModel from './models/DeviceRegistrationPageModel'; - -import { fixtureUrl } from './testcafe-helpers'; - -// Starting at "/" will give us a redirect to "/register" once the frontend -// realizes that the user hasn’t been registered. -fixture('New User Registration').page(fixtureUrl('/')); - -test('Registration with new password and MFA', async t => { - const loginPage = new LoginFormModel(); - // "NEW" makes the fake give us a user that needs to go through registation. - await loginPage.logIn(t, 'NEW02141'); - - const registerPage = new PageModel(); - await t - .expect( - registerPage.sectionHeader.withText('WELCOME TO ACCESS BOSTON!').exists - ) - .ok(); - - await t.click(Selector('.btn').withText('GET STARTED')); - - const passwordPage = new PasswordPageModel(); - await t - .typeText(passwordPage.currentPasswordField, 'correct-password') - .typeText(passwordPage.newPasswordField, 'newPassword2018') - .typeText(passwordPage.confirmPasswordField, 'newPassword2018') - .click(passwordPage.submitButton); - - const mfaPage = new DeviceRegistrationPageModel(); - - await t - .typeText(mfaPage.phoneNumberField, '617 555-1212') - .click(mfaPage.submitButton) - .typeText(mfaPage.codeField, '555555') - .click(mfaPage.codeSubmitButton); - - const donePage = new PageModel(); - await t - .expect(donePage.sectionHeader.withText('YOU’RE ALL SET!').exists) - .ok('On done page'); -}); - -test('Registration with just new password', async t => { - const loginPage = new LoginFormModel(); - // This is a special login that doesn't need an MFA device - await loginPage.logIn(t, 'NEW88888'); - - const registerPage = new PageModel(); - await t - .expect( - registerPage.sectionHeader.withText('WELCOME TO ACCESS BOSTON!').exists - ) - .ok(); - - await t.click(Selector('.btn').withText('GET STARTED')); - - const passwordPage = new PasswordPageModel(); - await t - .typeText(passwordPage.currentPasswordField, 'correct-password') - .typeText(passwordPage.newPasswordField, 'newPassword2018') - .typeText(passwordPage.confirmPasswordField, 'newPassword2018') - .click(passwordPage.submitButton); - - const donePage = new PageModel(); - await t - .expect(donePage.sectionHeader.withText('YOU’RE ALL SET!').exists) - .ok('On done page'); -}); diff --git a/services-js/access-boston/src/integration/testcafe-helpers.ts b/services-js/access-boston/src/integration/testcafe-helpers.ts deleted file mode 100644 index 4673023bd..000000000 --- a/services-js/access-boston/src/integration/testcafe-helpers.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { RequestLogger, ClientFunction } from 'testcafe'; - -const DEFAULT_SERVER_URL = 'http://localhost:3000'; - -export function fixtureUrl(path: string) { - return `${process.env.TEST_SERVER_URL || DEFAULT_SERVER_URL}${path}`; -} - -// This doesn't seem to be exported directly as a type from TestCafe. -export type TestCafeRequest = RequestLogger['requests'][0]; - -export function makeGraphQlLogger() { - return RequestLogger(/\/graphql/, { - logRequestBody: true, - stringifyRequestBody: true, - }); -} - -export function requestBodyPredicate(str: string) { - return ({ request }: TestCafeRequest) => - (request.body as string).includes(str); -} - -// APP_RUNNING is set in _app.tsx’s componentDidMount. It's useful to wait on to -// make sure we don’t interact with forms before the JS has started running, -// otherwise our values will get stomped by Formik’s initialValues. -export const isReactRunning = ClientFunction( - () => !!(window as any).APP_RUNNING -); diff --git a/services-js/access-boston/src/lib/AppsRegistry.test.ts b/services-js/access-boston/src/lib/AppsRegistry.test.ts deleted file mode 100644 index dda560d85..000000000 --- a/services-js/access-boston/src/lib/AppsRegistry.test.ts +++ /dev/null @@ -1,50 +0,0 @@ -import fs from 'fs'; -import path from 'path'; -import yaml from 'js-yaml'; - -import AppsRegistry from './AppsRegistry'; - -describe('appsForUserTypeAndGroups', () => { - let appsRegistry: AppsRegistry; - - beforeEach(() => { - const yamlFixture = yaml.safeLoad( - fs.readFileSync( - path.resolve(__dirname, '../../fixtures/apps.yaml'), - 'utf-8' - ) - ); - - appsRegistry = new AppsRegistry(yamlFixture); - }); - - it('returns just the "everyone" category for no groups', () => { - expect(appsRegistry.appsForGroups([], false, 'CH')).toMatchSnapshot(); - }); - - it('returns apps for a group', () => { - // This checks that categories with no apps are not returned - expect( - appsRegistry.appsForGroups(['SG_AB_IAM_TEAM'], false, 'CH') - ).toMatchSnapshot(); - }); - - it('returns apps for an agency', () => { - // This checks that categories with no apps are not returned - expect( - appsRegistry.appsForGroups(['SG_AB_IAM_TEAM'], false, 'BPS') - ).toMatchSnapshot(); - }); - - it('doesn’t return agency apps if none is specified', () => { - // This checks that categories with no apps are not returned - expect( - appsRegistry.appsForGroups(['SG_AB_IAM_TEAM'], false, null) - ).toMatchSnapshot(); - }); - - it('returns apps that require an MFA device', () => { - // This checks that categories with no apps are not returned - expect(appsRegistry.appsForGroups([], true, 'CH')).toMatchSnapshot(); - }); -}); diff --git a/services-js/access-boston/src/lib/AppsRegistry.ts b/services-js/access-boston/src/lib/AppsRegistry.ts deleted file mode 100644 index b558c6a6f..000000000 --- a/services-js/access-boston/src/lib/AppsRegistry.ts +++ /dev/null @@ -1,167 +0,0 @@ -import yaml from 'js-yaml'; - -export interface Notice { - label: string; - pretext: string; - text: string; -} - -export interface AppsCategory { - title: string; - showRequestAccessLink: boolean; - icons: boolean; - apps: App[]; -} - -export interface App { - title: string; - url: string; - iconUrl: string | null; - description: string; - // null groups means "everyone can see this" - groups: string[] | null; - mfaDeviceRequired: boolean; - // null agencies means "all agencies" - agencies: string[] | null; - target: string; -} - -export class NoticeClass implements Notice { - label: string = ''; - pretext: string = ''; - text: string = ''; - - constructor(opts: { label?: any; pretext?: any; text?: any }) { - (this.label = opts.label ? opts.label : ''), - (this.pretext = opts.pretext ? opts.pretext : ''), - (this.text = opts.text ? opts.text : ''); - } -} - -/** - * This class is in lib rather than server just so we can use it in Storybook - * stories. It doesn’t actually get used by the client app. - */ -export default class AppsRegistry { - showAll: boolean; - allCategories: AppsCategory[]; - noticeMsg: Notice[]; - - constructor(appsYaml: any, showAll = false) { - this.showAll = showAll; - const yamlCategories = appsYaml.categories; - const yamlNotice = appsYaml.notice; - - if (!yamlCategories || !Array.isArray(yamlCategories)) { - throw new Error('Missing categories array'); - } - - this.noticeMsg = yamlNotice ? yamlNotice : new NoticeClass({}); - - this.allCategories = yamlCategories.map(c => { - const { title, apps: yamlApps, show_request_access_link, icons } = c; - - if (!title || typeof title !== 'string') { - throw new Error('Category missing title: ' + JSON.stringify(c)); - } - - if (!yamlApps || !Array.isArray(yamlApps)) { - throw new Error('Category missing apps array: ' + JSON.stringify(c)); - } - - const apps: App[] = yamlApps.map(a => { - const { - title, - url, - groups, - description, - icon, - mfa_device_required, - agencies, - target, - } = a; - - if (!title || typeof title !== 'string') { - throw new Error('App missing a title: ' + JSON.stringify(a)); - } - - if (!url || typeof url !== 'string') { - throw new Error('App missing a url: ' + JSON.stringify(a)); - } - - if (groups && !Array.isArray(groups)) { - throw new Error('groups is not an array: ' + JSON.stringify(a)); - } - - if (agencies && !Array.isArray(agencies)) { - throw new Error('agencies is not an array: ' + JSON.stringify(a)); - } - - return { - title, - url, - iconUrl: icon || null, - description: description || '', - groups: groups || null, - mfaDeviceRequired: mfa_device_required || false, - agencies: agencies || null, - target: target || '', - }; - }); - return { - title, - apps, - showRequestAccessLink: !!show_request_access_link, - icons: !!icons, - }; - }); - } - - appsForNotice(): Notice[] { - return this.noticeMsg; - } - - appsForGroups( - userGroups: string[], - hasMfaDevice: boolean, - cobAgency: string | null - ): AppsCategory[] { - return ( - this.allCategories - .map(c => ({ - ...c, - apps: c.apps.filter(({ groups, mfaDeviceRequired, agencies }) => { - // this.showAll = false; - - const mfaRequirementMet = !mfaDeviceRequired || hasMfaDevice; - - const groupsRequirementMet = groups - ? groups && groups.find(g => userGroups.includes(g)) - ? true - : false - : false; - - const agencyRequirementMet = agencies - ? agencies && (cobAgency && agencies.includes(cobAgency)) - : false; - - const isGroupOrAgencies = - (groupsRequirementMet || agencyRequirementMet) && - mfaRequirementMet; - - return this.showAll || (!groups && !agencies) || isGroupOrAgencies; - }), - })) - // Filter out apps with no categories - .filter(c => c.apps.length > 0) - ); - } -} - -export function makeAppsRegistry( - yamlString: string, - showAll = false -): AppsRegistry { - const appsYaml = yaml.safeLoad(yamlString); - return new AppsRegistry(appsYaml, showAll); -} diff --git a/services-js/access-boston/src/lib/__snapshots__/AppsRegistry.test.ts.snap b/services-js/access-boston/src/lib/__snapshots__/AppsRegistry.test.ts.snap deleted file mode 100644 index 0a0c0e136..000000000 --- a/services-js/access-boston/src/lib/__snapshots__/AppsRegistry.test.ts.snap +++ /dev/null @@ -1,608 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`appsForUserTypeAndGroups doesn’t return agency apps if none is specified 1`] = ` -Array [ - Object { - "apps": Array [ - Object { - "agencies": null, - "description": "", - "groups": null, - "iconUrl": "/assets/apps/hub.svg", - "mfaDeviceRequired": false, - "target": "", - "title": "The Hub", - "url": "https://hub.boston.gov/", - }, - Object { - "agencies": null, - "description": "", - "groups": null, - "iconUrl": "/assets/apps/talented.svg", - "mfaDeviceRequired": false, - "target": "", - "title": "BPS Job Listings", - "url": "https://bostonpublicschools.tedk12.com/hire/index.aspx", - }, - Object { - "agencies": null, - "description": "", - "groups": null, - "iconUrl": "/assets/apps/boston-maps.svg", - "mfaDeviceRequired": false, - "target": "", - "title": "Boston Maps", - "url": "https://sso.boston.gov/idp/startSSO.ping?PartnerSpId=boston.maps.arcgis.com", - }, - Object { - "agencies": null, - "description": "", - "groups": Array [ - "SG_AB_IAM_TEAM", - ], - "iconUrl": "/assets/apps/identityiq.svg", - "mfaDeviceRequired": false, - "target": "", - "title": "IIQ", - "url": "https://identity.boston.gov/identityiq/", - }, - ], - "icons": true, - "showRequestAccessLink": false, - "title": "Applications", - }, - Object { - "apps": Array [ - Object { - "agencies": null, - "description": "", - "groups": null, - "iconUrl": null, - "mfaDeviceRequired": false, - "target": "", - "title": "Change My Password", - "url": "/change-password", - }, - Object { - "agencies": null, - "description": "", - "groups": null, - "iconUrl": null, - "mfaDeviceRequired": true, - "target": "", - "title": "Manage My Device(s)", - "url": "https://desktop.pingone.com/boston/Selection?cmd=devices", - }, - ], - "icons": false, - "showRequestAccessLink": false, - "title": "Account Tools", - }, - Object { - "apps": Array [ - Object { - "agencies": null, - "description": "", - "groups": null, - "iconUrl": null, - "mfaDeviceRequired": false, - "target": "_blank", - "title": "Group Management", - "url": "/group-management", - }, - ], - "icons": false, - "showRequestAccessLink": false, - "title": "Support Tools", - }, -] -`; - -exports[`appsForUserTypeAndGroups returns apps for a group 1`] = ` -Array [ - Object { - "apps": Array [ - Object { - "agencies": null, - "description": "", - "groups": null, - "iconUrl": "/assets/apps/hub.svg", - "mfaDeviceRequired": false, - "target": "", - "title": "The Hub", - "url": "https://hub.boston.gov/", - }, - Object { - "agencies": null, - "description": "", - "groups": null, - "iconUrl": "/assets/apps/talented.svg", - "mfaDeviceRequired": false, - "target": "", - "title": "BPS Job Listings", - "url": "https://bostonpublicschools.tedk12.com/hire/index.aspx", - }, - Object { - "agencies": null, - "description": "", - "groups": null, - "iconUrl": "/assets/apps/boston-maps.svg", - "mfaDeviceRequired": false, - "target": "", - "title": "Boston Maps", - "url": "https://sso.boston.gov/idp/startSSO.ping?PartnerSpId=boston.maps.arcgis.com", - }, - Object { - "agencies": null, - "description": "", - "groups": Array [ - "SG_AB_IAM_TEAM", - ], - "iconUrl": "/assets/apps/identityiq.svg", - "mfaDeviceRequired": false, - "target": "", - "title": "IIQ", - "url": "https://identity.boston.gov/identityiq/", - }, - ], - "icons": true, - "showRequestAccessLink": false, - "title": "Applications", - }, - Object { - "apps": Array [ - Object { - "agencies": Array [ - "CH", - ], - "description": "", - "groups": null, - "iconUrl": null, - "mfaDeviceRequired": false, - "target": "", - "title": "FleetHub", - "url": "http://boston.getlocalmotion.com/", - }, - Object { - "agencies": Array [ - "BPD", - "BFD", - "CH", - ], - "description": "", - "groups": null, - "iconUrl": null, - "mfaDeviceRequired": false, - "target": "", - "title": "International Travel", - "url": "https://goo.gl/forms/Xobh9aZeGXSnKH2m2", - }, - Object { - "agencies": Array [ - "CH", - ], - "description": "", - "groups": Array [ - "SG_AB_BLDGMAINTREQ", - ], - "iconUrl": null, - "mfaDeviceRequired": false, - "target": "", - "title": "Building Maintenance", - "url": "https://eamtst.cityhall.boston.cob:5443/WebMaint/", - }, - ], - "icons": false, - "showRequestAccessLink": false, - "title": "Forms / Links", - }, - Object { - "apps": Array [ - Object { - "agencies": null, - "description": "", - "groups": null, - "iconUrl": null, - "mfaDeviceRequired": false, - "target": "", - "title": "Change My Password", - "url": "/change-password", - }, - Object { - "agencies": null, - "description": "", - "groups": null, - "iconUrl": null, - "mfaDeviceRequired": true, - "target": "", - "title": "Manage My Device(s)", - "url": "https://desktop.pingone.com/boston/Selection?cmd=devices", - }, - ], - "icons": false, - "showRequestAccessLink": false, - "title": "Account Tools", - }, - Object { - "apps": Array [ - Object { - "agencies": null, - "description": "", - "groups": null, - "iconUrl": null, - "mfaDeviceRequired": false, - "target": "_blank", - "title": "Group Management", - "url": "/group-management", - }, - ], - "icons": false, - "showRequestAccessLink": false, - "title": "Support Tools", - }, -] -`; - -exports[`appsForUserTypeAndGroups returns apps for an agency 1`] = ` -Array [ - Object { - "apps": Array [ - Object { - "agencies": null, - "description": "", - "groups": null, - "iconUrl": "/assets/apps/hub.svg", - "mfaDeviceRequired": false, - "target": "", - "title": "The Hub", - "url": "https://hub.boston.gov/", - }, - Object { - "agencies": null, - "description": "", - "groups": null, - "iconUrl": "/assets/apps/talented.svg", - "mfaDeviceRequired": false, - "target": "", - "title": "BPS Job Listings", - "url": "https://bostonpublicschools.tedk12.com/hire/index.aspx", - }, - Object { - "agencies": null, - "description": "", - "groups": null, - "iconUrl": "/assets/apps/boston-maps.svg", - "mfaDeviceRequired": false, - "target": "", - "title": "Boston Maps", - "url": "https://sso.boston.gov/idp/startSSO.ping?PartnerSpId=boston.maps.arcgis.com", - }, - Object { - "agencies": null, - "description": "", - "groups": Array [ - "SG_AB_IAM_TEAM", - ], - "iconUrl": "/assets/apps/identityiq.svg", - "mfaDeviceRequired": false, - "target": "", - "title": "IIQ", - "url": "https://identity.boston.gov/identityiq/", - }, - ], - "icons": true, - "showRequestAccessLink": false, - "title": "Applications", - }, - Object { - "apps": Array [ - Object { - "agencies": null, - "description": "", - "groups": null, - "iconUrl": null, - "mfaDeviceRequired": false, - "target": "", - "title": "Change My Password", - "url": "/change-password", - }, - Object { - "agencies": null, - "description": "", - "groups": null, - "iconUrl": null, - "mfaDeviceRequired": true, - "target": "", - "title": "Manage My Device(s)", - "url": "https://desktop.pingone.com/boston/Selection?cmd=devices", - }, - ], - "icons": false, - "showRequestAccessLink": false, - "title": "Account Tools", - }, - Object { - "apps": Array [ - Object { - "agencies": null, - "description": "", - "groups": null, - "iconUrl": null, - "mfaDeviceRequired": false, - "target": "_blank", - "title": "Group Management", - "url": "/group-management", - }, - ], - "icons": false, - "showRequestAccessLink": false, - "title": "Support Tools", - }, -] -`; - -exports[`appsForUserTypeAndGroups returns apps that require an MFA device 1`] = ` -Array [ - Object { - "apps": Array [ - Object { - "agencies": null, - "description": "", - "groups": null, - "iconUrl": "/assets/apps/hub.svg", - "mfaDeviceRequired": false, - "target": "", - "title": "The Hub", - "url": "https://hub.boston.gov/", - }, - Object { - "agencies": null, - "description": "", - "groups": null, - "iconUrl": "/assets/apps/talented.svg", - "mfaDeviceRequired": false, - "target": "", - "title": "BPS Job Listings", - "url": "https://bostonpublicschools.tedk12.com/hire/index.aspx", - }, - Object { - "agencies": null, - "description": "", - "groups": null, - "iconUrl": "/assets/apps/boston-maps.svg", - "mfaDeviceRequired": false, - "target": "", - "title": "Boston Maps", - "url": "https://sso.boston.gov/idp/startSSO.ping?PartnerSpId=boston.maps.arcgis.com", - }, - ], - "icons": true, - "showRequestAccessLink": false, - "title": "Applications", - }, - Object { - "apps": Array [ - Object { - "agencies": Array [ - "CH", - ], - "description": "", - "groups": null, - "iconUrl": null, - "mfaDeviceRequired": false, - "target": "", - "title": "FleetHub", - "url": "http://boston.getlocalmotion.com/", - }, - Object { - "agencies": Array [ - "BPD", - "BFD", - "CH", - ], - "description": "", - "groups": null, - "iconUrl": null, - "mfaDeviceRequired": false, - "target": "", - "title": "International Travel", - "url": "https://goo.gl/forms/Xobh9aZeGXSnKH2m2", - }, - Object { - "agencies": Array [ - "CH", - ], - "description": "", - "groups": Array [ - "SG_AB_BLDGMAINTREQ", - ], - "iconUrl": null, - "mfaDeviceRequired": false, - "target": "", - "title": "Building Maintenance", - "url": "https://eamtst.cityhall.boston.cob:5443/WebMaint/", - }, - ], - "icons": false, - "showRequestAccessLink": false, - "title": "Forms / Links", - }, - Object { - "apps": Array [ - Object { - "agencies": null, - "description": "", - "groups": null, - "iconUrl": null, - "mfaDeviceRequired": false, - "target": "", - "title": "Change My Password", - "url": "/change-password", - }, - Object { - "agencies": null, - "description": "", - "groups": null, - "iconUrl": null, - "mfaDeviceRequired": true, - "target": "", - "title": "Manage My Device(s)", - "url": "https://desktop.pingone.com/boston/Selection?cmd=devices", - }, - ], - "icons": false, - "showRequestAccessLink": false, - "title": "Account Tools", - }, - Object { - "apps": Array [ - Object { - "agencies": null, - "description": "", - "groups": null, - "iconUrl": null, - "mfaDeviceRequired": false, - "target": "_blank", - "title": "Group Management", - "url": "/group-management", - }, - ], - "icons": false, - "showRequestAccessLink": false, - "title": "Support Tools", - }, -] -`; - -exports[`appsForUserTypeAndGroups returns just the "everyone" category for no groups 1`] = ` -Array [ - Object { - "apps": Array [ - Object { - "agencies": null, - "description": "", - "groups": null, - "iconUrl": "/assets/apps/hub.svg", - "mfaDeviceRequired": false, - "target": "", - "title": "The Hub", - "url": "https://hub.boston.gov/", - }, - Object { - "agencies": null, - "description": "", - "groups": null, - "iconUrl": "/assets/apps/talented.svg", - "mfaDeviceRequired": false, - "target": "", - "title": "BPS Job Listings", - "url": "https://bostonpublicschools.tedk12.com/hire/index.aspx", - }, - Object { - "agencies": null, - "description": "", - "groups": null, - "iconUrl": "/assets/apps/boston-maps.svg", - "mfaDeviceRequired": false, - "target": "", - "title": "Boston Maps", - "url": "https://sso.boston.gov/idp/startSSO.ping?PartnerSpId=boston.maps.arcgis.com", - }, - ], - "icons": true, - "showRequestAccessLink": false, - "title": "Applications", - }, - Object { - "apps": Array [ - Object { - "agencies": Array [ - "CH", - ], - "description": "", - "groups": null, - "iconUrl": null, - "mfaDeviceRequired": false, - "target": "", - "title": "FleetHub", - "url": "http://boston.getlocalmotion.com/", - }, - Object { - "agencies": Array [ - "BPD", - "BFD", - "CH", - ], - "description": "", - "groups": null, - "iconUrl": null, - "mfaDeviceRequired": false, - "target": "", - "title": "International Travel", - "url": "https://goo.gl/forms/Xobh9aZeGXSnKH2m2", - }, - Object { - "agencies": Array [ - "CH", - ], - "description": "", - "groups": Array [ - "SG_AB_BLDGMAINTREQ", - ], - "iconUrl": null, - "mfaDeviceRequired": false, - "target": "", - "title": "Building Maintenance", - "url": "https://eamtst.cityhall.boston.cob:5443/WebMaint/", - }, - ], - "icons": false, - "showRequestAccessLink": false, - "title": "Forms / Links", - }, - Object { - "apps": Array [ - Object { - "agencies": null, - "description": "", - "groups": null, - "iconUrl": null, - "mfaDeviceRequired": false, - "target": "", - "title": "Change My Password", - "url": "/change-password", - }, - Object { - "agencies": null, - "description": "", - "groups": null, - "iconUrl": null, - "mfaDeviceRequired": true, - "target": "", - "title": "Manage My Device(s)", - "url": "https://desktop.pingone.com/boston/Selection?cmd=devices", - }, - ], - "icons": false, - "showRequestAccessLink": false, - "title": "Account Tools", - }, - Object { - "apps": Array [ - Object { - "agencies": null, - "description": "", - "groups": null, - "iconUrl": null, - "mfaDeviceRequired": false, - "target": "_blank", - "title": "Group Management", - "url": "/group-management", - }, - ], - "icons": false, - "showRequestAccessLink": false, - "title": "Support Tools", - }, -] -`; diff --git a/services-js/access-boston/src/lib/apps.json b/services-js/access-boston/src/lib/apps.json deleted file mode 100644 index 3c01dbe31..000000000 --- a/services-js/access-boston/src/lib/apps.json +++ /dev/null @@ -1,254 +0,0 @@ -{ - "categories": [ - { - "title": "Applications", - "show_request_access_link": false, - "icons": true, - "apps": [ - { - "title": "Employee Self-Service", - "url": "https://ess.boston.gov/", - "icon": "/assets/apps/ess.svg", - "groups": [ - "SG_AB_ESS" - ] - }, - { - "title": "The Hub", - "url": "https://hub.boston.gov/", - "icon": "/assets/apps/hub.svg" - }, - { - "title": "AgilePoint", - "url": "https://boston.nxone.com", - "icon": "https://assets.boston.gov/icons/accessboston/agilepoint.svg", - "groups": [ - "SG_AB_AGILEPOINT" - ] - }, - { - "title": "Security Awareness", - "url": "https://boston.wombatsecurity.com", - "icon": "https://assets.boston.gov/icons/accessboston/proofpoint-web-security.svg", - "groups": [ - "SG_AB_PROOFPOINT" - ] - }, - { - "title": "Current Job Openings", - "url": "https://sso.boston.gov/idp/startSSO.ping?PartnerSpId=icims.com&TargetResource=https%3A//employeeconnect-boston.icims.com/r.jsp", - "icon": "/assets/apps/job-openings.svg", - "groups": [ - "SG_AB_ICIMS" - ] - }, - { - "title": "Current Job Openings", - "icon": "/assets/apps/job-openings.svg", - "url": "https://careercenter-boston.icims.com/jobs/", - "groups": [ - "SG_AB_ALLSPONSORED" - ] - }, - { - "title": "Career Center", - "url": "https://sso.boston.gov/idp/startSSO.ping?PartnerSpId=icims.com", - "icon": "/assets/apps/career-center.svg", - "groups": [ - "SG_AB_CAREERCTR" - ] - }, - { - "title": "BPS Job Listings", - "url": "https://bostonpublicschools.tedk12.com/hire/index.aspx", - "icon": "/assets/apps/talented.svg" - }, - { - "title": "Employee Training", - "url": "https://sso.boston.gov/idp/startSSO.ping?PartnerSpId=http%3A%2F%2Fwww.mylearningplan.com%2Fmvc%2Fsaml%2Fmetadata%2F13583", - "icon": "/assets/apps/employee-training.svg", - "groups": [ - "SG_AB_MLP" - ] - }, - { - "title": "Boston Maps", - "url": "https://sso.boston.gov/idp/startSSO.ping?PartnerSpId=boston.maps.arcgis.com", - "icon": "/assets/apps/boston-maps.svg" - }, - { - "title": "BAIS FN", - "url": "https://baisfn.cityhall.boston.cob/psp/prdfn/EMPLOYEE/ERP/h/?tab=DEFAULT", - "icon": "/assets/apps/bais-fn.svg", - "groups": [ - "SG_AB_PSFN" - ] - }, - { - "title": "BAIS HCM", - "url": "https://hcm.cityhall.boston.cob/psp/pshrpd3/EMPLOYEE/HRMS/h/?tab=DEFAULT", - "icon": "/assets/apps/bais-hcm.svg", - "groups": [ - "SG_AB_PSHCM" - ] - }, - { - "title": "Hyperion", - "url": "https://login.us2.oraclecloud.com/oam/server/obrareq.cgi?encquery%3Dv2GuCFxM8VxsxDZPK9HzUGyy9Q3kTEgTTNlLlAmtoZTwncih0HU8NAbYsRh9raKXKy8d7P4g6V6FY84QopZtihSoEOcPDIVU7qktPzNiMf87zkN0Hm9YU%2BT%2B3ytwl%2FKTMsgQpsowiFHZDm13DFTGawpK%2B%2Bt3RL%2F3YiNDK33gCZnp9AZw%2Fm5OFX8d9xf%2BP8q9mlMEN3SrirXdsgAq4Jzli5LbTH38nJTp9y0kYPR1cnwH8VP80yul1ZxpngYMTatK3Z613FGf4luKZdL0G9560UC5cdbxKo3Rqhwp76BvyRFaQ%2B%2F%2FhwI3PIWWdLcyR4scKux9x5p5H2GePV7xJSst0g%3D%3D%20agentid%3DPlanning_WG%20ver%3D1%20crmethod%3D2", - "icon": "/assets/apps/hyperion.svg", - "groups": [ - "SG_AB_HYPERION" - ] - }, - { - "title": "Civis", - "url": "https://sso.boston.gov/idp/startSSO.ping?PartnerSpId=https%3A%2F%2Fplatform.civisanalytics.com%2Fusers%2Fsaml%2Fmetadata", - "icon": "/assets/apps/civis.svg", - "groups": [ - "SG_AB_CIVIS" - ] - }, - { - "title": "IIQ", - "url": "https://identity.boston.gov/identityiq/", - "icon": "/assets/apps/identityiq.svg", - "groups": [ - "SG_AB_IAM_TEAM" - ] - }, - { - "title": "E-Builder", - "url": "https://sso.boston.gov/idp/startSSO.ping?PartnerSpId=sso.e-builder.net", - "icon": "/assets/apps/e-builder.svg", - "groups": [ - "SG_AB_EBUILDER" - ] - } - ] - }, - { - "title": "Forms / Links", - "apps": [ - { - "title": "Create Sponsored Account", - "url": "https://identity.boston.gov/identityiq/createSponsor.jsf", - "groups": [ - "SG_AB_SPONSOR" - ] - }, - { - "title": "FleetHub", - "url": "http://boston.getlocalmotion.com/", - "agencies": [ - "CH" - ] - }, - { - "title": "International Travel", - "url": "https://goo.gl/forms/Xobh9aZeGXSnKH2m2", - "agencies": [ - "BPD", - "BFD", - "CH" - ] - }, - { - "title": "Building Maintenance", - "url": "https://eamtst.cityhall.boston.cob:5443/WebMaint/", - "agencies": [ - "CH" - ] - }, - { - "title": "Request FN Access", - "url": "https://identity.boston.gov/identityiq/requestPsfn.jsf", - "groups": [ - "SG_AB_FN_LIASONS_USERS" - ] - } - ] - }, - { - "title": "Account Tools", - "apps": [ - { - "title": "Change My Password", - "url": "/change-password" - }, - { - "title": "Manage My Device(s)", - "url": "https://desktop.pingone.com/boston/Selection?cmd=devices", - "mfa_device_required": true - }, - { - "title": "Identity Verification", - "url": "/identity-verification", - "groups": [ - "SG_AB_AGILEPOINT" - ] - } - ] - }, - { - "title": "Support Tools", - "apps": [ - { - "title": "Identity Verification", - "url": "https://identity.boston.gov/identityiq/identityVerification.jsf", - "groups": [ - "SG_AB_SERVICEDESK_USERS" - ] - }, - { - "title": "View User Information", - "url": "https://identity.boston.gov/identityiq/viewUserIdentityonly.jsf", - "groups": [ - "SG_AB_SERVICEDESK_USERS" - ] - }, - { - "title": "Edit Group", - "url": "#", - "groups": [ - "SG_AB_HCMSECURITY" - ] - }, - { - "title": "Group Management", - "url": "/group-management", - "target": "_blank" - } - ] - }, - { - "title": "Manager Tools", - "apps": [ - { - "title": "Set an Access Boston Delegate", - "url": "https://identity.boston.gov/identityiq/dashboard/editPreferences.jsf", - "groups": [ - "SG_AB_MANAGER", - "SG_AB_TRAINING_TEAM", - "SG_AB_FN_LIASONS_USERS" - ] - }, - { - "title": "My Access Boston Approvals", - "url": "https://identity.boston.gov/identityiq/manage/workItems/workItems.jsf?workItemType=Approval", - "groups": [ - "SG_AB_MANAGER", - "SG_AB_TRAINING_TEAM", - "SG_AB_FN_LIASONS_USERS" - ] - }, - { - "title": "Manage Sponsored Account", - "url": "https://identity.boston.gov/identityiq/extendSponsor.jsf", - "groups": [ - "SG_AB_SPONSOR" - ] - } - ] - } - ] -} \ No newline at end of file diff --git a/services-js/access-boston/src/lib/validation.test.ts b/services-js/access-boston/src/lib/validation.test.ts deleted file mode 100644 index 219bfda7f..000000000 --- a/services-js/access-boston/src/lib/validation.test.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { analyzePassword, testNotCityEmailAddress } from './validation'; - -describe('analyzePassword', () => { - test('long enough', () => { - expect(analyzePassword('a').longEnough).toBe(false); - expect(analyzePassword('abcdeabcde').longEnough).toBe(true); - }); - - test('has lowercase', () => { - expect(analyzePassword('abc').hasLowercase).toBe(true); - expect(analyzePassword('ABC').hasLowercase).toBe(false); - }); - - test('has uppercase', () => { - expect(analyzePassword('ABC').hasUppercase).toBe(true); - expect(analyzePassword('abc').hasUppercase).toBe(false); - }); - - test('has number', () => { - expect(analyzePassword('123').hasNumber).toBe(true); - expect(analyzePassword('abc').hasUppercase).toBe(false); - }); - - test('has symbol', () => { - expect(analyzePassword('_').hasSymbol).toBe(true); - expect(analyzePassword('#').hasSymbol).toBe(true); - expect(analyzePassword('aB4').hasSymbol).toBe(false); - expect(analyzePassword(' ').hasSymbol).toBe(false); - }); - - test('complex enough', () => { - expect(analyzePassword('____').complexEnough).toBe(false); - expect(analyzePassword('__33').complexEnough).toBe(false); - expect(analyzePassword('_a33').complexEnough).toBe(true); - expect(analyzePassword('_a3B').complexEnough).toBe(true); - }); - - test('too long', () => { - expect(analyzePassword('12345678901234567890123456789012345').tooLong).toBe( - true - ); - expect(analyzePassword('abc').tooLong).toBe(false); - }); - - test('has spaces', () => { - expect(analyzePassword('a b').hasSpaces).toBe(true); - expect(analyzePassword('abc').hasSpaces).toBe(false); - }); -}); - -describe('testNotCityEmailAddress', () => { - test('GMail', () => { - expect(testNotCityEmailAddress('test@gmail.com')).toBe(true); - }); - - test('Boston.gov', () => { - expect(testNotCityEmailAddress('test@boston.gov')).toBe(false); - }); - - test('BOSTON.GOV', () => { - expect(testNotCityEmailAddress('test@BOSTON.GOV')).toBe(false); - }); - - test('Boston.gov subdomain', () => { - expect(testNotCityEmailAddress('test@staging.boston.gov')).toBe(false); - }); - - test('Notboston.gov', () => { - expect(testNotCityEmailAddress('test@notboston.gov')).toBe(true); - }); - - test('cityofboston.gov', () => { - expect(testNotCityEmailAddress('test@cityofboston.gov')).toBe(false); - }); - - test('bostonems.org', () => { - expect(testNotCityEmailAddress('test@bostonems.org')).toBe(false); - }); - - test('bphc.org', () => { - expect(testNotCityEmailAddress('test@bphc.org')).toBe(false); - }); -}); diff --git a/services-js/access-boston/src/lib/validation.ts b/services-js/access-boston/src/lib/validation.ts deleted file mode 100644 index f02586ccd..000000000 --- a/services-js/access-boston/src/lib/validation.ts +++ /dev/null @@ -1,138 +0,0 @@ -import * as yup from 'yup'; - -import { PHONE_REGEXP } from '@cityofboston/form-common'; - -export function analyzePassword(password: string) { - password = password || ''; - - const longEnough = password.length >= 10; - const hasLowercase = !!password.match(/[a-z]/); - const hasUppercase = !!password.match(/[A-Z]/); - const hasNumber = !!password.match(/\d/); - // We clear out spaces from the symbol check so that it doesn't fire for them. - const hasSymbol = !!password.replace(/\s/g, '').match(/[\W_]/); - - const complexEnough = - [hasLowercase, hasUppercase, hasNumber, hasSymbol].filter(b => b).length >= - 3; - - const tooLong = password.length > 32; - const hasSpaces = !!password.match(/\s/); - - return { - longEnough, - complexEnough, - hasLowercase, - hasUppercase, - hasNumber, - hasSymbol, - tooLong, - hasSpaces, - }; -} - -const NEW_PASSWORD_SHAPE = { - newPassword: yup - .string() - .test( - 'longEnough', - 'Your new password must be at least 10 characters', - val => analyzePassword(val).longEnough - ) - .test( - 'complexEnough', - 'Your new password must have more different types of characters', - val => analyzePassword(val).complexEnough - ) - .test( - 'tooLong', - 'Your new password must be 32 characters or less', - val => !analyzePassword(val).tooLong - ) - .test( - 'hasSpaces', - 'Your new password can’t have spaces', - val => !analyzePassword(val).hasSpaces - ) - .required('Please put in a new password'), - confirmPassword: yup - .string() - .required('Please confirm your new password') - .oneOf( - [yup.ref('newPassword')], - 'The password confirmation does not match your new password' - ), -}; - -export const changePasswordSchema = yup.object().shape({ - password: yup.string().required('Your current password is required'), - ...NEW_PASSWORD_SHAPE, -}); - -export const forgotPasswordSchema = yup.object().shape({ - ...NEW_PASSWORD_SHAPE, -}); - -/** - * Helper method to pull errors out of a Yup ValidationError and add them to a - * map of key -> error strings. - * - * Recursively traverses the Validation error to find everything inside. - */ -export function addValidationError( - errorMap: { [key: string]: string }, - err: yup.ValidationError -) { - if (err.path) { - errorMap[err.path] = err.message; - } - - err.inner.forEach(innerErr => { - addValidationError(errorMap, innerErr); - }); -} - -const CITY_DOMAINS = [ - 'boston.gov', - 'pd.boston.gov', - 'bostonpublicschools.org', - 'bpl.org', - 'cityofboston.gov', - 'ci.boston.ma.us', - 'boston.k12.ma.us', - 'bphc.org', - 'bostonems.org', -]; - -const CITY_DOMAIN_REGEXPS = CITY_DOMAINS.map( - d => new RegExp(`[@.]${d.replace(/\./g, '\\.')}$`, 'i') -); - -// This can get called w/ an undefined value, apparently. -export function testNotCityEmailAddress(val: string | undefined): boolean { - return !val || !CITY_DOMAIN_REGEXPS.find(r => !!val.match(r)); -} - -export const registerDeviceSchema = yup.object().shape({ - phoneOrEmail: yup.string().oneOf(['phone', 'email']), - smsOrVoice: yup.string().oneOf(['sms', 'voice']), - phoneNumber: yup.string().when('phoneOrEmail', { - is: 'phone', - then: yup - .string() - .required('Please put in your phone number.') - .matches(PHONE_REGEXP, 'That doesn’t look like a phone number.'), - }), - email: yup.string().when('phoneOrEmail', { - is: 'email', - then: yup - .string() - .required('Please put in an email address.') - .email('This doesn’t look like an email address.') - .test( - 'not-cob', - 'Please use a personal email, not a work email.', - testNotCityEmailAddress - ), - }), -}); diff --git a/services-js/access-boston/src/next-env.d.ts b/services-js/access-boston/src/next-env.d.ts deleted file mode 100644 index 7b7aa2c77..000000000 --- a/services-js/access-boston/src/next-env.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -/// -/// diff --git a/services-js/access-boston/src/pages/_app.tsx b/services-js/access-boston/src/pages/_app.tsx deleted file mode 100644 index 355941a41..000000000 --- a/services-js/access-boston/src/pages/_app.tsx +++ /dev/null @@ -1,192 +0,0 @@ -import React from 'react'; -import App, { AppContext, AppInitialProps, AppProps } from 'next/app'; -import { NextPageContext } from 'next'; - -import Router from 'next/router'; -import getConfig from 'next/config'; -import cookies from 'next-cookies'; -import { hydrate, cache as emotionCache } from 'emotion'; -import { CacheProvider } from '@emotion/core'; - -import { - FetchGraphql, - makeFetchGraphql, - RouterListener, - GtagSiteAnalytics, - ScreenReaderSupport, -} from '@cityofboston/next-client-common'; - -import CrumbContext from '../client/CrumbContext'; -import { RedirectError } from '../client/auth-helpers'; -``; -// Adds server generated styles to emotion cache. -// '__NEXT_DATA__.ids' is set in '_document.js' -if (typeof window !== 'undefined') { - hydrate((window as any).__NEXT_DATA__.ids); -} - -/** - * Our App’s getInitialProps automatically calls the page’s getInitialProps with - * an instance of this class as the second argument, after the Next context. - */ -export interface GetInitialPropsDependencies { - fetchGraphql: FetchGraphql; -} - -export type GetInitialProps = ( - cxt: NextPageContext, - deps: GetInitialPropsDependencies -) => T | Promise; - -/** - * These props are automatically given to any Pages in the app. While magically - * providing Props out of nowhere is a bit hard to follow, this pattern seems to - * have the best combination of allowing Pages to be explicit about their - * dependencies for testing purposes while avoiding too much boilerplate of - * wrapper components and Context.Consumer render props. - * - * To use this: - * - * interface InitialProps {foo: string; bar: string[]; - * } - * - * interface Props extends InitialProps, Pick - * {} - * - * class MyPage extends React.Component {static getInitialProps: - * GetInitialProps = async (ctx, initialDeps) => { - * ... - * } - * - * handleAction: () => {this.props.fetchGraphql(…); - * } - * } - */ -export interface PageDependencies { - routerListener: RouterListener; - fetchGraphql: FetchGraphql; - screenReaderSupport: ScreenReaderSupport; -} - -interface Props { - serverCrumb: string; -} - -/** - * Custom app wrapper for setting up global dependencies: - * - * - GetInitialPropsDependencies are passed as a second argument to getInitialProps - * - PageDependencies are spread as props for the page - */ -export default class AccessBostonApp extends App { - private pageDependencies: PageDependencies; - - /** - * We have to keep the crumb on the instance because after the initial - * server-side render, getInitialProps is unable to read it off of the - * cookies. - */ - private crumb: string; - - static async getInitialProps({ - Component, - ctx, - }: AppContext): Promise { - const initialPageDependencies: GetInitialPropsDependencies = { - fetchGraphql: makeFetchGraphql(getConfig(), ctx.req), - }; - - // Crumb is our XSRF token from the Hapi plugin. It's only available on the - // server. - const { crumb } = ctx.req ? cookies(ctx) : { crumb: '' }; - - try { - const pageProps = Component.getInitialProps - ? await (Component.getInitialProps as any)(ctx, initialPageDependencies) - : {}; - - return { - pageProps, - serverCrumb: crumb || '', - }; - } catch (e) { - let redirectUrl: string | null; - - if (e instanceof RedirectError) { - redirectUrl = e.url; - } else if (e.message === 'Forbidden') { - // TODO(finh): Be more pedantic about detecting these, possibly by - // checking the status code when doing GraphQL fetches. - if ((window as any).Rollbar) { - (window as any).Rollbar.warn('Forbidden response in getInitialProps'); - } - redirectUrl = '/login'; - } else { - redirectUrl = null; - } - - if (redirectUrl) { - const { res } = ctx; - if (res) { - res.writeHead(302, { Location: redirectUrl }); - res.end(); - } else { - window.location.href = redirectUrl; - } - - return { - pageProps: {}, - serverCrumb: '', - }; - } else { - throw e; - } - } - } - - constructor(props: Props & AppProps) { - super(props); - - this.crumb = this.props.serverCrumb; - - this.pageDependencies = { - routerListener: new RouterListener(), - fetchGraphql: makeFetchGraphql(getConfig()), - screenReaderSupport: new ScreenReaderSupport(), - }; - } - - componentDidMount() { - const { routerListener, screenReaderSupport } = this.pageDependencies; - - screenReaderSupport.attach(); - - routerListener.attach({ - router: Router, - siteAnalytics: new GtagSiteAnalytics(), - screenReaderSupport, - }); - // Used by testcafe-helpers as a signal that React is running and has - // rendered the page. Used to ensure we don’t try to interact before the JS - // has loaded. - (window as any).APP_RUNNING = true; - } - - componentWillUnmount() { - const { routerListener, screenReaderSupport } = this.pageDependencies; - routerListener.detach(); - screenReaderSupport.detach(); - } - - render() { - const { Component, pageProps } = this.props; - - return ( - - - - - - ); - } -} diff --git a/services-js/access-boston/src/pages/_document.tsx b/services-js/access-boston/src/pages/_document.tsx deleted file mode 100644 index a57d1ee64..000000000 --- a/services-js/access-boston/src/pages/_document.tsx +++ /dev/null @@ -1,142 +0,0 @@ -import Document, { - Head, - Main, - NextScript, - DocumentInitialProps, - DocumentContext, - DocumentProps, -} from 'next/document'; -import { extractCritical } from 'emotion-server'; - -import { - makeNProgressStyle, - ScreenReaderSupport, - GtagSiteAnalytics, -} from '@cityofboston/next-client-common'; -import { CompatibilityWarning, StatusModal } from '@cityofboston/react-fleet'; - -import { HEADER_HEIGHT } from '../client/styles'; -import React from 'react'; - -type Props = { - userAgent: string; - rollbarAccessToken: string | undefined; - rollbarEnvironment: string | undefined; - - css: string; - ids: string[]; -}; - -export default class MyDocument extends Document { - props: any; - - static async getInitialProps({ - renderPage, - req, - }: DocumentContext): Promise { - const page = await renderPage(); - const styles = extractCritical(page.html); - const userAgent = req!.headers['user-agent'] || ''; - return { - ...page, - ...styles, - userAgent, - rollbarAccessToken: process.env.ROLLBAR_BROWSER_ACCESS_TOKEN, - rollbarEnvironment: - process.env.ROLLBAR_ENVIRONMENT || process.env.NODE_ENV, - }; - } - - constructor(props: Props & DocumentProps) { - super(props); - - const { __NEXT_DATA__, ids } = props; - if (ids) { - (__NEXT_DATA__ as any).ids = ids; - } - } - - render() { - const { userAgent, rollbarAccessToken, rollbarEnvironment } = this.props; - - return ( - - - - - {process.env.GTM_CONTAINER_ID && ( - - - - - - - -
    -
    -
    -
    -

    $templateMessages.getMessage($messageKeyPrefix, "headerMessage")

    -
    - -
    - #if($authnMessageKey) -
    - $templateMessages.getMessage($authnMessageKey) -
    - #end - - #if(($errorMessageKeys) && ($errorMessageKeys.size() > 0)) -
    - #if($errorMessageKeys.size() == 1) - $templateMessages.getMessage($messageKeyPrefix, $errorMessageKeys.get(0)) - #else -
      - #foreach($errorMessageKey in $errorMessageKeys) -
    • $templateMessages.getMessage($messageKeyPrefix, $errorMessageKey)
    • - #end -
    - #end -
    - #end -
    - - -
    - -
    - -
    New passwords must:
    -
      -
    • Be at least 10 characters long
    • -
    • Use at least 3 of these: -
        -
      • A lowercase letter
      • -
      • An uppercase letter
      • -
      • A number
      • -
      • A special character
      • -
      -
    • -
    • Not have spaces
    • -
    • Not be longer than 32 characters
    • -
    - -
    Don't use personal info, like your name, ID or address. If you use just two consecutive characters from your name or ID in your password, it will fail. Your new password will have to be different than your last 5 passwords. -
    -
    - - - -
    - #if($usernameDisabled) - - - #else - -
    - - - - - -
    - $templateMessages.getMessage($messageKeyPrefix, "missingField") -
    -
    - #end - -
    - - - - -
    - $templateMessages.getMessage($messageKeyPrefix, "missingField") -
    -
    - -
    - - - - -
    - $templateMessages.getMessage($messageKeyPrefix, "missingField") -
    -
    - -
    - - - - -
    - $templateMessages.getMessage($messageKeyPrefix, "missingField") -
    -
    - - #if ($isPasswordExpiring) - - #end - -
    - - - - - - -
    -
    -
    -
    -
    -
    - - - - - - diff --git a/services-js/access-boston/src/server/ping-templates/html.form.login.template.html b/services-js/access-boston/src/server/ping-templates/html.form.login.template.html deleted file mode 100755 index 8b80625fa..000000000 --- a/services-js/access-boston/src/server/ping-templates/html.form.login.template.html +++ /dev/null @@ -1,334 +0,0 @@ - -#* -The server renders this HTML page in an end-user's browser when -needed authentication credentials may be obtained via HTTP Basic -Authentication or an HTML form. - -Velocity variables (identified by the $ character) are generated -at runtime by the server. - -The following variables are available on this page, but not used by default: - -$entityId - The entity ID (connection ID) of the SP Connection used in this SSO transaction -$connectionName - The name of the SP Connection used in this SSO transaction -$client_id - The ID of the OAuth client used in this transaction -$spAdapterId - The SP Adapter ID used in this transaction - -It is recommended to sanitize the values that are displayed using $escape.escape() for example $escape.escape($client_id). - -Change text or formatting as needed. Modifying Velocity statements -is not recommended as it may interfere with expected server behavior. -*# - - - -#set( $messageKeyPrefix = "html.form.login.template." ) - - -#set ($enableCheckboxByDefault = true) -#if($rememberUsernameCookieExists) - #set ($rememberUsernameChecked = "checked") -#else - #if($enableCheckboxByDefault) - - #set ($rememberUsernameChecked = "checked") - #else - - #set ($rememberUsernameChecked = "") - #end -#end - -#if(!$assetPath) - #set($assetPath = 'assets') -#end - - - - $templateMessages.getMessage($messageKeyPrefix, "title") - - - - - - - - - - - - - -
    -
    -
    -
    -

    Sign In

    -
    - -
    - #if($authnMessageKey) -
    $templateMessages.getMessage($authnMessageKey)
    - #end - - #if($errorMessageKey) -
    $templateMessages.getMessage($messageKeyPrefix, $errorMessageKey)
    - #end - - ## Uncomment below to display any additional server error: - #* - #if($serverError) -
    $serverError
    - #end - *# -
    - - -
    - -
    - #if($altAuthSources.size() > 0) -

    - $templateMessages.getMessage($messageKeyPrefix, "title") -

    - #end - - -
    - - - #if($usernameEditable) - - - #else - - #end - -
    - $templateMessages.getMessage($messageKeyPrefix, "missingField") -
    -
    - -
    - - - - -
    - $templateMessages.getMessage($messageKeyPrefix, "missingField") -
    -
    - - #if($enableRememberUsername) -
    - -
    - #end - - - - #if($supportsPasswordChange || $supportsPasswordReset) -
    - #if($supportsPasswordChange) - $templateMessages.getMessage($messageKeyPrefix, "changePassword") - #end - - #if($supportsPasswordChange && $supportsPasswordReset) - | - #end - - #if($supportsPasswordReset) - - - - #end -
    - #end - - - #if($registrationEnabled) -
    - - - $templateMessages.getMessage($messageKeyPrefix, "noAccountMessage") - -
    - #end -
    - - -
    - #if($altAuthSources.size() > 0) - - #end - #else - - - - #end - -
    -
    - - -
    -
    -
    - - - - - diff --git a/services-js/access-boston/src/server/ping-templates/idp.logout.success.page.template.html b/services-js/access-boston/src/server/ping-templates/idp.logout.success.page.template.html deleted file mode 100755 index eb23e13cd..000000000 --- a/services-js/access-boston/src/server/ping-templates/idp.logout.success.page.template.html +++ /dev/null @@ -1,80 +0,0 @@ - -#* -The server renders this HTML page in an end-user's browser when a -logout request succeeds but no redirect URL is specified. - -Velocity variables (identified by the $ character) are generated -at runtime by the server. - -Change text or formatting as needed. Modifying Velocity statements -is not recommended as it may interfere with expected server behavior. -*# - - -#set( $messageKeyPrefix = "idp.logout.success.page.template." ) - -#if(!$assetPath) - #set($assetPath = 'assets') -#end - - - - $templateMessages.getMessage($messageKeyPrefix, "title") - - - - - - - - - - - - - -
    -
    -
    -

    Logged Out

    -
    - - -
    - -
    - $templateMessages.getMessage($messageKeyPrefix, "info") -
    - -
    - -

    Need help? Please Contact:

    - -

    Boston Public Schools

    - - -

    All Other Departments

    - - -
    -
    - - ## Uncomment below to display additional dynamic content to user: - #* -
    -
    $templateMessages.getMessage("global.fullStackTraceTitle")
    -
    $fullStackTrace
    -
    - *# -
    - -
    - - - diff --git a/services-js/access-boston/src/server/ping-templates/mockData.ts b/services-js/access-boston/src/server/ping-templates/mockData.ts deleted file mode 100644 index 30c2c36f9..000000000 --- a/services-js/access-boston/src/server/ping-templates/mockData.ts +++ /dev/null @@ -1,74 +0,0 @@ -export default { - assetPath: 'assets/ping/assets', - name: 'name', - username: 'city-person', - pass: 'pass', - newPass1: 'newPass2', - newPass2: 'newPass1', - PingFedBaseURL: '/', - forgotPasswordUrl: '/', - changePasswordUrl: '/', - enableRememberUsername: false, - rememberUsernameCookieExists: false, - enableCheckboxByDefault: false, - rememberUsernameChecked: 'checked', - usernameEditable: false, - supportsPasswordChange: false, - supportsPasswordReset: false, - registrationEnabled: false, - templateMessages: { - strings: { - title: 'Page Title', - usernameTitle: 'Username', - passwordTitle: 'Password', - newPassword1Title: 'New Password', - newPassword2Title: 'Confirm Password', - missingField: 'Please fill out this field', - rememberUsernameTitle: 'Remember Username', - signInButtonTitle: 'Sign In', - changeButtonTitle: 'Change Password', - cancelButtonTitle: 'Cancel', - changePassword: 'Change Password', - forgotPassword: 'Forgot Password', - noAccountMessage: 'No Account', - registerAccount: 'Register Account', - loginWithButtonTitle: 'Login', - info: 'You have now been logged out.', - headerMessage: 'Change Password', - }, - getMessage: function(_prefix, message) { - return this.strings[message]; - }, - }, - locale: { - getLanguage: () => 'en', - }, - orientation: 'ltr', - url: '', - authnMessageKey: '', - errorMessageKey: '', - serverError: '', - altAuthSources: { - authSource: [], - size: () => 0, - }, - loginFailed: false, - adapterIdField: 'adapterId Field', - adapterId: 'adapterId', - localizedMessageResolver: { - strings: { - locale: 'en', - }, - resolveMessage: function(_locale, message) { - if (message === 'pa.error.contact.system.administrator') { - return 'No known federation session to logout or SLO is not configured'; - } - return message; - }, - }, - Encode: { - forHtml: parameters => parameters, - }, - title: 'Sign Off Error', - lang: 'en', -}; diff --git a/services-js/access-boston/src/server/services/IdVerification.ts b/services-js/access-boston/src/server/services/IdVerification.ts deleted file mode 100644 index 6f23f5cbc..000000000 --- a/services-js/access-boston/src/server/services/IdVerification.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** - * Service to connect, process, etc ID Verification requests. - */ - -export default class IdVerification { - // employeeId: string; - // constructor( - // employeeId: string | undefined, - // ) { - // if (!employeeId) { - // throw new Error('ID Verificaiton Employee ID was not provided'); - // } - // (this.employeeId = employeeId ? employeeId : ''); - // } -} diff --git a/services-js/access-boston/src/server/services/IdVerificationFake.ts b/services-js/access-boston/src/server/services/IdVerificationFake.ts deleted file mode 100644 index fec48126c..000000000 --- a/services-js/access-boston/src/server/services/IdVerificationFake.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { promisify } from 'util'; -import fs from 'fs'; -import path from 'path'; -// import IdVerification from './IdVerification'; - -const readFile = promisify(fs.readFile); - -export default class IdVerificationFake { - async loadFixture(filename: string, env: string): Promise { - return JSON.parse( - await readFile( - path.join( - __dirname, - `../../../fixtures/id-verification/${env}/${filename}.json` - ), - 'utf-8' - ) - ); - } -} diff --git a/services-js/access-boston/src/server/services/IdentityIq.ts b/services-js/access-boston/src/server/services/IdentityIq.ts deleted file mode 100644 index 7f49228ec..000000000 --- a/services-js/access-boston/src/server/services/IdentityIq.ts +++ /dev/null @@ -1,271 +0,0 @@ -import fetch from 'node-fetch'; -import url from 'url'; - -const TASK_RESULT_SCHEMA = - 'urn:ietf:params:scim:schemas:sailpoint:1.0:TaskResult'; -const LAUNCHED_WORKFLOW_SCHEMA = - 'urn:ietf:params:scim:schemas:sailpoint:1.0:LaunchedWorkflow'; - -const CHANGE_PASSWORD_WORKFLOW = 'CoB-Workflow-ChangePasswordRESTOnly'; -const RESET_PASSWORD_WORKFLOW = 'CoB-Workflow-ForgortPassword'; -const REGISTER_USER_WORKFLOW = 'CoB-Workflow-MFARegistration'; - -export interface WorkflowResponse { - totalResults: number; - startIndex: number; - schemas: string[]; - Resources: WorkflowDescription[]; -} - -export interface WorkflowDescription { - meta: { - created: string; - location: string; - version: string; - resourceType: 'Workflow'; - }; - schemas: string[]; - name: string; - id: string; - type?: string; - description?: string; -} - -interface ApiError { - schemas: ['urn:ietf:params:scim:api:messages:2.0:Error']; - detail: string; - status: '500'; -} - -interface LaunchWorkflowRequest { - schemas: string[]; - [LAUNCHED_WORKFLOW_SCHEMA]: { - workflowName: string; - input: Array<{ - key: string; - value: string; - type?: string; - }>; - }; -} - -export interface LaunchedWorkflowResponse { - partitioned: boolean; - completed?: string; - type: 'Workflow'; - launched: string; - pendingSignoffs: number; - [LAUNCHED_WORKFLOW_SCHEMA]: { - output: Array<{ - type?: string; - value: string; - key: string; - }>; - input: [{}]; - workflowSummary: string; - workflowName: string; - }; - meta: { - created: string; - location: string; - version: string; - resourceType: 'LaunchedWorkflow'; - }; - schemas: string[]; - name: string; - messages: string[]; - attributes: Array<{ - value: string; - key: string; - type?: string; - }>; - id: string; - completionStatus?: 'Error' | 'Success'; - taskDefinition: string; - terminated: boolean; - launcher: string; -} - -/** - * Service to connect to IdentityIQ to change passwords and handle other - * workflow tasks. - */ -export default class IdentityIq { - private url: string; - private username: string; - private password: string; - - constructor( - url: string | undefined, - username: string | undefined, - password: string | undefined - ) { - if (!url) { - throw new Error('IdentityIQ URL not provided'); - } - - if (!username) { - throw new Error('IdentityIQ username not provided'); - } - - if (!password) { - throw new Error('IdentityIQ password not provided'); - } - - this.url = url; - this.username = username; - this.password = password; - } - - private makeScimUrl(path: string): string { - return url.resolve(this.url, `scim/v2/${path}`); - } - - private makeDefaultHeaders() { - return { - Authorization: - 'Basic ' + - Buffer.from(this.username + ':' + this.password).toString('base64'), - }; - } - - private async makeScimRequest( - path: string, - method: string = 'GET', - body: any = undefined - ): Promise { - const response = await fetch(this.makeScimUrl(path), { - method, - body: body ? JSON.stringify(body) : undefined, - headers: this.makeDefaultHeaders(), - timeout: 60 * 1000, - }); - - if (!response.ok) { - const responseText = await response.json(); - try { - const apiError: ApiError = JSON.parse(responseText); - throw new Error(apiError.detail); - } catch { - // eslint-disable-next-line no-console - console.error(responseText); - throw new Error(`IdentityIQ ${response.statusText}`); - } - } - - const output = await response.json(); - // eslint-disable-next-line no-console - console.debug(JSON.stringify(output, null, 2)); - return output; - } - - async changePassword( - userId: string, - currentPassword: string, - newPassword: string - ): Promise { - const requestBody: LaunchWorkflowRequest = { - schemas: [LAUNCHED_WORKFLOW_SCHEMA, TASK_RESULT_SCHEMA], - [LAUNCHED_WORKFLOW_SCHEMA]: { - workflowName: CHANGE_PASSWORD_WORKFLOW, - input: [ - { - key: 'currentSecret', - value: currentPassword, - }, - { - key: 'newSecret', - value: newPassword, - }, - { - key: 'launcher', - value: userId, - }, - { - key: 'transient', - value: 'false', - }, - ], - }, - }; - - return this.makeScimRequest('LaunchedWorkflows', 'POST', requestBody); - } - - async resetPassword( - userId: string, - newPassword: string, - token: string - ): Promise { - const requestBody: LaunchWorkflowRequest = { - schemas: [LAUNCHED_WORKFLOW_SCHEMA, TASK_RESULT_SCHEMA], - [LAUNCHED_WORKFLOW_SCHEMA]: { - workflowName: RESET_PASSWORD_WORKFLOW, - input: [ - { - key: 'newSecret', - value: newPassword, - }, - { - key: 'launcher', - value: userId, - }, - { - key: 'token', - value: token, - }, - { - key: 'transient', - value: 'false', - }, - ], - }, - }; - - return this.makeScimRequest('LaunchedWorkflows', 'POST', requestBody); - } - - /** - * Updates the User in IIQ to indicate that device registration has succeeded. - * Saves the email or phone number used for the device. - */ - async updateUserRegistration( - userId: string, - { email, phoneNumber }: { email?: string; phoneNumber?: string } - ) { - const requestBody: LaunchWorkflowRequest = { - schemas: [LAUNCHED_WORKFLOW_SCHEMA, TASK_RESULT_SCHEMA], - [LAUNCHED_WORKFLOW_SCHEMA]: { - workflowName: REGISTER_USER_WORKFLOW, - input: [ - { - key: 'mfaEmail', - value: email || '', - }, - { - key: 'mfaPhone', - value: phoneNumber || '', - }, - { - key: 'launcher', - value: userId, - }, - { - key: 'isUserRegistered', - value: 'true', - }, - { - key: 'transient', - value: 'false', - }, - ], - }, - }; - - return this.makeScimRequest('LaunchedWorkflows', 'POST', requestBody); - } - - async fetchWorkflow(caseId: string): Promise { - return this.makeScimRequest(`LaunchedWorkflows/${caseId}`); - } -} diff --git a/services-js/access-boston/src/server/services/IdentityIqFake.ts b/services-js/access-boston/src/server/services/IdentityIqFake.ts deleted file mode 100644 index d4b98cfcc..000000000 --- a/services-js/access-boston/src/server/services/IdentityIqFake.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { promisify } from 'util'; -import fs from 'fs'; -import path from 'path'; -import IdentityIq, { LaunchedWorkflowResponse } from './IdentityIq'; - -const readFile = promisify(fs.readFile); - -export default class IdentityIqFake implements Required { - private async loadFixture(name: string): Promise { - return JSON.parse( - await readFile( - path.join(__dirname, `../../../fixtures/identityiq/${name}.json`), - 'utf-8' - ) - ); - } - - async changePassword(_userId, password): Promise { - if (password === 'wrong-password') { - return this.loadFixture('LaunchWorkflow-ChangePassword-failure'); - } else { - return this.loadFixture('LaunchWorkflow-ChangePassword-success'); - } - } - - async updateUserRegistration(): Promise {} - - async resetPassword( - _userId, - _password, - token - ): Promise { - if (!token) { - throw new Error('Reset password token was not provided'); - } - - return this.loadFixture('LaunchWorkflow-ResetPassword-success'); - } - - async fetchWorkflow(): Promise { - return this.loadFixture('LaunchWorkflow-ChangePassword-failure'); - } -} diff --git a/services-js/access-boston/src/server/services/PingId.ts b/services-js/access-boston/src/server/services/PingId.ts deleted file mode 100644 index b7d8f27fb..000000000 --- a/services-js/access-boston/src/server/services/PingId.ts +++ /dev/null @@ -1,344 +0,0 @@ -import { of as loadProperties } from 'java-properties'; -import jws from 'jws'; -import fetch from 'node-fetch'; - -const PINGID_VERSION = '4.9'; -const PINGID_LOCALE = 'en'; - -function makePingIdTimestamp() { - return new Date() - .toISOString() - .replace('T', ' ') - .replace('Z', ''); -} - -/** - * A handful of the error IDs that Ping will return. - * - * @see https://www.pingidentity.com/content/developer/en/api/pingid-api/pingid-api-error-codes.html - */ -export enum ErrorId { - OK = 200, - USER_NOT_EXIST = 10564, - WRONG_PASSWORD = 20513, - INVALID_SESSION = 20517, - INVALID_PHONE_NUMBER = 20559, - FAILED_TO_SEND_OTP = 20558, -} - -export class ApiError extends Error { - readonly errorId: ErrorId; - - constructor(errorId: ErrorId, message: string) { - super(message); - this.errorId = errorId; - } -} - -interface CommonResponse { - errorMsg: string | 'ok'; - uniqueMsgId: string; - errorId: ErrorId; - clientData: any; -} - -type UserStatus = - | 'ACTIVE' - | 'NOT_ACTIVE' - | 'PENDING_ACTIVATION' - | 'SUSPENDED' - | 'PENDING_CHANGE_DEVICE'; - -enum UserRole { - REGULAR = 'REGULAR', - ADMIN = 'ADMIN', -} - -export interface UserDetails { - userName: string; - email: string | null; - lname: string; - userId: number; - userInBypass: boolean; - userEnabled: boolean; - fname: string; - picURL: string; - spList: []; - lastLogin: number | null; - bypassExpiration: null; - deviceDetails: Object | null; - lastTransactions: []; - devicesDetails: []; - status: UserStatus; - role: UserRole; -} - -interface UserDetailsResponseBody extends CommonResponse { - userDetails: UserDetails; - sameDeviceUsersDetails: []; -} - -interface AddUserResponseBody extends CommonResponse { - activationCode: string; - userDetails: UserDetails; -} - -interface StartPairingResponseBody extends CommonResponse { - sessionId: string; -} - -interface FinalizePairingResponseBody extends CommonResponse {} - -interface JwsResponse { - header: {}; - /** This is a JSON-encoded object of type JwsPayload. */ - payload: string; -} - -type JwsPayload = { - responseBody: T; -}; - -/** - * How the OTP should be sent for device pairing. These values directly match - * the Ping API’s expected input. - */ -export enum VerificationType { - SMS = 'SMS', - VOICE = 'VOICE', - EMAIL = 'EMAIL', -} - -export interface PingIdOptions { - endpoint: string; - jwsKey: Buffer; - token: string; - orgAlias: string; -} - -export interface AddUserArgs { - userId: string; - firstName: string; - lastName: string; - email: string; -} - -/** - * Service class for connecting to the PingID API. - * - * PingID is used to manage the multi-factor auth parts of the IAM project. The - * Access Boston portal uses it to set up a user’s initial MFA device. - */ -export default class PingId { - private endpoint: string; - private jwsKey: Buffer; - private token: string; - private orgAlias: string; - - constructor({ endpoint, jwsKey, token, orgAlias }: PingIdOptions) { - this.endpoint = endpoint; - this.jwsKey = jwsKey; - this.token = token; - this.orgAlias = orgAlias; - } - - /** - * Internal method to make a JWS-signed request to Ping’s API. Returns the - * decoded-and-parsed responseBody. - * - * @throws ApiError on non-200 responses - * - * @see - * https://www.pingidentity.com/content/developer/en/api/pingid-api.html#using_the_api - */ - private async makeRequest( - path: string, - body: Object - ): Promise { - const header = { - alg: 'HS256', - org_alias: this.orgAlias, - token: this.token, - }; - - const payload = { - reqHeader: { - orgAlias: this.orgAlias, - secretKey: this.token, - timestamp: makePingIdTimestamp(), - version: PINGID_VERSION, - locale: PINGID_LOCALE, - }, - reqBody: body, - }; - - const signedBody = jws.sign({ - header, - payload, - secret: this.jwsKey, - }); - const url = `${this.endpoint}/rest/4/${path}/do`; - - const resp = await fetch(url, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Content-Length': Buffer.byteLength(signedBody, 'UTF-8').toString(), - }, - body: signedBody, - }); - - if (resp.ok) { - const decoded: JwsResponse = jws.decode(await resp.text()); - const payload: JwsPayload = JSON.parse(decoded.payload); - return payload.responseBody; - } else { - const responseText = await resp.text(); - - // Sometimes we get a raw JSON response, sometimes we get one that’s - // JWS-encoded. No, headers don’t tell us when it’s one or the other, so - // we peek at the first char in order to guess. :-P - if (responseText.startsWith('{')) { - const { responseBody } = JSON.parse(responseText); - - throw new ApiError(responseBody.errorId, responseBody.errorMsg); - } else { - const decoded: JwsResponse = jws.decode(responseText); - const payload: JwsPayload = JSON.parse(decoded.payload); - - // eslint-disable-next-line no-console - console.error(payload.responseBody); - - throw new ApiError( - payload.responseBody.errorId, - payload.responseBody.errorMsg - ); - } - } - } - - /** - * Adds a new user to PingID. Before a user is registered they can exist in - * IdentityIQ but not in Ping. - * - * Will throw an exception if the user is already in Ping, so call - * getUserDetails first. - */ - async addUser({ - userId, - firstName, - lastName, - email, - }: AddUserArgs): Promise { - return (await this.makeRequest('adduser', { - activateUser: true, - username: userId, - fName: firstName, - lname: lastName, - role: UserRole.REGULAR, - email, - })).userDetails; - } - - /** - * Loads a user by its ID. Returns null if the user was not found. - */ - async getUserDetails(userId: string): Promise { - try { - return (await this.makeRequest( - 'getuserdetails', - { - getSameDeviceUsers: false, - userName: userId, - } - )).userDetails; - } catch (e) { - if (e instanceof ApiError && e.errorId === ErrorId.USER_NOT_EXIST) { - return null; - } else { - throw e; - } - } - } - - /** - * Starts the MFA pairing process for a phone number or email address. Calling - * this method will cause Ping to send an email, SMS, or make a phone call. - * - * @returns The sessionId that will be used for finalizePairing. - */ - async startPairing( - userId: string, - type: VerificationType, - phoneOrEmail: string - ): Promise { - return (await this.makeRequest( - 'startofflinepairing', - { - username: userId, - type, - pairingData: phoneOrEmail, - } - )).sessionId; - } - - /** - * Completes the pairing for the given session ID with the given one-time - * password. - * - * Returns "true" in case of success. If the OTP is not correct, will return - * the WRONG_PASSWORD error ID. - * - * Other server errors will throw (such as invalid session ID) since they’re - * not recoverable in the same way that a wrong password is. - */ - async finalizePairing( - sessionId: string, - otp: string - ): Promise { - try { - await this.makeRequest( - 'finalizeofflinepairing', - { sessionId, otp } - ); - - return true; - } catch (e) { - if (e instanceof ApiError && e.errorId === ErrorId.WRONG_PASSWORD) { - return ErrorId.WRONG_PASSWORD; - } else { - throw e; - } - } - } -} - -/** - * Keys for the data in the pingid.properties file. - */ -const PROPS_KEYS = { - useBase64Key: 'use_base64_key', - useSignature: 'use_signature', - token: 'token', - idpUrl: 'idp_url', - orgAlias: 'org_alias', - adminUrl: 'admin_url', - authenticatorUrl: 'authenticator_url', -}; - -/** - * Makes a PingId object from the pingid.properties configuration file that’s - * exported from Ping. - */ -export async function pingIdFromProperties( - propertiesFileName: string -): Promise { - const props = loadProperties(propertiesFileName); - - const endpoint = props.get(PROPS_KEYS.idpUrl); - const jwsKey = Buffer.from(props.get(PROPS_KEYS.useBase64Key), 'base64'); - const token = props.get(PROPS_KEYS.token); - const orgAlias = props.get(PROPS_KEYS.orgAlias); - - return new PingId({ endpoint, jwsKey, token, orgAlias }); -} diff --git a/services-js/access-boston/src/server/services/PingIdFake.ts b/services-js/access-boston/src/server/services/PingIdFake.ts deleted file mode 100644 index 1bcb27a81..000000000 --- a/services-js/access-boston/src/server/services/PingIdFake.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { promisify } from 'util'; -import fs from 'fs'; -import path from 'path'; -import PingId, { UserDetails, ErrorId } from './PingId'; - -const readFile = promisify(fs.readFile); - -export default class PingIdFake implements Required { - private async loadFixture(name: string): Promise { - return JSON.parse( - await readFile( - path.join(__dirname, `../../../fixtures/pingid/${name}.json`), - 'utf-8' - ) - ); - } - - async addUser() { - return (await this.loadFixture('adduser')).responseBody; - } - - async getUserDetails(userId: string): Promise { - if (userId.startsWith('NEW')) { - return null; - } else { - return (await this.loadFixture('getuserdetails-exists')).responseBody - .userDetails; - } - } - - async startPairing(): Promise { - return 'oacts_rxodmgpbVkjVltIBVP7C7m6y6ddsOY-a8BYqpDHHxZY'; - } - - async finalizePairing( - _sessionId, - otp - ): Promise { - if (otp === '999999') { - return ErrorId.WRONG_PASSWORD; - } else { - return true; - } - } -} diff --git a/services-js/access-boston/src/server/services/SamlAuth.test.ts b/services-js/access-boston/src/server/services/SamlAuth.test.ts deleted file mode 100644 index 706006db4..000000000 --- a/services-js/access-boston/src/server/services/SamlAuth.test.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { makeIdentityProvider } from './SamlAuth'; -import path from 'path'; -import fs from 'fs'; -import crypto from 'crypto'; - -describe('makeIdentityProvider', () => { - let samlMetadata: Buffer; - - beforeEach(() => { - samlMetadata = fs.readFileSync( - path.join(__dirname, '../../../fixtures/saml-metadata.xml') - ); - }); - - it('parses the xml', async () => { - const identityProvider = await makeIdentityProvider(samlMetadata); - expect(identityProvider).toMatchSnapshot(); - }); - - it('returns valid certificates', async () => { - const identityProvider = await makeIdentityProvider(samlMetadata); - const certificate = identityProvider.certificates[0]; - - // We use publicEncrypt just to test that the certificate is parsable. - const out = crypto.publicEncrypt(certificate, new Buffer('TEST TEXT')); - expect(out.toString('base64').length).toBeGreaterThan(1); - }); -}); diff --git a/services-js/access-boston/src/server/services/SamlAuth.ts b/services-js/access-boston/src/server/services/SamlAuth.ts deleted file mode 100644 index f4c642b48..000000000 --- a/services-js/access-boston/src/server/services/SamlAuth.ts +++ /dev/null @@ -1,385 +0,0 @@ -import fs from 'fs'; -import { DOMParser } from 'xmldom'; -import xpath from 'xpath'; -import { IdentityProvider, ServiceProvider } from 'saml2-js'; - -export interface SamlConfigPaths { - serviceProviderKeyPath: string; - serviceProviderCertPath: string; - metadataPath: string; -} - -interface SamlResponseHeader { - version: '2.0'; - destination: string; - in_response_to: string; - id: string; -} - -interface SamlAuthAssertion { - response_header: SamlResponseHeader; - type: 'authn_response'; - user: { - name_id: string; - session_index: string; - attributes: { - groups?: string[]; - FirstName?: string[]; - LastName?: string[]; - email?: string[]; - changePasswordRequired?: string[]; - mfaRegistrationRequired?: string[]; - userAccessToken?: string[]; - userMFARegistrationDate?: string[]; - isUserRegistered?: string[]; - cobUserAgency?: string[]; - }; - }; -} - -interface SamlLogoutRequestAssertion { - response_header: SamlResponseHeader; - type: 'logout_request'; - issuer: string; - name_id: string; - session_index: string; -} - -type SamlAssertion = SamlAuthAssertion | SamlLogoutRequestAssertion; - -export type SamlRequestPostBody = { - SAMLRequest: string; - RelayState: string; -}; - -export type SamlResponsePostBody = { - SAMLResponse: string; -}; - -export interface ServiceProviderConfig { - metadataUrl: string; - assertUrl: string; -} - -export interface SamlLoginResult { - type: 'login'; - nameId: string; - sessionIndex: string; - firstName: string; - lastName: string; - email: string; - groups: string[]; - needsNewPassword: boolean; - needsMfaDevice: boolean; - hasMfaDevice: boolean; - userAccessToken: string; - /** Format is MM/DD/YYYY */ - userMfaRegistrationDate: string | null; - cobAgency: string | null; -} - -export interface SamlLogoutRequestResult { - type: 'logout'; - requestId: string; - nameId: string; - sessionIndex: string; -} - -export type SamlAssertResult = SamlLoginResult | SamlLogoutRequestResult; - -const SAML_METADATA_NAMESPACES = { - md: 'urn:oasis:names:tc:SAML:2.0:metadata', - ds: 'http://www.w3.org/2000/09/xmldsig#', -}; - -const REDIRECT_BINDING_URI = - 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'; - -export async function makeIdentityProvider( - metadata: Buffer, - logoutUrl: string | null = null -): Promise { - const parser = new DOMParser(); - const select = xpath.useNamespaces(SAML_METADATA_NAMESPACES); - const doc = parser.parseFromString(metadata.toString('utf-8')); - - const ssoDescriptorElement = select( - '//md:IDPSSODescriptor', - doc, - true - ) as Element; - - const signRequests = - ssoDescriptorElement.getAttribute('WantAuthnRequestsSigned') === 'true'; - - const redirectBindingElement = select( - `//md:SingleSignOnService[@Binding='${REDIRECT_BINDING_URI}']`, - doc, - true - ) as Element; - - const redirectUrl = redirectBindingElement.getAttribute('Location') || ''; - - const signingCertificateElements = select( - "//md:KeyDescriptor[@use='signing']", - doc - ) as Element[]; - - // The textContent is a Base64-encoded binary cert in DER format. We can - // turn that into PEM just by adding the BEGIN/END bits. - // - // If we format the XML then there are some newlines around the Base64 - // content than need to be trimmed. - const certificates = signingCertificateElements.map(el => - [ - '-----BEGIN CERTIFICATE-----', - el.textContent!.trim(), - '-----END CERTIFICATE-----', - ].join('\n') - ); - - return new IdentityProvider({ - sso_login_url: redirectUrl, - sso_logout_url: logoutUrl || redirectUrl, - sign_get_request: signRequests, - certificates, - }); -} - -export async function makeServiceProvider( - { metadataUrl, assertUrl }: ServiceProviderConfig, - serviceProviderCert: Buffer, - serviceProviderKey: Buffer -) { - const privateKey = serviceProviderKey.toString('utf-8'); - const cert = serviceProviderCert.toString('utf-8'); - - return new ServiceProvider({ - entity_id: metadataUrl, - private_key: privateKey, - certificate: cert, - assert_endpoint: assertUrl, - allow_unencrypted_assertion: true, - }); -} - -export async function makeSamlAuth( - { - serviceProviderKeyPath, - serviceProviderCertPath, - metadataPath, - }: SamlConfigPaths, - serviceProviderConfig: ServiceProviderConfig, - logoutUrl: string -): Promise { - const metadata: Promise = new Promise((resolve, reject) => { - fs.readFile(metadataPath, (err, buf) => { - if (err) { - if (err.code === 'ENOENT') { - resolve(null); - } else { - reject(err); - } - } else { - resolve(buf); - } - }); - }); - - const serviceProviderKey: Promise = new Promise((resolve, reject) => { - fs.readFile(serviceProviderKeyPath, (err, buf) => { - if (err) { - reject(err); - } else { - resolve(buf); - } - }); - }); - - const serviceProviderCert: Promise = new Promise( - (resolve, reject) => { - fs.readFile(serviceProviderCertPath, (err, buf) => { - if (err) { - reject(err); - } else { - resolve(buf); - } - }); - } - ); - - const metadataBuffer = await metadata; - - const [identityProvider, serviceProvider] = await Promise.all([ - metadataBuffer ? makeIdentityProvider(metadataBuffer, logoutUrl) : null, - makeServiceProvider( - serviceProviderConfig, - await serviceProviderCert, - await serviceProviderKey - ), - ]); - - return new SamlAuth(identityProvider, serviceProvider); -} - -export default class SamlAuth { - private identityProvider: IdentityProvider | null; - private serviceProvider: ServiceProvider; - - constructor( - identityProvider: IdentityProvider | null, - serviceProvider: ServiceProvider - ) { - this.identityProvider = identityProvider; - this.serviceProvider = serviceProvider; - } - - getMetadata(): string { - return this.serviceProvider.create_metadata(); - } - - makeLoginUrl(): Promise { - return new Promise((resolve, reject) => { - this.serviceProvider.create_login_request_url( - this.identityProvider, - {}, - (err, loginUrl) => { - if (err) { - return reject(err); - } - - resolve(loginUrl); - } - ); - }); - } - - public makeLogoutSuccessUrl( - requestId: string, - relayState: string - ): Promise { - return new Promise((resolve, reject) => { - this.serviceProvider.create_logout_response_url( - this.identityProvider, - { - in_response_to: requestId, - sign_get_request: true, - relay_state: relayState, - }, - (err, successUrl) => { - if (err) { - reject(err); - } else { - resolve(successUrl); - } - } - ); - }); - } - - private async processSamlAssertion( - saml: SamlAssertion - ): Promise { - // eslint-disable-next-line no-console - console.debug('SAML RESPONSE', JSON.stringify(saml, null, 2)); - // eslint-disable-next-line no-console - // console.log('SAML RESPONSE', JSON.stringify(saml, null, 2)); - - switch (saml.type) { - case 'authn_response': { - const { user } = saml; - const { attributes } = user; - - // eslint-disable-next-line no-console - // console.log('processSamlAssertion: ', attributes, user, saml); - - return { - type: 'login', - nameId: user.name_id, - sessionIndex: user.session_index, - firstName: (attributes.FirstName && attributes.FirstName[0]) || '', - lastName: (attributes.LastName && attributes.LastName[0]) || '', - email: (attributes.email && attributes.email[0]) || '', - groups: attributes.groups || [], - needsNewPassword: attributeIsTrue(attributes.changePasswordRequired), - needsMfaDevice: attributeIsTrue(attributes.mfaRegistrationRequired), - hasMfaDevice: attributeIsTrue(attributes.isUserRegistered), - userAccessToken: - (attributes.userAccessToken && attributes.userAccessToken[0]) || '', - userMfaRegistrationDate: - (attributes.userMFARegistrationDate && - attributes.userMFARegistrationDate[0]) || - null, - cobAgency: - (attributes.cobUserAgency && attributes.cobUserAgency[0]) || null, - }; - } - case 'logout_request': - return { - type: 'logout', - requestId: saml.response_header.id, - nameId: saml.name_id, - sessionIndex: saml.session_index, - }; - - default: - throw new Error( - `Unrecognized SAML assertion type: ${(saml as any).type}` - ); - } - } - - handlePostAssert( - body: SamlRequestPostBody | SamlResponsePostBody - ): Promise { - return new Promise((resolve, reject) => { - this.serviceProvider.post_assert( - this.identityProvider, - { request_body: body }, - (err, saml: SamlAssertion) => { - if (err) { - // eslint-disable-next-line no-console - console.debug('SAML assert error', body); - - reject(err); - return; - } - - try { - resolve(this.processSamlAssertion(saml)); - } catch (e) { - reject(e); - } - } - ); - }); - } - - handleGetAssert(query: { - [key: string]: string | string[]; - }): Promise { - return new Promise((resolve, reject) => { - this.serviceProvider.redirect_assert( - this.identityProvider, - { request_body: query }, - (err, saml: SamlAssertion) => { - if (err) { - // eslint-disable-next-line no-console - console.debug('SAML assert error', query); - reject(err); - return; - } - - try { - resolve(this.processSamlAssertion(saml)); - } catch (e) { - reject(e); - } - } - ); - }); - } -} - -const attributeIsTrue = (attr: string[] | undefined): boolean => - !!(attr && attr[0] && attr[0].toLowerCase() === 'true'); diff --git a/services-js/access-boston/src/server/services/SamlAuthFake.ts b/services-js/access-boston/src/server/services/SamlAuthFake.ts deleted file mode 100644 index ce9a9877c..000000000 --- a/services-js/access-boston/src/server/services/SamlAuthFake.ts +++ /dev/null @@ -1,91 +0,0 @@ -import SamlAuth, { - SamlLoginResult, - SamlAssertResult, - SamlLogoutRequestResult, -} from './SamlAuth'; -import { RequestQuery } from 'hapi'; - -export interface SamlAuthFakeOptions { - loginFormUrl: string; -} - -export default class SamlAuthFake implements Required { - private loginFormUrl: string; - - constructor({ loginFormUrl }: SamlAuthFakeOptions) { - this.loginFormUrl = loginFormUrl; - } - - getMetadata(): string { - return ''; - } - - makeLoginUrl(): Promise { - return Promise.resolve(this.loginFormUrl); - } - - makeLogoutSuccessUrl(): Promise { - return Promise.resolve('/'); - } - - handlePostAssert(body: any): Promise { - const userId = body.userId; - - if (!userId) { - throw new Error('userId is blank'); - } - - const isNewUser = userId.startsWith('NEW'); - - const result: SamlLoginResult = { - type: 'login', - nameId: userId, - sessionIndex: 'session', - firstName: 'Test', - lastName: 'User', - email: 'test@boston.gov', - groups: [ - 'COB-Group-TestGrp01', - 'SG_AB_IAM_TEAM', - 'SG_AB_TANIUM', - 'SG_AB_SERVICEDESK_USERS', - 'SG_AB_BLDGMAINTREQ', - 'SG_AB_CONFIRMID', - 'SG_AB_GRPMGMT_CIVIS', - // 'SG_AB_GRPMGMT_EBUILDER', - // 'SG_AB_GRPMGMT_AUDITING', - // 'SG_AB_GRPMGMT_TANIUM', - // 'SG_AB_GRPMGMT_PSHCM', - // 'SG_AB_GRPMGMT_Lagan_Groups', - ], - needsNewPassword: isNewUser, - needsMfaDevice: isNewUser && userId !== 'NEW88888', - hasMfaDevice: !isNewUser, - userAccessToken: 'jfqWE7DExC4nUa7pvkABezkM4oNT', - userMfaRegistrationDate: '04/17/2019', - cobAgency: 'CH', - }; - // eslint-disable-next-line no-console - // console.log('SamlAuthFake > handlePostAssert > result: ', result); - return Promise.resolve(result); - } - - handleGetAssert(query: RequestQuery): Promise { - const result: SamlLogoutRequestResult = { - type: 'logout', - requestId: '4', - nameId: Array.isArray(query.userId) ? query.userId[0] : query.userId, - sessionIndex: 'session', - }; - return Promise.resolve(result); - } -} - -export function makeFakeLoginHandler(path: string, userId: string) { - return () => - `
    -
    ${path}
    - - -
    `; -} diff --git a/services-js/access-boston/src/server/services/__snapshots__/SamlAuth.test.ts.snap b/services-js/access-boston/src/server/services/__snapshots__/SamlAuth.test.ts.snap deleted file mode 100644 index 693bf3745..000000000 --- a/services-js/access-boston/src/server/services/__snapshots__/SamlAuth.test.ts.snap +++ /dev/null @@ -1,16 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`makeIdentityProvider parses the xml 1`] = ` -IdentityProvider { - "certificates": Array [ - "-----BEGIN CERTIFICATE----- -MIIHuzCCBaOgAwIBAgITFgAAAO/Occ90ikDr+wAAAAAA7zANBgkqhkiG9w0BAQsFADBwMRMwEQYKCZImiZPyLGQBGRYDY29iMRYwFAYKCZImiZPyLGQBGRYGYm9zdG9uMRgwFgYKCZImiZPyLGQBGRYIY2l0eWhhbGwxJzAlBgNVBAMTHkNpdHlPZkJvc3Rvbi1FbnRlcnByaXNlLVN1Yi1DQTAeFw0xODAyMjYxOTAxMDFaFw0yMTAyMjUxOTAxMDFaMIGAMQswCQYDVQQGEwJVUzELMAkGA1UECBMCTUExDzANBgNVBAcTBkJvc3RvbjEXMBUGA1UEChMOQ2l0eSBPZiBCb3N0b24xEDAOBgNVBAsTB0lBTSBTU08xKDAmBgNVBAMTH3pkcGluZ2ZlZDAxLmNpdHloYWxsLmJvc3Rvbi5jb2IwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQChXwANkULq6nkAZNHNpXmEMbboxJ6ihFlXcmVoQEx3HwBqdfE6Fk5oRu744aDApbdUc0J4WK/Cv4xcjw2WRG1b7TUjHFzfQMbziuNlik/o65zDjEo0WKfJLKZ/P6CweWtSREMLwqbjkt+RbOBdFcu79FcwPef5jE6fpddipKxOWZq+QZ05Kesap/v7OPc/pKAAelbeIZa3gtJesOVzLHXcd3O1620hy3lVYCZjJgVroRVhOxO6S9w9Nylo2Tt1xVipwr3CCDNv97qp5KyFczrQ1UYlJu/iqTcP6YJtQvGZNDtS5ZHul+8JdDU4osrsYHE3Gkpt9jPD9Kxhcg5brdL3AgMBAAGjggM7MIIDNzAdBgNVHQ4EFgQU2ayFg7boLF++hn+cIDBP8AuBYL4wHwYDVR0jBBgwFoAUDIanuimnqw/y+njgpCNP4oCVxbYwggErBgNVHR8EggEiMIIBHjCCARqgggEWoIIBEoaBymxkYXA6Ly8vQ049Q2l0eU9mQm9zdG9uLUVudGVycHJpc2UtU3ViLUNBLENOPVpQQ09CU0NBQVBQMDEsQ049Q0RQLENOPVB1YmxpYyUyMEtleSUyMFNlcnZpY2VzLENOPVNlcnZpY2VzLENOPUNvbmZpZ3VyYXRpb24sREM9Ym9zdG9uLERDPWNvYj9jZXJ0aWZpY2F0ZVJldm9jYXRpb25MaXN0P2Jhc2U/b2JqZWN0Q2xhc3M9Y1JMRGlzdHJpYnV0aW9uUG9pbnSGQ2h0dHA6Ly9jcmwuYm9zdG9uLmNvYi9DZXJ0RW5yb2xsL0NpdHlPZkJvc3Rvbi1FbnRlcnByaXNlLVN1Yi1DQS5jcmwwggFEBggrBgEFBQcBAQSCATYwggEyMIG8BggrBgEFBQcwAoaBr2xkYXA6Ly8vQ049Q2l0eU9mQm9zdG9uLUVudGVycHJpc2UtU3ViLUNBLENOPUFJQSxDTj1QdWJsaWMlMjBLZXklMjBTZXJ2aWNlcyxDTj1TZXJ2aWNlcyxDTj1Db25maWd1cmF0aW9uLERDPWJvc3RvbixEQz1jb2I/Y0FDZXJ0aWZpY2F0ZT9iYXNlP29iamVjdENsYXNzPWNlcnRpZmljYXRpb25BdXRob3JpdHkwcQYIKwYBBQUHMAKGZWh0dHA6Ly9haWEuYm9zdG9uLmNvYi9DZXJ0RW5yb2xsL1pQQ09CU0NBQVBQMDEuY2l0eWhhbGwuYm9zdG9uLmNvYl9DaXR5T2ZCb3N0b24tRW50ZXJwcmlzZS1TdWItQ0EuY3J0MA4GA1UdDwEB/wQEAwIFoDA8BgkrBgEEAYI3FQcELzAtBiUrBgEEAYI3FQiG54BehtyeT4eZmyaF9OcShcyXUF+L62mH7PtvAgFkAgEKMBMGA1UdJQQMMAoGCCsGAQUFBwMBMBsGCSsGAQQBgjcVCgQOMAwwCgYIKwYBBQUHAwEwDQYJKoZIhvcNAQELBQADggIBAAwojfsctWBq3KeYXWzlMtIzB6c0xLOFvw/hcsKT+n7oQG1RbN4For/SJtbtdE7cswNjOc7ZKI9KiRzSJSBPtVmiRfyaCMeNLbkXqrYJ8/Ke/APOuVJhBtbm5WleNUovr0BscVC/5bOv8PNlmenS9DPjucvHoghX++91+od7zca2kzavvP5TZhmc0+ls//dFwjPrKQudb2Bo3y93z6sUh+zaadvMv3jwKMyLWY2oEmaUIjMRyuPMKKbOFb0U+rIfB49vsAc3HqVMFf5mx0O7fVot91V7zfp1hNGm7CXMyKuMh8MRuwHxPPL0GmRD0PRNs2KCWSd1fnkFM5u8gDIQyGO/j88Epc+zWMiWYyQVc+7qvCVuDZh2cWaeEiNeOrOiPbkrtftMf1IxO091IwqGKEfEo8cN+d4EDxMMR8hUcKnZ+wXoKwfzXaXX++7ARwBeEtVASqNw5r7ncY/3LEZPwDYyDPIMubjZtpZeSzL71G0AWKGXUcAU0TReZyLGGWTdojoiuIsVifaMZNpMVEFQ5+xWw49XmXIBGuZYyWLf1SO87baq8J8/w5EnMnJcbsB+oWtzUvu+td6UvRup48VRBabtr9FM3QtI4GkjwVoZWihHMYK92iThcZShm5mBSLvSDsoKSRYU/zOKy4ZQ473zGDDp3AsBC5tsnNmqYnaSDXIN ------END CERTIFICATE-----", - ], - "shared_options": Object { - "sign_get_request": false, - }, - "sso_login_url": "https://sso-dev.boston.gov/idp/SSO.saml2", - "sso_logout_url": "https://sso-dev.boston.gov/idp/SSO.saml2", -} -`; diff --git a/services-js/access-boston/src/server/start.ts b/services-js/access-boston/src/server/start.ts deleted file mode 100644 index ce746b6ea..000000000 --- a/services-js/access-boston/src/server/start.ts +++ /dev/null @@ -1,35 +0,0 @@ -/* eslint no-console: 0 */ - -/** - * @file Entrypoint for our server. Uses `require` so we can control import - * order and set up error reporting before getting the main server file going. - */ - -// We don't want any local configurations to affect the test runs -if ( - process.env.NODE_ENV !== 'test' && - process.env.NODE_ENV !== ('testcafe' as any) -) { - require('dotenv').config(); -} - -const Rollbar = require('rollbar'); -const rollbar = new Rollbar({ - accessToken: process.env.ROLLBAR_ACCESS_TOKEN, - captureUncaught: true, - captureUnhandledRejections: true, - scrubFields: ['currentPassword', 'newPassword', 'confirmPassword'], - payload: { - environment: process.env.ROLLBAR_ENVIRONMENT || process.env.NODE_ENV, - }, -}); - -const startServer = require('./access-boston').default; - -startServer(rollbar).catch((err: Error) => { - console.error('Error starting server', err); - process.exit(1); -}); -console.log('GROUP_MANAGEMENT_API_URL: ', process.env.GROUP_MANAGEMENT_API_URL); - -export {}; diff --git a/services-js/access-boston/src/stories/ChangePasswordPage.stories.tsx b/services-js/access-boston/src/stories/ChangePasswordPage.stories.tsx deleted file mode 100644 index da4b565c8..000000000 --- a/services-js/access-boston/src/stories/ChangePasswordPage.stories.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import React from 'react'; -import { storiesOf } from '@storybook/react'; - -import ChangePasswordPage from '../pages/change-password'; -import { Account } from '../client/graphql/fetch-account'; - -const ACCOUNT: Account = { - employeeId: 'CON01234', - firstName: 'Jyn', - lastName: 'Doe', - needsMfaDevice: false, - needsNewPassword: false, - hasMfaDevice: true, - resetPasswordToken: '', - mfaRequiredDate: null, - groups: [''], - email: 'jondoe@boston.gov', -}; - -storiesOf('ChangePasswordPage', module) - .add('default', () => ( - - )) - .add('first time registration', () => ( - - )) - .add('first time registration w/ temp password', () => ( - - )) - - .add('submitting', () => ( - - )) - .add('network error', () => ( - - )) - .add('session error', () => ( - - )); diff --git a/services-js/access-boston/src/stories/ForgotPasswordPage.stories.tsx b/services-js/access-boston/src/stories/ForgotPasswordPage.stories.tsx deleted file mode 100644 index 7485b5e6c..000000000 --- a/services-js/access-boston/src/stories/ForgotPasswordPage.stories.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import React from 'react'; -import { storiesOf } from '@storybook/react'; - -import ForgotPasswordPage from '../pages/forgot'; -import { Account } from '../client/graphql/fetch-account'; - -const ACCOUNT: Account = { - employeeId: 'CON01234', - firstName: 'Jyn', - lastName: 'Doe', - needsMfaDevice: false, - needsNewPassword: false, - hasMfaDevice: true, - resetPasswordToken: 'jfqWE7DExC4nUa7pvkABezkM4oNT', - mfaRequiredDate: null, - groups: [''], - email: 'jondoe@boston.gov', -}; - -storiesOf('ForgotPasswordPage', module) - .add('default', () => ( - - )) - .add('success', () => ( - - )) - .add('network error', () => ( - - )) - .add('session error', () => ( - - )); diff --git a/services-js/access-boston/src/stories/GroupManagementPage.stories.tsx b/services-js/access-boston/src/stories/GroupManagementPage.stories.tsx deleted file mode 100644 index d2c78c4a6..000000000 --- a/services-js/access-boston/src/stories/GroupManagementPage.stories.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import React from 'react'; -import { storiesOf } from '@storybook/react'; - -import GroupManagement from '../pages/group-management'; - -import { Account } from '../client/graphql/fetch-account'; - -const ACCOUNT: Account = { - employeeId: 'CON01234', - firstName: 'Jyn', - lastName: 'Doe', - needsMfaDevice: false, - needsNewPassword: false, - hasMfaDevice: true, - resetPasswordToken: '', - mfaRequiredDate: '2019-03-19T15:49:37.758Z', - groups: [''], - email: 'jondoe@boston.gov', -}; - -storiesOf('GroupManagementPage', module).add('default', () => ( - -)); -// .add('view group', () => ) -// .add('view member', () => ); diff --git a/services-js/access-boston/src/stories/IndexPage.stories.tsx b/services-js/access-boston/src/stories/IndexPage.stories.tsx deleted file mode 100644 index c6e855630..000000000 --- a/services-js/access-boston/src/stories/IndexPage.stories.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import React from 'react'; -import { storiesOf } from '@storybook/react'; - -import IndexPage, { FlashMessage } from '../pages/index'; -import { Account, Apps } from '../client/graphql/fetch-account-and-apps'; -import { makeAppsRegistry } from '../lib/AppsRegistry'; - -// @ts-ignore -import APPS_YAML from '../../fixtures/apps.yaml'; -import { NoticeClass } from '../lib/AppsRegistry'; - -const ACCOUNT: Account = { - employeeId: 'CON01234', - firstName: 'Jyn', - lastName: 'Doe', - needsMfaDevice: false, - needsNewPassword: false, - hasMfaDevice: true, - resetPasswordToken: '', - mfaRequiredDate: '2019-03-19T15:49:37.758Z', - groups: [''], - email: 'jondoe@boston.gov', -}; - -const appsRegistry = makeAppsRegistry(APPS_YAML, true); - -const APPS: Apps = { - categories: appsRegistry.appsForGroups([], true, 'CH').map(cat => ({ - title: cat.title, - showIcons: cat.icons, - requestAccessUrl: cat.showRequestAccessLink ? '#' : null, - apps: cat.apps, - })), -}; - -storiesOf('IndexPage', module) - .add('default', () => ( - - )) - .add('change password success', () => ( - - )) - .add('hasn’t registered MFA', () => ( - - )); diff --git a/services-js/access-boston/src/stories/README.md b/services-js/access-boston/src/stories/README.md deleted file mode 100644 index ece0e3ac7..000000000 --- a/services-js/access-boston/src/stories/README.md +++ /dev/null @@ -1,2 +0,0 @@ -Use this directory for stories that can’t go next to their components for -whatever reason.. diff --git a/services-js/access-boston/src/stories/RegisterMfaPage.stories.tsx b/services-js/access-boston/src/stories/RegisterMfaPage.stories.tsx deleted file mode 100644 index 8468d25fb..000000000 --- a/services-js/access-boston/src/stories/RegisterMfaPage.stories.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import React from 'react'; -import { storiesOf } from '@storybook/react'; - -import RegisterMfaPage from '../pages/mfa'; -import { Account } from '../client/graphql/fetch-account'; - -const ACCOUNT: Account = { - employeeId: 'CON01234', - firstName: 'Jyn', - lastName: 'Doe', - needsMfaDevice: false, - needsNewPassword: false, - hasMfaDevice: false, - resetPasswordToken: '', - mfaRequiredDate: null, - groups: [''], - email: 'jondoe@boston.gov', -}; - -storiesOf('RegisterMfaPage', module) - .add('default', () => ( - - )) - .add('email', () => ( - - )) - .add('modal open', () => ( - - )); diff --git a/services-js/access-boston/src/stories/RegisterPage.stories.tsx b/services-js/access-boston/src/stories/RegisterPage.stories.tsx deleted file mode 100644 index 726802062..000000000 --- a/services-js/access-boston/src/stories/RegisterPage.stories.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import React from 'react'; -import { storiesOf } from '@storybook/react'; - -import RegisterPage from '../pages/register'; - -storiesOf('RegisterPage', module) - .add('default', () => ( - - )) - .add('password already ok', () => ( - - )); diff --git a/services-js/access-boston/src/stories/RegistrationDonePage.stories.tsx b/services-js/access-boston/src/stories/RegistrationDonePage.stories.tsx deleted file mode 100644 index 8397228d5..000000000 --- a/services-js/access-boston/src/stories/RegistrationDonePage.stories.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import React from 'react'; -import { storiesOf } from '@storybook/react'; - -import DonePage from '../pages/done'; - -storiesOf('DonePage', module).add('default', () => ); diff --git a/services-js/access-boston/src/stories/Storyshots.test.ts b/services-js/access-boston/src/stories/Storyshots.test.ts deleted file mode 100644 index 48179bc20..000000000 --- a/services-js/access-boston/src/stories/Storyshots.test.ts +++ /dev/null @@ -1,6 +0,0 @@ -import path from 'path'; -import initStoryshots from '@storybook/addon-storyshots'; - -require('babel-plugin-require-context-hook/register')(); - -initStoryshots({ configPath: path.resolve(__dirname, '../../.storybook') }); diff --git a/services-js/access-boston/src/stories/__snapshots__/Storyshots.test.ts.snap b/services-js/access-boston/src/stories/__snapshots__/Storyshots.test.ts.snap deleted file mode 100644 index 001b44639..000000000 --- a/services-js/access-boston/src/stories/__snapshots__/Storyshots.test.ts.snap +++ /dev/null @@ -1,13251 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Storyshots AccessBostonFooter default 1`] = ` -
    -
    -
    -
    -
    - The Access Boston Portal, and the systems, data and other resources that require Access Boston authentication for access are only to be used for legitimate City of Boston purposes. Use may be monitored, and unauthorized access or improper use of the resources may be subject to civil and/or criminal penalties under applicable federal, state and/or local law. -
    -
    - -
    -
    -
    -`; - -exports[`Storyshots AccessBostonHeader default 1`] = ` -
    -

    - - Access Boston - -

    -
    - - Jyn Doe - -
    - - -
    -
    -
    -`; - -exports[`Storyshots ChangePasswordPage default 1`] = ` -
    -
    -

    - - Access Boston - -

    -
    - - Jyn Doe - -
    - - -
    -
    -
    -
    -
    -
    -

    - Change Password -

    -
    -
    - -
    -
    -
    - New passwords must: -
    -
      -
    • - Be at least 10 characters long -
    • -
    • - Use at least 3 of these: -
        -
      • - A lowercase letter -
      • -
      • - An uppercase letter -
      • -
      • - A number -
      • -
      • - A special character -
      • -
      -
    • -
    • - Not have spaces -
    • -
    • - Not be longer than 32 characters -
    • -
    -
    - Don't use personal info, like your name, ID or address. If you use just two consecutive characters from your name or ID in your password, it will fail. Your new password will have to be different than your last 5 passwords. -
    -
    -
    -
    - - -
    -   -
    -
    -
    - - -
    -   -
    -
    -
    - - -
    -   -
    -
    -
    - -
    - -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    -
    -
    - The Access Boston Portal, and the systems, data and other resources that require Access Boston authentication for access are only to be used for legitimate City of Boston purposes. Use may be monitored, and unauthorized access or improper use of the resources may be subject to civil and/or criminal penalties under applicable federal, state and/or local law. -
    -
    - -
    -
    -
    -
    -`; - -exports[`Storyshots ChangePasswordPage first time registration 1`] = ` -
    -
    -

    - - Access Boston - -

    -
    - - Jyn Doe - -
    - - -
    -
    -
    -
    -
    -
    -

    - Create a New Password -

    -
    -

    - You’ll need a new password for Access Boston. We’ve changed the requirements for passwords to make sure that they’re strong enough. -

    -

    - You’ll use this password when logging in to Access Boston websites like The Hub. If you work in City Hall or for BPS you’ll also use it for your desktop computer. -

    -
    -
    - -
    -
    -
    - New passwords must: -
    -
      -
    • - Be at least 10 characters long -
    • -
    • - Use at least 3 of these: -
        -
      • - A lowercase letter -
      • -
      • - An uppercase letter -
      • -
      • - A number -
      • -
      • - A special character -
      • -
      -
    • -
    • - Not have spaces -
    • -
    • - Not be longer than 32 characters -
    • -
    -
    - Don't use personal info, like your name, ID or address. If you use just two consecutive characters from your name or ID in your password, it will fail. Your new password will have to be different than your last 5 passwords. -
    -
    -
    -
    - - -
    -   -
    -
    -
    - - -
    -   -
    -
    -
    - - -
    -   -
    -
    -
    - -
    -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    -
    -
    - The Access Boston Portal, and the systems, data and other resources that require Access Boston authentication for access are only to be used for legitimate City of Boston purposes. Use may be monitored, and unauthorized access or improper use of the resources may be subject to civil and/or criminal penalties under applicable federal, state and/or local law. -
    -
    - -
    -
    -
    -
    -`; - -exports[`Storyshots ChangePasswordPage first time registration w/ temp password 1`] = ` -
    -
    -

    - - Access Boston - -

    -
    - - Jyn Doe - -
    - - -
    -
    -
    -
    -
    -
    -

    - Create a New Password -

    -
    -

    - You’ll need a new password for Access Boston. We’ve changed the requirements for passwords to make sure that they’re strong enough. -

    -

    - You’ll use this password when logging in to Access Boston websites like The Hub. If you work in City Hall or for BPS you’ll also use it for your desktop computer. -

    -

    - Please look for an email from us with your temporary password. -

    -
    -
    - -
    -
    -
    - New passwords must: -
    -
      -
    • - Be at least 10 characters long -
    • -
    • - Use at least 3 of these: -
        -
      • - A lowercase letter -
      • -
      • - An uppercase letter -
      • -
      • - A number -
      • -
      • - A special character -
      • -
      -
    • -
    • - Not have spaces -
    • -
    • - Not be longer than 32 characters -
    • -
    -
    - Don't use personal info, like your name, ID or address. If you use just two consecutive characters from your name or ID in your password, it will fail. Your new password will have to be different than your last 5 passwords. -
    -
    -
    -
    - - -
    -   -
    -
    -
    - - -
    -   -
    -
    -
    - - -
    -   -
    -
    -
    - -
    -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    -
    -
    - The Access Boston Portal, and the systems, data and other resources that require Access Boston authentication for access are only to be used for legitimate City of Boston purposes. Use may be monitored, and unauthorized access or improper use of the resources may be subject to civil and/or criminal penalties under applicable federal, state and/or local law. -
    -
    - -
    -
    -
    -
    -`; - -exports[`Storyshots ChangePasswordPage network error 1`] = ` -
    -
    -

    - - Access Boston - -

    -
    - - Jyn Doe - -
    - - -
    -
    -
    -
    -
    -
    -

    - Change Password -

    -
    -
    - -
    -
    -
    - New passwords must: -
    -
      -
    • - Be at least 10 characters long -
    • -
    • - Use at least 3 of these: -
        -
      • - A lowercase letter -
      • -
      • - An uppercase letter -
      • -
      • - A number -
      • -
      • - A special character -
      • -
      -
    • -
    • - Not have spaces -
    • -
    • - Not be longer than 32 characters -
    • -
    -
    - Don't use personal info, like your name, ID or address. If you use just two consecutive characters from your name or ID in your password, it will fail. Your new password will have to be different than your last 5 passwords. -
    -
    -
    -
    - - -
    -   -
    -
    -
    - - -
    -   -
    -
    -
    - - -
    -   -
    -
    -
    - -
    - -
    -
    -
    -
    -
    -
    -
    -
    - There was a network problem -
    -
    - Your password was probably not changed. If this keeps happening, please get in touch: -
    - -
    - -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    -
    -
    - The Access Boston Portal, and the systems, data and other resources that require Access Boston authentication for access are only to be used for legitimate City of Boston purposes. Use may be monitored, and unauthorized access or improper use of the resources may be subject to civil and/or criminal penalties under applicable federal, state and/or local law. -
    -
    - -
    -
    -
    -
    -`; - -exports[`Storyshots ChangePasswordPage session error 1`] = ` -
    -
    -

    - - Access Boston - -

    -
    - - Jyn Doe - -
    - - -
    -
    -
    -
    -
    -
    -

    - Change Password -

    -
    -
    - -
    -
    -
    - New passwords must: -
    -
      -
    • - Be at least 10 characters long -
    • -
    • - Use at least 3 of these: -
        -
      • - A lowercase letter -
      • -
      • - An uppercase letter -
      • -
      • - A number -
      • -
      • - A special character -
      • -
      -
    • -
    • - Not have spaces -
    • -
    • - Not be longer than 32 characters -
    • -
    -
    - Don't use personal info, like your name, ID or address. If you use just two consecutive characters from your name or ID in your password, it will fail. Your new password will have to be different than your last 5 passwords. -
    -
    -
    -
    - - -
    -   -
    -
    -
    - - -
    -   -
    -
    -
    - - -
    -   -
    -
    -
    - -
    - -
    -
    -
    -
    -
    -
    -
    -
    - Your session has expired -
    -
    - Your password was not changed. You will need to log in again to change your password. -
    -
    - -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    -
    -
    - The Access Boston Portal, and the systems, data and other resources that require Access Boston authentication for access are only to be used for legitimate City of Boston purposes. Use may be monitored, and unauthorized access or improper use of the resources may be subject to civil and/or criminal penalties under applicable federal, state and/or local law. -
    -
    - -
    -
    -
    -
    -`; - -exports[`Storyshots ChangePasswordPage submitting 1`] = ` -
    -
    -

    - - Access Boston - -

    -
    - - Jyn Doe - -
    - - -
    -
    -
    -
    -
    -
    -

    - Change Password -

    -
    -
    - -
    -
    -
    - New passwords must: -
    -
      -
    • - Be at least 10 characters long -
    • -
    • - Use at least 3 of these: -
        -
      • - A lowercase letter -
      • -
      • - An uppercase letter -
      • -
      • - A number -
      • -
      • - A special character -
      • -
      -
    • -
    • - Not have spaces -
    • -
    • - Not be longer than 32 characters -
    • -
    -
    - Don't use personal info, like your name, ID or address. If you use just two consecutive characters from your name or ID in your password, it will fail. Your new password will have to be different than your last 5 passwords. -
    -
    -
    -
    - - -
    -   -
    -
    -
    - - -
    -   -
    -
    -
    - - -
    -   -
    -
    -
    - -
    - -
    -
    -
    -
    -
    -
    -
    -
    - Saving your new password… -
    -
    - Please be patient and don’t refresh your browser. This might take a bit. -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    -
    -
    - The Access Boston Portal, and the systems, data and other resources that require Access Boston authentication for access are only to be used for legitimate City of Boston purposes. Use may be monitored, and unauthorized access or improper use of the resources may be subject to civil and/or criminal penalties under applicable federal, state and/or local law. -
    -
    - -
    -
    -
    -
    -`; - -exports[`Storyshots Common/AppWrapper default 1`] = ` -
    -
    -

    - - Access Boston - -

    -
    - - Jyn Doe - -
    - - -
    -
    -
    -
    - hi -
    -
    -
    -
    -
    -
    - The Access Boston Portal, and the systems, data and other resources that require Access Boston authentication for access are only to be used for legitimate City of Boston purposes. Use may be monitored, and unauthorized access or improper use of the resources may be subject to civil and/or criminal penalties under applicable federal, state and/or local law. -
    -
    - -
    -
    -
    -
    -`; - -exports[`Storyshots Confirm-Identity EnterID 1`] = ` -
    -
    -
    - - Step 1 of 4 - - -
    -
    -
    -
    -
    -
    -
    -
    -

    - Confirm ID -

    -
    -
    - Instructions -
    -
    - Please enter the Employee ID or User ID number of the person to be verified. -
    -
    -
    - - -
    -   -
    -
    -
    -
    -
    -
    -
    -
    - -
    -
    - -
    -
    -
    -
    -
    -`; - -exports[`Storyshots Confirm-Identity Failure 1`] = ` -
    -
    -
    -
    -
    -
    -

    - Confirm ID -

    -
    -

    - The identity verification process was unsuccessful. -

    -

    - If the person is an - - Employee - - , please ask them to reach out to their Human Resources representative to confirm that their data is correct. -

    -

    - If the person has a - - Sponsored - - account, please ask them to reach out to their Sponsor to confirm that their data is correct. -

    -
    -
    -
    -
    -
    -
    - -
    -
    -
    -
    -`; - -exports[`Storyshots Confirm-Identity Quit 1`] = ` -
    -
    -
    -
    -
    -
    - Access Boston -
    -
    -

    - Quit Confirm ID -

    -
    -

    - Are you sure you want to quit the Identify Verification Process? -

    -

    - All unsaved changes will be lost and the process will have to be restarted. -

    -
    -
    -
    -
    -
    - -
    -
    - -
    -
    -
    -
    -`; - -exports[`Storyshots Confirm-Identity Review 1`] = ` -
    -
    -
    - - Step 3 of 4 - - -
    -
    -
    -
    -
    -
    -
    -
    -

    - Confirm ID -

    -
    -
    - Review Details -
    -
    - Please review the details below with the person to confirm. -
    -
    -
    - - -
    -   -
    -
    -
    - - -
    -   -
    -
    -
    - - -
    -   -
    -
    -
    -
    - - Data of Birth (MM/DD/YYYY) - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - -
    -
    - -
    -
    - -
    -
    -
    -
    -
    -`; - -exports[`Storyshots Confirm-Identity Success 1`] = ` -
    -
    -
    - - Step 4 of 4 - - -
    -
    -
    -
    -
    -
    -
    -
    -

    - Confirm ID -

    -
    -

    - The identity verification process was successful. -

    -

    - Please proceed with password reset activities. -

    -
    -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    -`; - -exports[`Storyshots Confirm-Identity Validate 1`] = ` -
    -
    -
    - - Step 2 of 4 - - -
    -
    -
    -
    -
    -
    -
    -
    -

    - Confirm ID -

    -
    -
    - Validate -
    -
    - Confirm the name displayed matches the one the caller is providing, then enter their Date of Birth. -
    -
    -
    - - -
    -   -
    -
    -
    - - -
    -   -
    -
    -
    - - -
    -   -
    -
    -
    -
    - - Data of Birth (MM/DD/YYYY) - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - -
    -
    - -
    -
    - -
    -
    -
    -
    -
    -`; - -exports[`Storyshots DonePage default 1`] = ` -
    -
    -

    - - Access Boston - -

    -
    -
    -
    -
    -

    - You’re all set! -

    -
    -
    - You’re now set up with your Access Boston account. Log in now to continue. -
    - -
    -
    -
    -
    -
    -
    -
    - The Access Boston Portal, and the systems, data and other resources that require Access Boston authentication for access are only to be used for legitimate City of Boston purposes. Use may be monitored, and unauthorized access or improper use of the resources may be subject to civil and/or criminal penalties under applicable federal, state and/or local law. -
    -
    - -
    -
    -
    -
    -`; - -exports[`Storyshots ForgotPasswordPage default 1`] = ` -
    -
    -

    - Access Boston -

    -
    -
    -
    -
    -

    - Forgot Password -

    -
    -
    - -
    -
    -
    - New passwords must: -
    -
      -
    • - Be at least 10 characters long -
    • -
    • - Use at least 3 of these: -
        -
      • - A lowercase letter -
      • -
      • - An uppercase letter -
      • -
      • - A number -
      • -
      • - A special character -
      • -
      -
    • -
    • - Not have spaces -
    • -
    • - Not be longer than 32 characters -
    • -
    -
    - Don't use personal info, like your name, ID or address. If you use just two consecutive characters from your name or ID in your password, it will fail. Your new password will have to be different than your last 5 passwords. -
    -
    -
    -
    - - -
    -   -
    -
    -
    - - -
    -   -
    -
    -
    - -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - The Access Boston Portal, and the systems, data and other resources that require Access Boston authentication for access are only to be used for legitimate City of Boston purposes. Use may be monitored, and unauthorized access or improper use of the resources may be subject to civil and/or criminal penalties under applicable federal, state and/or local law. -
    -
    - -
    -
    -
    -
    -`; - -exports[`Storyshots ForgotPasswordPage network error 1`] = ` -Array [ -
    -
    -

    - Access Boston -

    -
    -
    -
    -
    -

    - Forgot Password -

    -
    -
    - -
    -
    -
    - New passwords must: -
    -
      -
    • - Be at least 10 characters long -
    • -
    • - Use at least 3 of these: -
        -
      • - A lowercase letter -
      • -
      • - An uppercase letter -
      • -
      • - A number -
      • -
      • - A special character -
      • -
      -
    • -
    • - Not have spaces -
    • -
    • - Not be longer than 32 characters -
    • -
    -
    - Don't use personal info, like your name, ID or address. If you use just two consecutive characters from your name or ID in your password, it will fail. Your new password will have to be different than your last 5 passwords. -
    -
    -
    -
    - - -
    -   -
    -
    -
    - - -
    -   -
    -
    -
    - -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - The Access Boston Portal, and the systems, data and other resources that require Access Boston authentication for access are only to be used for legitimate City of Boston purposes. Use may be monitored, and unauthorized access or improper use of the resources may be subject to civil and/or criminal penalties under applicable federal, state and/or local law. -
    -
    - -
    -
    -
    -
    , -
    -
    -
    -
    - There might have been a network problem -
    -
    - Please try the new password, it should still work. If it doesn’t, please get in touch -
    - -
    -
    -
    , -] -`; - -exports[`Storyshots ForgotPasswordPage session error 1`] = ` -Array [ -
    -
    -

    - Access Boston -

    -
    -
    -
    -
    -

    - Forgot Password -

    -
    -
    - -
    -
    -
    - New passwords must: -
    -
      -
    • - Be at least 10 characters long -
    • -
    • - Use at least 3 of these: -
        -
      • - A lowercase letter -
      • -
      • - An uppercase letter -
      • -
      • - A number -
      • -
      • - A special character -
      • -
      -
    • -
    • - Not have spaces -
    • -
    • - Not be longer than 32 characters -
    • -
    -
    - Don't use personal info, like your name, ID or address. If you use just two consecutive characters from your name or ID in your password, it will fail. Your new password will have to be different than your last 5 passwords. -
    -
    -
    -
    - - -
    -   -
    -
    -
    - - -
    -   -
    -
    -
    - -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - The Access Boston Portal, and the systems, data and other resources that require Access Boston authentication for access are only to be used for legitimate City of Boston purposes. Use may be monitored, and unauthorized access or improper use of the resources may be subject to civil and/or criminal penalties under applicable federal, state and/or local law. -
    -
    - -
    -
    -
    -
    , -
    -
    -
    -
    - Your session has expired -
    -
    - Your password was not changed. You will need to log in again to reset your password. -
    -
    - -
    -
    -
    -
    , -] -`; - -exports[`Storyshots ForgotPasswordPage success 1`] = ` -
    -
    -

    - Access Boston -

    -
    -
    -
    -
    -

    - Reset successful! -

    -
    -
    - Your password change has gone through. Log in now with your new password. -
    - -
    -
    -
    -
    -
    -
    -
    - The Access Boston Portal, and the systems, data and other resources that require Access Boston authentication for access are only to be used for legitimate City of Boston purposes. Use may be monitored, and unauthorized access or improper use of the resources may be subject to civil and/or criminal penalties under applicable federal, state and/or local law. -
    -
    - -
    -
    -
    -
    -`; - -exports[`Storyshots GroupManagementPage default 1`] = ` -
    -
    -

    - - Access Boston - -

    -
    - - Jyn Doe - -
    - - -
    -
    -
    -
    -
    -
    -
    -
    -

    - Do you want... -

    -
    -
    - - -
    -
    -
    -
    -
    -
    -

    - Group search -

    -
    -
    -
    - -
    -
    -
    - -
    -
    -
    - -
    -
    - -
    -
    -
    -
    -
    -
    -
    -
    -
    - The Access Boston Portal, and the systems, data and other resources that require Access Boston authentication for access are only to be used for legitimate City of Boston purposes. Use may be monitored, and unauthorized access or improper use of the resources may be subject to civil and/or criminal penalties under applicable federal, state and/or local law. -
    -
    - -
    -
    -
    -
    -`; - -exports[`Storyshots GroupManagementPage/Index default 1`] = ` -
    -
    -
    -
    -

    - Do you want... -

    -
    -
    - - -
    -
    -
    -
    -
    -
    -

    - Group search -

    -
    -
    -
    - -
    -
    -
    - -
    -
    -
    - -
    -
    - -
    -
    -
    -`; - -exports[`Storyshots GroupManagementPage/InitialView default 1`] = ` -Array [ -
    -
    -
    -

    - Do you want... -

    -
    -
    - - -
    -
    -
    , -
    -
    -
    -

    - Group search -

    -
    -
    -
    - -
    -
    -
    - -
    -
    -
    - -
    -
    - -
    -
    , -] -`; - -exports[`Storyshots GroupManagementPage/ListComponents/EditableList editable group view 1`] = ` -
      -
      - This group has no members -
      -
    -`; - -exports[`Storyshots GroupManagementPage/ListComponents/EditableList editable person view 1`] = ` -
      -
      - This person hasn’t been added to any groups -
      -
    -`; - -exports[`Storyshots GroupManagementPage/ListComponents/EditableList loading view 1`] = ` -
    - - - -
    -`; - -exports[`Storyshots GroupManagementPage/ListComponents/EditableList no results, group view 1`] = ` -
      -
      - This group has no members -
      -
    -`; - -exports[`Storyshots GroupManagementPage/ListComponents/EditableList no results, person view 1`] = ` -
      -
      - This person hasn’t been added to any groups -
      -
    -`; - -exports[`Storyshots GroupManagementPage/ListComponents/ListItemComponent default 1`] = ` -
  • - - -
  • -`; - -exports[`Storyshots GroupManagementPage/ListComponents/ListItemComponent newly added 1`] = ` -
  • - - -
  • -`; - -exports[`Storyshots GroupManagementPage/ListComponents/ListItemComponent no link 1`] = ` -
  • - - Bob Roberts - -
  • -`; - -exports[`Storyshots GroupManagementPage/ListComponents/ListItemComponent not modifiable 1`] = ` -
  • - - -
  • -`; - -exports[`Storyshots GroupManagementPage/ListComponents/ReviewList groups added 1`] = ` -Array [ -
    -

    - Groups added -

    -
    , -
      , -] -`; - -exports[`Storyshots GroupManagementPage/ListComponents/ReviewList members added 1`] = ` -Array [ -
      -

      - Members added -

      -
      , -
        , -] -`; - -exports[`Storyshots GroupManagementPage/ManagementView group view 1`] = ` -
        -
        -
        -
        - -
        -

        - BPD_Administrative - - group -

        -
        -
        -
        -
        -
        -
        -
        -

        - Add a new member -

        -
        -
        -
        - -
        -
        -
        - -
        -
        -
        - -
        -
        - -
        -
        -
        -
        -
        -

        - Current members -

        -
        -
          -
          - This group has no members -
          -
        -
        - -
        -
        -
        -
        -`; - -exports[`Storyshots GroupManagementPage/ManagementView loading view 1`] = ` -
        -
        -
        -
        - -
        -

        - BPD_Administrative - - group -

        -
        -
        -
        -
        -
        -
        -
        -

        - Add a new member -

        -
        -
        -
        - -
        -
        -
        - -
        -
        -
        - -
        -
        - -
        -
        -
        -
        -
        -

        - Current members -

        -
        -
        - - - -
        -
        - -
        -
        -
        -
        -`; - -exports[`Storyshots GroupManagementPage/ManagementView person view 1`] = ` -
        -
        -
        -
        - -
        -

        - Cloud Howell - -

        -
        - - cloud.howell@cityofboston.gov - -
        -
        -
        -
        -
        -
        -
        -
        -

        - Add to a group -

        -
        -
        -
        - -
        -
        -
        - -
        -
        -
        - -
        -
        - -
        -
        -
        -
        -
        -

        - Current groups -

        -
        -
          -
          - This person hasn’t been added to any groups -
          -
        -
        - -
        -
        -
        -
        -`; - -exports[`Storyshots GroupManagementPage/SearchComponent group add 1`] = ` -
        -
        -
        -

        - Add to a group -

        -
        -
        -
        - -
        -
        -
        - -
        -
        -
        - -
        -
        - -
        -
        -`; - -exports[`Storyshots GroupManagementPage/SearchComponent group search 1`] = ` -
        -
        -
        -

        - Group search -

        -
        -
        -
        - -
        -
        -
        - -
        -
        -
        - -
        -
        - -
        -
        -`; - -exports[`Storyshots GroupManagementPage/SearchComponent no results 1`] = ` -
        -
        -
        -

        - Group search -

        -
        -
        -
        - -
        -
        -
        - -
        -
        -
        - -
        -
        - -
        -
        -`; - -exports[`Storyshots GroupManagementPage/SearchComponent person add 1`] = ` -
        -
        -
        -

        - Add a new member -

        -
        -
        -
        - -
        -
        -
        - -
        -
        -
        - -
        -
        - -
        -
        -`; - -exports[`Storyshots GroupManagementPage/SearchComponent person search 1`] = ` -
        -
        -
        -

        - Person search -

        -
        -
        -
        - -
        -
        -
        - -
        -
        -
        - -
        -
        - -
        -
        -`; - -exports[`Storyshots GroupManagementPage/SearchComponent searching 1`] = ` -
        -
        -
        -

        - Group search -

        -
        -
        -
        - -
        -
        -
        - -
        -
        -
        - -
        -
        - -
        -
        -`; - -exports[`Storyshots GroupManagementPage/SearchComponent server error/could not reach server 1`] = ` -
        -
        -
        -

        - Group search -

        -
        -
        -
        - -
        -
        -
        - -
        -
        -
        - -
        -
        - -
        -
        -`; - -exports[`Storyshots GroupManagementPage/SelectedComponent groups view 1`] = ` -
        -
        -
        - -
        -

        - ANML02_LostFound - - group -

        -
        -
        -
        -
        -`; - -exports[`Storyshots GroupManagementPage/SelectedComponent person view 1`] = ` -
        -
        -
        - -
        -

        - Cloud Howell - -

        -
        - - cloud.howell@cityofboston.gov - -
        -
        -
        -
        -
        -`; - -exports[`Storyshots IndexPage change password success 1`] = ` -
        -
        -

        - - Access Boston - -

        -
        - - Jyn Doe - -
        - - -
        -
        -
        -
        -
        -
        -
        - Your password has been changed! -
        -
        -
        -
        -
        -
        -

        - Applications -

        -
        - -
        - -
        -
        -
        -
        -
        -
        -
        - The Access Boston Portal, and the systems, data and other resources that require Access Boston authentication for access are only to be used for legitimate City of Boston purposes. Use may be monitored, and unauthorized access or improper use of the resources may be subject to civil and/or criminal penalties under applicable federal, state and/or local law. -
        -
        - -
        -
        -
        -
        -`; - -exports[`Storyshots IndexPage default 1`] = ` -
        -
        -

        - - Access Boston - -

        -
        - - Jyn Doe - -
        - - -
        -
        -
        -
        -
        -
        -
        -

        - Applications -

        -
        - -
        - -
        -
        -
        -
        -
        -
        -
        - The Access Boston Portal, and the systems, data and other resources that require Access Boston authentication for access are only to be used for legitimate City of Boston purposes. Use may be monitored, and unauthorized access or improper use of the resources may be subject to civil and/or criminal penalties under applicable federal, state and/or local law. -
        -
        - -
        -
        -
        -
        -`; - -exports[`Storyshots IndexPage hasn’t registered MFA 1`] = ` -
        -
        -

        - - Access Boston - -

        -
        - - Jyn Doe - -
        - - -
        -
        -
        -
        -
        -
        -
        -
        -
        - Account notice -
        -
        - You have - - - 28 days - - - to complete your registration. -
        -
        - -
        -
        -
        -
        -
        -
        -

        - Applications -

        -
        - -
        - -
        -
        -
        -
        -
        -
        -
        - The Access Boston Portal, and the systems, data and other resources that require Access Boston authentication for access are only to be used for legitimate City of Boston purposes. Use may be monitored, and unauthorized access or improper use of the resources may be subject to civil and/or criminal penalties under applicable federal, state and/or local law. -
        -
        - -
        -
        -
        -
        -`; - -exports[`Storyshots PasswordPolicy blank 1`] = ` -Array [ -
        - New passwords must: -
        , -
          -
        • - Be at least 10 characters long -
        • -
        • - Use at least 3 of these: -
            -
          • - A lowercase letter -
          • -
          • - An uppercase letter -
          • -
          • - A number -
          • -
          • - A special character -
          • -
          -
        • -
        • - Not have spaces -
        • -
        • - Not be longer than 32 characters -
        • -
        , -
        - Don't use personal info, like your name, ID or address. If you use just two consecutive characters from your name or ID in your password, it will fail. Your new password will have to be different than your last 5 passwords. -
        , -] -`; - -exports[`Storyshots PasswordPolicy complex enough 1`] = ` -Array [ -
        - New passwords must: -
        , -
          -
        • - Be at least 10 characters long -
        • -
        • - Use at least 3 of these: -
            -
          • - A lowercase letter -
          • -
          • - An uppercase letter -
          • -
          • - A number -
          • -
          • - A special character -
          • -
          -
        • -
        • - Not have spaces -
        • -
        • - Not be longer than 32 characters -
        • -
        , -
        - Don't use personal info, like your name, ID or address. If you use just two consecutive characters from your name or ID in your password, it will fail. Your new password will have to be different than your last 5 passwords. -
        , -] -`; - -exports[`Storyshots PasswordPolicy failed are errors 1`] = ` -Array [ -
        - New passwords must: -
        , -
          -
        • - Be at least 10 characters long -
        • -
        • - Use at least 3 of these: -
            -
          • - A lowercase letter -
          • -
          • - An uppercase letter -
          • -
          • - A number -
          • -
          • - A special character -
          • -
          -
        • -
        • - Not have spaces -
        • -
        • - Not be longer than 32 characters -
        • -
        , -
        - Don't use personal info, like your name, ID or address. If you use just two consecutive characters from your name or ID in your password, it will fail. Your new password will have to be different than your last 5 passwords. -
        , -] -`; - -exports[`Storyshots PasswordPolicy long enough 1`] = ` -Array [ -
        - New passwords must: -
        , -
          -
        • - Be at least 10 characters long -
        • -
        • - Use at least 3 of these: -
            -
          • - A lowercase letter -
          • -
          • - An uppercase letter -
          • -
          • - A number -
          • -
          • - A special character -
          • -
          -
        • -
        • - Not have spaces -
        • -
        • - Not be longer than 32 characters -
        • -
        , -
        - Don't use personal info, like your name, ID or address. If you use just two consecutive characters from your name or ID in your password, it will fail. Your new password will have to be different than your last 5 passwords. -
        , -] -`; - -exports[`Storyshots PasswordPolicy spaces error 1`] = ` -Array [ -
        - New passwords must: -
        , -
          -
        • - Be at least 10 characters long -
        • -
        • - Use at least 3 of these: -
            -
          • - A lowercase letter -
          • -
          • - An uppercase letter -
          • -
          • - A number -
          • -
          • - A special character -
          • -
          -
        • -
        • - Not have spaces -
        • -
        • - Not be longer than 32 characters -
        • -
        , -
        - Don't use personal info, like your name, ID or address. If you use just two consecutive characters from your name or ID in your password, it will fail. Your new password will have to be different than your last 5 passwords. -
        , -] -`; - -exports[`Storyshots RegisterMfaPage default 1`] = ` -
        -
        -

        - - Access Boston - -

        -
        - - Jyn Doe - -
        - - -
        -
        -
        -
        -
        -
        -

        - Set up security codes -

        -
        -

        - Access Boston will send you a security code when you log in on a new computer, tablet, or phone. You’ll also need a code to reset your password if you forget it. -

        -

        - This is called multi-factor authentication. It keeps your account secure even if someone steals your password. -

        -

        - Use your cell phone number if you have one. This is the most secure option. If you don’t have a phone, you can use a personal email address instead. -

        -
        -
        -
        - -
        -
        - -
        -
        -
        -   -
        -
        -
        -
        - You should use your cell phone number if you have one. -
        - Note: normal cell phone charges will apply. -
        -
        -
        -
        - How should we send security codes? -
        - - -
        -
        - -
        -
        - Don’t have access to a phone? -
        - - Get codes via personal email - -
        -
        -
        -
        - -
        -
        -
        -
        -
        -
        -
        - The Access Boston Portal, and the systems, data and other resources that require Access Boston authentication for access are only to be used for legitimate City of Boston purposes. Use may be monitored, and unauthorized access or improper use of the resources may be subject to civil and/or criminal penalties under applicable federal, state and/or local law. -
        -
        - -
        -
        -
        -
        -`; - -exports[`Storyshots RegisterMfaPage email 1`] = ` -
        -
        -

        - - Access Boston - -

        -
        - - Jyn Doe - -
        - - -
        -
        -
        -
        -
        -
        -

        - Set up security codes -

        -
        -

        - Access Boston will send you a security code when you log in on a new computer, tablet, or phone. You’ll also need a code to reset your password if you forget it. -

        -

        - This is called multi-factor authentication. It keeps your account secure even if someone steals your password. -

        -

        - Use your cell phone number if you have one. This is the most secure option. If you don’t have a phone, you can use a personal email address instead. -

        -
        -
        -
        - -
        -
        - -
        -
        -
        -   -
        -
        -
        -
        - Choose a personal address, not a City of Boston one. -
        -
        - -
        - -
        -
        -
        -
        - -
        -
        -
        -
        -
        -
        -
        - The Access Boston Portal, and the systems, data and other resources that require Access Boston authentication for access are only to be used for legitimate City of Boston purposes. Use may be monitored, and unauthorized access or improper use of the resources may be subject to civil and/or criminal penalties under applicable federal, state and/or local law. -
        -
        - -
        -
        -
        -
        -`; - -exports[`Storyshots RegisterMfaPage modal open 1`] = ` -
        -
        -

        - - Access Boston - -

        -
        - - Jyn Doe - -
        - - -
        -
        -
        -
        -
        -
        -

        - Set up security codes -

        -
        -

        - Access Boston will send you a security code when you log in on a new computer, tablet, or phone. You’ll also need a code to reset your password if you forget it. -

        -

        - This is called multi-factor authentication. It keeps your account secure even if someone steals your password. -

        -

        - Use your cell phone number if you have one. This is the most secure option. If you don’t have a phone, you can use a personal email address instead. -

        -
        -
        -
        - -
        -
        - -
        -
        -
        -   -
        -
        -
        -
        - You should use your cell phone number if you have one. -
        - Note: normal cell phone charges will apply. -
        -
        -
        -
        - How should we send security codes? -
        - - -
        -
        - -
        -
        - Don’t have access to a phone? -
        - - Get codes via personal email - -
        -
        -
        -
        -
        -
        -
        - Check your phone! We’ve sent a text to - - - - - . -
        -
        -
        -
        - -
        - - -
        -
        -
        -
        - Didn’t get it? - - - - or - - - . -
        -
        -
        -
        -
        -
        - -
        -
        -
        -
        -
        -
        -
        - The Access Boston Portal, and the systems, data and other resources that require Access Boston authentication for access are only to be used for legitimate City of Boston purposes. Use may be monitored, and unauthorized access or improper use of the resources may be subject to civil and/or criminal penalties under applicable federal, state and/or local law. -
        -
        - -
        -
        -
        -
        -`; - -exports[`Storyshots RegisterMfaPage/DeviceVerificationForm code sending error 1`] = ` -
        -
        -

        - Set up security codes -

        -
        -

        - Access Boston will send you a security code when you log in on a new computer, tablet, or phone. You’ll also need a code to reset your password if you forget it. -

        -

        - This is called multi-factor authentication. It keeps your account secure even if someone steals your password. -

        -

        - Use your cell phone number if you have one. This is the most secure option. If you don’t have a phone, you can use a personal email address instead. -

        -
        -
        -
        - -
        -
        - -
        -
        -
        -   -
        -
        -
        -
        - You should use your cell phone number if you have one. -
        - Note: normal cell phone charges will apply. -
        -
        -
        -
        - How should we send security codes? -
        - - -
        -
        - We weren’t able to send to that address. Please try something else. -
        -
        - -
        -
        - Don’t have access to a phone? -
        - - Get codes via personal email - -
        -
        -
        -`; - -exports[`Storyshots RegisterMfaPage/DeviceVerificationForm email with error 1`] = ` -
        -
        -

        - Set up security codes -

        -
        -

        - Access Boston will send you a security code when you log in on a new computer, tablet, or phone. You’ll also need a code to reset your password if you forget it. -

        -

        - This is called multi-factor authentication. It keeps your account secure even if someone steals your password. -

        -

        - Use your cell phone number if you have one. This is the most secure option. If you don’t have a phone, you can use a personal email address instead. -

        -
        -
        -
        - -
        -
        - -
        -
        -
        - This doesn’t look like an email address. -
        -
        -
        -
        - Choose a personal address, not a City of Boston one. -
        -
        - -
        - -
        -
        -
        -`; - -exports[`Storyshots RegisterMfaPage/DeviceVerificationForm phone 1`] = ` -
        -
        -

        - Set up security codes -

        -
        -

        - Access Boston will send you a security code when you log in on a new computer, tablet, or phone. You’ll also need a code to reset your password if you forget it. -

        -

        - This is called multi-factor authentication. It keeps your account secure even if someone steals your password. -

        -

        - Use your cell phone number if you have one. This is the most secure option. If you don’t have a phone, you can use a personal email address instead. -

        -
        -
        -
        - -
        -
        - -
        -
        -
        -   -
        -
        -
        -
        - You should use your cell phone number if you have one. -
        - Note: normal cell phone charges will apply. -
        -
        -
        -
        - How should we send security codes? -
        - - -
        -
        - -
        -
        - Don’t have access to a phone? -
        - - Get codes via personal email - -
        -
        -
        -`; - -exports[`Storyshots RegisterMfaPage/DeviceVerificationModal checking the code 1`] = ` -
        -
        -
        -
        - Please pick up! We’re making a phone call to - - - (xxx) xxx-xx34 - - . -
        -
        -
        -
        - -
        - - -
        -
        -
        -
        - Didn’t get it? - - - - or - - - . -
        -
        -
        -
        -
        -`; - -exports[`Storyshots RegisterMfaPage/DeviceVerificationModal incorrect code 1`] = ` -
        -
        -
        -
        - - That code didn’t seem right. Can you try again? - -
        -
        -
        -
        - -
        - - -
        -
        -
        -
        - Didn’t get it? - - - - or - - - . -
        -
        -
        -
        -
        -`; - -exports[`Storyshots RegisterMfaPage/DeviceVerificationModal other error 1`] = ` -
        -
        -
        -
        - - Something went wrong. - -
        -
        - We had a problem verifying that code. You can try to - - - - or - - - . -
        -
        - Please get in touch with the helpdesk if this keeps happening. -
        -
        -
        -
        -`; - -exports[`Storyshots RegisterMfaPage/DeviceVerificationModal sending 1`] = ` -
        -
        -
        -
        - We’re sending you a security code… -
        -
        -
        -
        -`; - -exports[`Storyshots RegisterMfaPage/DeviceVerificationModal waiting for SMS code 1`] = ` -
        -
        -
        -
        - Check your phone! We’ve sent a text to - - - (xxx) xxx-xx34 - - . -
        -
        -
        -
        - -
        - - -
        -
        -
        -
        - Didn’t get it? - - - - or - - - . -
        -
        -
        -
        -
        -`; - -exports[`Storyshots RegisterMfaPage/DeviceVerificationModal waiting for email code 1`] = ` -
        -
        -
        -
        - Check your inbox! We’ve sent an email to - - test@boston.gov - - . -
        -
        -
        -
        - -
        - - -
        -
        -
        -
        - Didn’t get it? - - - - or - - - . -
        -
        -
        -
        -
        -`; - -exports[`Storyshots RegisterMfaPage/DeviceVerificationModal waiting for voice code 1`] = ` -
        -
        -
        -
        - Please pick up! We’re making a phone call to - - - (xxx) xxx-xx34 - - . -
        -
        -
        -
        - -
        - - -
        -
        -
        -
        - Didn’t get it? - - - - or - - - . -
        -
        -
        -
        -
        -`; - -exports[`Storyshots RegisterPage default 1`] = ` -
        -
        -

        - - Access Boston - -

        -
        - - Jyn Doe - -
        - - -
        -
        -
        -
        -
        -
        -

        - Welcome to Access Boston! -

        -
        -
        -
        -
        - Access Boston is the new place to log into your City of Boston employee account. -
        -

        - We’ve made a few changes to make things more secure: -

        -
          -
        • - Passwords need to be a bit longer and a bit stronger. But you only have to change them once a year! -
        • -
        • - We’ll need a mobile phone number or personal email address to send security codes to. You’ll need a security code when you use a new computer or forget your password. -
        • -
        -

        - We’ll walk you through everything you need to do to get set up. - -

        - -
        -
        -
        - If you need extra help, give us a call: -
        - -
        -
        - -
        -
        -
        -
        -
        -
        -
        - The Access Boston Portal, and the systems, data and other resources that require Access Boston authentication for access are only to be used for legitimate City of Boston purposes. Use may be monitored, and unauthorized access or improper use of the resources may be subject to civil and/or criminal penalties under applicable federal, state and/or local law. -
        -
        - -
        -
        -
        -
        -`; - -exports[`Storyshots RegisterPage password already ok 1`] = ` -
        -
        -

        - - Access Boston - -

        -
        - - Jyn Doe - -
        - - -
        -
        -
        -
        -
        -
        -

        - Welcome to Access Boston! -

        -
        -
        -
        -
        - Access Boston is the new place to log into your City of Boston employee account. -
        -

        - We’ve made a few changes to make things more secure: -

        -
          -
        • - Passwords need to be a bit longer and a bit stronger. But you only have to change them once a year! -
        • -
        • - We’ll need a mobile phone number or personal email address to send security codes to. You’ll need a security code when you use a new computer or forget your password. -
        • -
        -

        - We’ll walk you through everything you need to do to get set up. - - Your password is already strong enough, so we just need to set you up for security codes. -

        - -
        -
        -
        - If you need extra help, give us a call: -
        - -
        -
        - -
        -
        -
        -
        -
        -
        -
        - The Access Boston Portal, and the systems, data and other resources that require Access Boston authentication for access are only to be used for legitimate City of Boston purposes. Use may be monitored, and unauthorized access or improper use of the resources may be subject to civil and/or criminal penalties under applicable federal, state and/or local law. -
        -
        - -
        -
        -
        -
        -`; - -exports[`Storyshots TextInput variants 1`] = ` -Array [ -
        - - -
        -   -
        -
        , -
        - - -
        - This field is important to get right -
        -
        -   -
        -
        , -
        - - -
        -   -
        -
        , -
        - - -
        -   -
        -
        , -
        - - -
        - Value is not correct -
        -
        , -
        - - -
        -   -
        -
        , -
        - -
        -
        - -
        -
        -
        - Error is over here -
        -
        -
        -
        - We sent you this in a card in the mail -
        -
        , -] -`; diff --git a/services-js/access-boston/src/tsconfig.json b/services-js/access-boston/src/tsconfig.json deleted file mode 100644 index a98f4c8b6..000000000 --- a/services-js/access-boston/src/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "../tsconfig.json", - "compilerOptions": { - "isolatedModules": true, - "noEmit": true, - "module": "esnext", - "allowJs": true - }, - "include": ["**/*.ts", "**/*.tsx"] -} diff --git a/services-js/access-boston/static/assets/apps/agilepoint.svg b/services-js/access-boston/static/assets/apps/agilepoint.svg deleted file mode 100644 index fd5d7cf91..000000000 --- a/services-js/access-boston/static/assets/apps/agilepoint.svg +++ /dev/null @@ -1 +0,0 @@ -AgilePoint \ No newline at end of file diff --git a/services-js/access-boston/static/assets/apps/bais-fn.svg b/services-js/access-boston/static/assets/apps/bais-fn.svg deleted file mode 100755 index ebd3c5485..000000000 --- a/services-js/access-boston/static/assets/apps/bais-fn.svg +++ /dev/null @@ -1 +0,0 @@ -bar graph \ No newline at end of file diff --git a/services-js/access-boston/static/assets/apps/bais-hcm.svg b/services-js/access-boston/static/assets/apps/bais-hcm.svg deleted file mode 100755 index f0e87fe1a..000000000 --- a/services-js/access-boston/static/assets/apps/bais-hcm.svg +++ /dev/null @@ -1 +0,0 @@ -web persona \ No newline at end of file diff --git a/services-js/access-boston/static/assets/apps/boston-maps.svg b/services-js/access-boston/static/assets/apps/boston-maps.svg deleted file mode 100755 index 7cbb60d3b..000000000 --- a/services-js/access-boston/static/assets/apps/boston-maps.svg +++ /dev/null @@ -1 +0,0 @@ -maps \ No newline at end of file diff --git a/services-js/access-boston/static/assets/apps/career-center.svg b/services-js/access-boston/static/assets/apps/career-center.svg deleted file mode 100755 index d7ffbdec0..000000000 --- a/services-js/access-boston/static/assets/apps/career-center.svg +++ /dev/null @@ -1 +0,0 @@ -all icons2 \ No newline at end of file diff --git a/services-js/access-boston/static/assets/apps/civis.svg b/services-js/access-boston/static/assets/apps/civis.svg deleted file mode 100755 index 68f73c87d..000000000 --- a/services-js/access-boston/static/assets/apps/civis.svg +++ /dev/null @@ -1 +0,0 @@ -pie chart \ No newline at end of file diff --git a/services-js/access-boston/static/assets/apps/e-builder.svg b/services-js/access-boston/static/assets/apps/e-builder.svg deleted file mode 100755 index 8bcbad06c..000000000 --- a/services-js/access-boston/static/assets/apps/e-builder.svg +++ /dev/null @@ -1,25 +0,0 @@ - - - - - Construnction managment - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/services-js/access-boston/static/assets/apps/employee-training.svg b/services-js/access-boston/static/assets/apps/employee-training.svg deleted file mode 100755 index cd2ce441b..000000000 --- a/services-js/access-boston/static/assets/apps/employee-training.svg +++ /dev/null @@ -1 +0,0 @@ -all icons2 \ No newline at end of file diff --git a/services-js/access-boston/static/assets/apps/ess.svg b/services-js/access-boston/static/assets/apps/ess.svg deleted file mode 100755 index 0e15ed395..000000000 --- a/services-js/access-boston/static/assets/apps/ess.svg +++ /dev/null @@ -1 +0,0 @@ -featured guide \ No newline at end of file diff --git a/services-js/access-boston/static/assets/apps/fleethub.svg b/services-js/access-boston/static/assets/apps/fleethub.svg deleted file mode 100755 index af413bece..000000000 --- a/services-js/access-boston/static/assets/apps/fleethub.svg +++ /dev/null @@ -1 +0,0 @@ -car \ No newline at end of file diff --git a/services-js/access-boston/static/assets/apps/hub.svg b/services-js/access-boston/static/assets/apps/hub.svg deleted file mode 100755 index b59a4daf1..000000000 --- a/services-js/access-boston/static/assets/apps/hub.svg +++ /dev/null @@ -1 +0,0 @@ -all icons2 \ No newline at end of file diff --git a/services-js/access-boston/static/assets/apps/hyperion.svg b/services-js/access-boston/static/assets/apps/hyperion.svg deleted file mode 100755 index 802f94e97..000000000 --- a/services-js/access-boston/static/assets/apps/hyperion.svg +++ /dev/null @@ -1 +0,0 @@ -online purcahse \ No newline at end of file diff --git a/services-js/access-boston/static/assets/apps/identityiq.svg b/services-js/access-boston/static/assets/apps/identityiq.svg deleted file mode 100755 index 1cac62bd9..000000000 --- a/services-js/access-boston/static/assets/apps/identityiq.svg +++ /dev/null @@ -1 +0,0 @@ -ID badge \ No newline at end of file diff --git a/services-js/access-boston/static/assets/apps/job-openings.svg b/services-js/access-boston/static/assets/apps/job-openings.svg deleted file mode 100755 index 96042be1d..000000000 --- a/services-js/access-boston/static/assets/apps/job-openings.svg +++ /dev/null @@ -1 +0,0 @@ -job search \ No newline at end of file diff --git a/services-js/access-boston/static/assets/apps/proofpoint.svg b/services-js/access-boston/static/assets/apps/proofpoint.svg deleted file mode 100755 index b97bba185..000000000 --- a/services-js/access-boston/static/assets/apps/proofpoint.svg +++ /dev/null @@ -1 +0,0 @@ -all icons2 \ No newline at end of file diff --git a/services-js/access-boston/static/assets/apps/talented.svg b/services-js/access-boston/static/assets/apps/talented.svg deleted file mode 100755 index 6175fd6c7..000000000 --- a/services-js/access-boston/static/assets/apps/talented.svg +++ /dev/null @@ -1 +0,0 @@ -school \ No newline at end of file diff --git a/services-js/access-boston/static/assets/favicon.ico b/services-js/access-boston/static/assets/favicon.ico deleted file mode 100644 index dfc738fa8..000000000 Binary files a/services-js/access-boston/static/assets/favicon.ico and /dev/null differ diff --git a/services-js/access-boston/static/assets/ping/assets/css/main.css b/services-js/access-boston/static/assets/ping/assets/css/main.css deleted file mode 100644 index 8d3295b46..000000000 --- a/services-js/access-boston/static/assets/ping/assets/css/main.css +++ /dev/null @@ -1,149 +0,0 @@ -.page-header { - position: fixed; - top: 0; - left: 0; - background-color: rgb(9, 31, 47); - color: white; - z-index: 2; -} - -.b { - position: relative; - top: 50px; -} - -.form-fields { - max-width: 400px; -} - -.page-header > h1 { - width: 100vw; - font-family: Montserrat, Arial, sans-serif; - text-transform: uppercase; - font-size: 1.25rem; - font-weight: bold; -} - -.place-bottom { - visibility: hidden; -} - -.place-bottom.show { - visibility: visible; -} - -.ping-buttons.login > button[type='button'] { - display: block; - width: 100%; -} - -.change-password button { - margin-bottom: 2rem; - - -webkit-box-flex: 1; - -ms-flex-positive: 1; - flex-grow: 1; -} - -.change-password .ping-buttons { - display: -webkit-box; - display: -ms-flexbox; - display: flex; - - -webkit-box-orient: vertical; - -webkit-box-direction: normal; - -ms-flex-direction: column; - flex-direction: column; -} - -@media (min-width: 420px) { - .ping-buttons.login { - text-align: right; - } - - .ping-buttons.login > button[type='button'] { - display: inline-block; - width: auto; - min-width: 200px; - } - - .change-password .ping-buttons { - -webkit-box-orient: horizontal; - -webkit-box-direction: normal; - -ms-flex-direction: row; - flex-direction: row; - - -webkit-box-pack: justify; - -ms-flex-pack: justify; - justify-content: space-between; - } - - .change-password button:last-of-type { - margin-left: 2rem; - } -} - -@media (min-width: 840px) { - .change-password > div.g--5 { - margin-left: 1.5rem; - margin-right: 0; - - -webkit-box-ordinal-group: 2; - -ms-flex-order: 1; - order: 1; - } -} - -.btn:focus { - outline: 2px solid white; - box-shadow: 0 0 0 5px #288be4; -} - -.btn-link-styled { - font: inherit; - padding: 0; - border: none; - color: #288be4; - background-color: transparent; - cursor: pointer; -} - -.btn-link-styled:hover { - color: #fb4d42; -} - -.btn.secondary { - background-color: white; - color: #288be4; - border: 2px solid #288be4; -} - -.image-aside { - display: none; - width: 100%; - margin: 0 auto; -} - -@media (min-width: 840px) { - .image-aside { - display: block; - margin-right: -0.75rem; - } -} - -.image-aside > img { - width: 100%; -} - -.ping-pass-change { - text-align: center; - white-space: nowrap; -} - -.ping-register { - text-align: right; -} - -.contact-heading { - margin-top: 0.75rem !important; -} diff --git a/services-js/access-boston/static/assets/ping/assets/images/city-hall.svg b/services-js/access-boston/static/assets/ping/assets/images/city-hall.svg deleted file mode 100644 index ae41281b3..000000000 --- a/services-js/access-boston/static/assets/ping/assets/images/city-hall.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/services-js/access-boston/tsconfig.json b/services-js/access-boston/tsconfig.json deleted file mode 100644 index ef3f3cfa8..000000000 --- a/services-js/access-boston/tsconfig.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "extends": - "./node_modules/@cityofboston/config-typescript/tsconfig.default.json", - "compilerOptions": { - "rootDir": "src", - "outDir": "build", - "declaration": false, - "resolveJsonModule": true - }, - "include": ["src/**/*"], - "exclude": ["src/integration/**/*"] -} diff --git a/services-js/access-boston/tsconfig.server.json b/services-js/access-boston/tsconfig.server.json deleted file mode 100644 index 708846dad..000000000 --- a/services-js/access-boston/tsconfig.server.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "extends": "./node_modules/@cityofboston/config-typescript/tsconfig.default.json", - "compilerOptions": { - "rootDir": "src", - "outDir": "build", - "sourceMap": true, - "declaration": false - }, - "include": [ - "src/**/*" - ], - "exclude": [ - "src/client/**/*", - "src/pages/**/*", - "src/stories/**/*", - "src/integration/**/*" - ] -} \ No newline at end of file diff --git a/services-js/access-boston/tsnode.config.json b/services-js/access-boston/tsnode.config.json deleted file mode 100644 index 2f86eb6ea..000000000 --- a/services-js/access-boston/tsnode.config.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "extends": "./node_modules/@cityofboston/config-typescript/tsconfig.default.json", - "compilerOptions": { - "module": "commonjs" - } -} diff --git a/services-js/commissions-app/.babelrc b/services-js/commissions-app/.babelrc deleted file mode 100644 index 76d4301fc..000000000 --- a/services-js/commissions-app/.babelrc +++ /dev/null @@ -1,7 +0,0 @@ -{ - "presets": [ - "@cityofboston/config-babel/next", - "@cityofboston/config-babel/typescript", - "@cityofboston/config-babel/storybook" - ] -} diff --git a/services-js/commissions-app/.eslintrc b/services-js/commissions-app/.eslintrc deleted file mode 100644 index a2d1476c4..000000000 --- a/services-js/commissions-app/.eslintrc +++ /dev/null @@ -1,19 +0,0 @@ -{ - "plugins": [ - "graphql" - ], - "rules": { - "graphql/template-strings": [ - "error", - { - "projectName": "commissions-app" - } - ], - "graphql/named-operations": [ - "error", - { - "projectName": "commissions-app" - } - ] - } -} \ No newline at end of file diff --git a/services-js/commissions-app/.storybook/.babelrc b/services-js/commissions-app/.storybook/.babelrc deleted file mode 100644 index 61ee23b00..000000000 --- a/services-js/commissions-app/.storybook/.babelrc +++ /dev/null @@ -1 +0,0 @@ -{ "extends": "../.babelrc" } \ No newline at end of file diff --git a/services-js/commissions-app/.storybook/addons.js b/services-js/commissions-app/.storybook/addons.js deleted file mode 100644 index a6461528a..000000000 --- a/services-js/commissions-app/.storybook/addons.js +++ /dev/null @@ -1 +0,0 @@ -require('@cityofboston/storybook-common/addons'); diff --git a/services-js/commissions-app/.storybook/config.js b/services-js/commissions-app/.storybook/config.js deleted file mode 100644 index f9f8571da..000000000 --- a/services-js/commissions-app/.storybook/config.js +++ /dev/null @@ -1,11 +0,0 @@ -import { addParameters, configure } from '@storybook/react'; - -import { loadStories, storybookOptions } from '@cityofboston/storybook-common'; - -import './addons'; - -const req = require.context('../src', true, /\.stories\.(jsx?|tsx?)$/); - -addParameters(storybookOptions('commissions-app')); - -configure(() => loadStories(req), module); diff --git a/services-js/commissions-app/.storybook/manager-head.html b/services-js/commissions-app/.storybook/manager-head.html deleted file mode 100644 index 9191c39c0..000000000 --- a/services-js/commissions-app/.storybook/manager-head.html +++ /dev/null @@ -1,10 +0,0 @@ - - diff --git a/services-js/commissions-app/.storybook/preview-head.html b/services-js/commissions-app/.storybook/preview-head.html deleted file mode 100644 index 41348c942..000000000 --- a/services-js/commissions-app/.storybook/preview-head.html +++ /dev/null @@ -1,7 +0,0 @@ - - - - - \ No newline at end of file diff --git a/services-js/commissions-app/.storybook/webpack.config.js b/services-js/commissions-app/.storybook/webpack.config.js deleted file mode 100644 index 63e0af29c..000000000 --- a/services-js/commissions-app/.storybook/webpack.config.js +++ /dev/null @@ -1,3 +0,0 @@ -const { webpackConfig } = require('@cityofboston/storybook-common'); - -module.exports = ({ config }) => webpackConfig(config); diff --git a/services-js/commissions-app/README.md b/services-js/commissions-app/README.md deleted file mode 100644 index e3280c8e3..000000000 --- a/services-js/commissions-app/README.md +++ /dev/null @@ -1,16 +0,0 @@ - -## URLs - -### Localhost -http://localhost:3000/commissions/apply - -### Staging -https://commissions-app.digital-staging.boston.gov/commissions/apply - -### PROD -https://apps.boston.gov/commissions/apply - -### Deploys - -- 2020.09.30: Security Patch: Remove hardcoded ssl pass in deploy script -- 2022.01.12: `Temp` Disable resume/cover letter upload and use mailto diff --git a/services-js/commissions-app/deploy/Dockerfile b/services-js/commissions-app/deploy/Dockerfile deleted file mode 100644 index 406db62d8..000000000 --- a/services-js/commissions-app/deploy/Dockerfile +++ /dev/null @@ -1,72 +0,0 @@ -FROM node:14.19.1-alpine as build_phase - -ENV WORKSPACE=commissions-app - -WORKDIR /app - -# Install python/pip -ENV PYTHONUNBUFFERED=1 -RUN apk add --no-cache git openssl bash \ - && apk add --update --no-cache python3 curl unzip \ - && ln -sf python3 /usr/bin/python \ - && python3 -m ensurepip \ - && pip3 install --no-cache --upgrade pip setuptools \ - && cd /tmp \ - && curl "https://s3.amazonaws.com/aws-cli/awscli-bundle.zip" -o "awscli-bundle.zip" \ - && unzip awscli-bundle.zip \ - && ./awscli-bundle/install -i /usr/local/aws -b /usr/local/bin/aws \ - && rm awscli-bundle.zip \ - && rm -rf awscli-bundle - -# To prevent “Error: could not get uid/gid” -RUN npm config set unsafe-perm true - -# Need to upgrade yarn to at least 1.6 -RUN yarn global add yarn@^1.6.0 - -# This is the tar'd up collection of package.json files created by -# build-service-container.sh. Working with it and the lockfiles means we can -# cache the yarn install across builds when there are no dependency changes. -ADD package-json.tar yarn.lock lerna.json .yarnrc /app/ - -# We don’t run the scripts because they will try to build our custom packages, -# which will fail because we don’t have the source code at this point. -# -# TODO(finh): Scope this down to $WORKSPACE when yarn has that capability. -RUN yarn install --frozen-lockfile --ignore-scripts - -ADD . /app/ - -RUN /app/scripts/generate-ssl-key.sh /app/services-js/$WORKSPACE - -# This does the building of our repo’s packages, which couldn’t happen during -# the initial install because we didn’t have source. The scope keeps us from -# building unnecessary packages. -RUN npx lerna run --stream --include-filtered-dependencies --scope services-js.$WORKSPACE prepare - -FROM node:14.19.1-alpine as deploy_phase - -EXPOSE 3000 - -ENV WORKSPACE=commissions-app -ENV NODE_ENV=production -ENV USE_SSL=1 - -WORKDIR /app/services-js/$WORKSPACE - -RUN apk add --no-cache git openssl \ - && apk add --update --no-cache python3 curl unzip \ - && ln -sf python3 /usr/bin/python \ - && python3 -m ensurepip \ - && pip3 install --no-cache --upgrade pip setuptools \ - && cd /tmp \ - && curl "https://s3.amazonaws.com/aws-cli/awscli-bundle.zip" -o "awscli-bundle.zip" \ - && unzip awscli-bundle.zip \ - && ./awscli-bundle/install -i /usr/local/aws -b /usr/local/bin/aws \ - && rm awscli-bundle.zip \ - && rm -rf awscli-bundle - -COPY --from=build_phase /app /app - -ENTRYPOINT ["/app/scripts/service-entrypoint.sh"] -CMD ["yarn", "start"] diff --git a/services-js/commissions-app/fixtures/Authorities.json b/services-js/commissions-app/fixtures/Authorities.json deleted file mode 100644 index 36368a153..000000000 --- a/services-js/commissions-app/fixtures/Authorities.json +++ /dev/null @@ -1,82 +0,0 @@ -[ - { - "AuthorityId": 100, - "AuthorityType": "Not Applicable", - "AddBy": "system", - "AddDtTm": "2013-09-05T16:22:33.380Z", - "ModBy": null, - "ModDtTm": null - }, - { - "AuthorityId": 102, - "AuthorityType": "City", - "AddBy": "system", - "AddDtTm": "2013-09-05T16:22:33.387Z", - "ModBy": null, - "ModDtTm": null - }, - { - "AuthorityId": 102, - "AuthorityType": "City", - "AddBy": "system", - "AddDtTm": "2013-09-05T16:22:33.387Z", - "ModBy": null, - "ModDtTm": null - }, - { - "AuthorityId": 100, - "AuthorityType": "Not Applicable", - "AddBy": "system", - "AddDtTm": "2013-09-05T16:22:33.380Z", - "ModBy": null, - "ModDtTm": null - }, - { - "AuthorityId": 102, - "AuthorityType": "City", - "AddBy": "system", - "AddDtTm": "2013-09-05T16:22:33.387Z", - "ModBy": null, - "ModDtTm": null - }, - { - "AuthorityId": 102, - "AuthorityType": "City", - "AddBy": "system", - "AddDtTm": "2013-09-05T16:22:33.387Z", - "ModBy": null, - "ModDtTm": null - }, - { - "AuthorityId": 102, - "AuthorityType": "City", - "AddBy": "system", - "AddDtTm": "2013-09-05T16:22:33.387Z", - "ModBy": null, - "ModDtTm": null - }, - { - "AuthorityId": 103, - "AuthorityType": "Fed", - "AddBy": "system", - "AddDtTm": "2013-09-05T16:22:33.387Z", - "ModBy": null, - "ModDtTm": null - }, - { - "AuthorityId": 102, - "AuthorityType": "City", - "AddBy": "system", - "AddDtTm": "2013-09-05T16:22:33.387Z", - "ModBy": null, - "ModDtTm": null - }, - { - "AuthorityId": 102, - "AuthorityType": "City", - "AddBy": "system", - "AddDtTm": "2013-09-05T16:22:33.387Z", - "ModBy": null, - "ModDtTm": null - } -] \ No newline at end of file diff --git a/services-js/commissions-app/fixtures/BoardMembers.json b/services-js/commissions-app/fixtures/BoardMembers.json deleted file mode 100644 index 7b496775d..000000000 --- a/services-js/commissions-app/fixtures/BoardMembers.json +++ /dev/null @@ -1,1612 +0,0 @@ -[ - { - "BoardID": 1, - "AssignmentId": 141, - "PersonId": 874, - "StatusId": 301, - "BoardName": "Trustees of Charitable Donations for Inhabitants of Boston", - "AppointDtTm": "2009-01-07T00:00:00.000Z", - "ExpireDtTm": "2012-01-09T00:00:00.000Z", - "Docket": null, - "FirstName": "Jarret", - "LastName": "O'Conner", - "StatusName": "Holdover", - "Stipend": 0 - }, - { - "BoardID": 1, - "AssignmentId": 142, - "PersonId": 767, - "StatusId": 301, - "BoardName": "Trustees of Charitable Donations for Inhabitants of Boston", - "AppointDtTm": "2009-01-07T00:00:00.000Z", - "ExpireDtTm": "2012-01-09T00:00:00.000Z", - "Docket": null, - "FirstName": "Rod", - "LastName": "Carroll", - "StatusName": "Holdover", - "Stipend": 0 - }, - { - "BoardID": 1, - "AssignmentId": 143, - "PersonId": 731, - "StatusId": 301, - "BoardName": "Trustees of Charitable Donations for Inhabitants of Boston", - "AppointDtTm": "2009-01-07T00:00:00.000Z", - "ExpireDtTm": "2012-01-09T00:00:00.000Z", - "Docket": null, - "FirstName": "Braeden", - "LastName": "Hilpert", - "StatusName": "Holdover", - "Stipend": 0 - }, - { - "BoardID": 1, - "AssignmentId": 192, - "PersonId": 720, - "StatusId": 301, - "BoardName": "Trustees of Charitable Donations for Inhabitants of Boston", - "AppointDtTm": "2009-01-07T00:00:00.000Z", - "ExpireDtTm": "2012-01-09T00:00:00.000Z", - "Docket": null, - "FirstName": "Torrance", - "LastName": "Prosacco", - "StatusName": "Holdover", - "Stipend": 0 - }, - { - "BoardID": 1, - "AssignmentId": 383, - "PersonId": 732, - "StatusId": 301, - "BoardName": "Trustees of Charitable Donations for Inhabitants of Boston", - "AppointDtTm": "2009-01-07T00:00:00.000Z", - "ExpireDtTm": "2012-01-09T00:00:00.000Z", - "Docket": null, - "FirstName": "Christian", - "LastName": "Schaefer", - "StatusName": "Holdover", - "Stipend": 0 - }, - { - "BoardID": 1, - "AssignmentId": 384, - "PersonId": 779, - "StatusId": 301, - "BoardName": "Trustees of Charitable Donations for Inhabitants of Boston", - "AppointDtTm": "2009-01-07T00:00:00.000Z", - "ExpireDtTm": "2012-01-09T00:00:00.000Z", - "Docket": null, - "FirstName": "Emiliano", - "LastName": "Hettinger", - "StatusName": "Holdover", - "Stipend": 0 - }, - { - "BoardID": 1, - "AssignmentId": 385, - "PersonId": 368, - "StatusId": 301, - "BoardName": "Trustees of Charitable Donations for Inhabitants of Boston", - "AppointDtTm": "2009-01-07T00:00:00.000Z", - "ExpireDtTm": "2012-01-09T00:00:00.000Z", - "Docket": null, - "FirstName": "Joesph", - "LastName": "Cole", - "StatusName": "Holdover", - "Stipend": 0 - }, - { - "BoardID": 1, - "AssignmentId": 386, - "PersonId": 729, - "StatusId": 301, - "BoardName": "Trustees of Charitable Donations for Inhabitants of Boston", - "AppointDtTm": "2009-01-07T00:00:00.000Z", - "ExpireDtTm": "2012-01-09T00:00:00.000Z", - "Docket": null, - "FirstName": "Heather", - "LastName": "Boyle", - "StatusName": "Holdover", - "Stipend": 0 - }, - { - "BoardID": 2, - "AssignmentId": 165, - "PersonId": 170, - "StatusId": 101, - "BoardName": "Boston School Committee Nominating Panel", - "AppointDtTm": "2018-04-20T00:00:00.000Z", - "ExpireDtTm": "2020-04-20T00:00:00.000Z", - "Docket": "06723", - "FirstName": "Anderson", - "LastName": "Corkery", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 2, - "AssignmentId": 167, - "PersonId": 1050, - "StatusId": 101, - "BoardName": "Boston School Committee Nominating Panel", - "AppointDtTm": null, - "ExpireDtTm": null, - "Docket": null, - "FirstName": "Delores", - "LastName": "Will", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 2, - "AssignmentId": 169, - "PersonId": 165, - "StatusId": 101, - "BoardName": "Boston School Committee Nominating Panel", - "AppointDtTm": "2018-04-20T00:00:00.000Z", - "ExpireDtTm": "2020-04-13T00:00:00.000Z", - "Docket": "0671", - "FirstName": "Petra", - "LastName": "Becker", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 2, - "AssignmentId": 452, - "PersonId": 1048, - "StatusId": 101, - "BoardName": "Boston School Committee Nominating Panel", - "AppointDtTm": "2018-04-13T00:00:00.000Z", - "ExpireDtTm": "2020-04-13T00:00:00.000Z", - "Docket": "0671", - "FirstName": "Suzanne", - "LastName": "MacGyver", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 2, - "AssignmentId": 456, - "PersonId": 1469, - "StatusId": 101, - "BoardName": "Boston School Committee Nominating Panel", - "AppointDtTm": null, - "ExpireDtTm": null, - "Docket": null, - "FirstName": "Ray", - "LastName": "Renner", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 2, - "AssignmentId": 844, - "PersonId": 166, - "StatusId": 101, - "BoardName": "Boston School Committee Nominating Panel", - "AppointDtTm": null, - "ExpireDtTm": null, - "Docket": null, - "FirstName": "Clemens", - "LastName": "Bahringer", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 2, - "AssignmentId": 1139, - "PersonId": 1404, - "StatusId": 101, - "BoardName": "Boston School Committee Nominating Panel", - "AppointDtTm": null, - "ExpireDtTm": null, - "Docket": null, - "FirstName": "Sim", - "LastName": "Greenholt", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 2, - "AssignmentId": 1167, - "PersonId": 1344, - "StatusId": 101, - "BoardName": "Boston School Committee Nominating Panel", - "AppointDtTm": null, - "ExpireDtTm": null, - "Docket": null, - "FirstName": "Theodora", - "LastName": "Marvin", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 2, - "AssignmentId": 1265, - "PersonId": 1458, - "StatusId": 301, - "BoardName": "Boston School Committee Nominating Panel", - "AppointDtTm": "2016-10-04T00:00:00.000Z", - "ExpireDtTm": "2018-09-01T00:00:00.000Z", - "Docket": null, - "FirstName": "Lucious", - "LastName": "Bruen", - "StatusName": "Holdover", - "Stipend": 0 - }, - { - "BoardID": 2, - "AssignmentId": 1275, - "PersonId": 1465, - "StatusId": 101, - "BoardName": "Boston School Committee Nominating Panel", - "AppointDtTm": null, - "ExpireDtTm": null, - "Docket": null, - "FirstName": "Cierra", - "LastName": "Streich", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 2, - "AssignmentId": 1276, - "PersonId": 1467, - "StatusId": 101, - "BoardName": "Boston School Committee Nominating Panel", - "AppointDtTm": null, - "ExpireDtTm": null, - "Docket": null, - "FirstName": "Devante", - "LastName": "McDermott", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 4, - "AssignmentId": 6, - "PersonId": 1409, - "StatusId": 101, - "BoardName": "Boston School Committee", - "AppointDtTm": "2016-01-04T00:00:00.000Z", - "ExpireDtTm": "2020-01-06T00:00:00.000Z", - "Docket": "0133", - "FirstName": "Bettye", - "LastName": "Gaylord", - "StatusName": "Active", - "Stipend": 7500 - }, - { - "BoardID": 4, - "AssignmentId": 458, - "PersonId": 991, - "StatusId": 101, - "BoardName": "Boston School Committee", - "AppointDtTm": "2017-01-03T00:00:00.000Z", - "ExpireDtTm": "2021-01-04T00:00:00.000Z", - "Docket": null, - "FirstName": "Merritt", - "LastName": "Wilkinson", - "StatusName": "Active", - "Stipend": 7500 - }, - { - "BoardID": 4, - "AssignmentId": 869, - "PersonId": 1074, - "StatusId": 101, - "BoardName": "Boston School Committee", - "AppointDtTm": "2018-01-01T00:00:00.000Z", - "ExpireDtTm": "2022-01-03T00:00:00.000Z", - "Docket": "0103", - "FirstName": "Chester", - "LastName": "Ledner", - "StatusName": "Active", - "Stipend": 7500 - }, - { - "BoardID": 4, - "AssignmentId": 941, - "PersonId": 1155, - "StatusId": 3504, - "BoardName": "Boston School Committee", - "AppointDtTm": null, - "ExpireDtTm": null, - "Docket": null, - "FirstName": "Tiara", - "LastName": "Kihn", - "StatusName": "employee", - "Stipend": 7500 - }, - { - "BoardID": 4, - "AssignmentId": 942, - "PersonId": 1156, - "StatusId": 3504, - "BoardName": "Boston School Committee", - "AppointDtTm": null, - "ExpireDtTm": null, - "Docket": null, - "FirstName": "Katlyn", - "LastName": "Hintz", - "StatusName": "employee", - "Stipend": 7500 - }, - { - "BoardID": 4, - "AssignmentId": 1010, - "PersonId": 1213, - "StatusId": 101, - "BoardName": "Boston School Committee", - "AppointDtTm": "2018-01-01T00:00:00.000Z", - "ExpireDtTm": "2022-01-03T00:00:00.000Z", - "Docket": "0102", - "FirstName": "Russel", - "LastName": "Keebler", - "StatusName": "Active", - "Stipend": 7500 - }, - { - "BoardID": 4, - "AssignmentId": 1138, - "PersonId": 1327, - "StatusId": 101, - "BoardName": "Boston School Committee", - "AppointDtTm": "2014-12-30T00:00:00.000Z", - "ExpireDtTm": "2019-01-07T00:00:00.000Z", - "Docket": "0130", - "FirstName": "Vito", - "LastName": "Kuvalis", - "StatusName": "Active", - "Stipend": 7500 - }, - { - "BoardID": 4, - "AssignmentId": 1166, - "PersonId": 1342, - "StatusId": 101, - "BoardName": "Boston School Committee", - "AppointDtTm": "2014-12-30T00:00:00.000Z", - "ExpireDtTm": "2019-01-07T00:00:00.000Z", - "Docket": "0131", - "FirstName": "Alfonso", - "LastName": "Funk", - "StatusName": "Active", - "Stipend": 7500 - }, - { - "BoardID": 4, - "AssignmentId": 1176, - "PersonId": 1355, - "StatusId": 101, - "BoardName": "Boston School Committee", - "AppointDtTm": "2016-01-04T00:00:00.000Z", - "ExpireDtTm": "2020-01-06T00:00:00.000Z", - "Docket": "0132", - "FirstName": "Shaina", - "LastName": "Wolf", - "StatusName": "Active", - "Stipend": 7500 - }, - { - "BoardID": 7, - "AssignmentId": 14, - "PersonId": 1511, - "StatusId": 101, - "BoardName": "Neighborhood Housing Trust Fund", - "AppointDtTm": "2018-01-09T00:00:00.000Z", - "ExpireDtTm": "2018-12-31T00:00:00.000Z", - "Docket": null, - "FirstName": "Braeden", - "LastName": "Huels", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 7, - "AssignmentId": 19, - "PersonId": 184, - "StatusId": 301, - "BoardName": "Neighborhood Housing Trust Fund", - "AppointDtTm": "2014-03-11T00:00:00.000Z", - "ExpireDtTm": "2016-01-08T00:00:00.000Z", - "Docket": "0520", - "FirstName": "Leonora", - "LastName": "Wolff", - "StatusName": "Holdover", - "Stipend": 0 - }, - { - "BoardID": 7, - "AssignmentId": 873, - "PersonId": 984, - "StatusId": 301, - "BoardName": "Neighborhood Housing Trust Fund", - "AppointDtTm": "2014-03-11T00:00:00.000Z", - "ExpireDtTm": "2016-01-08T00:00:00.000Z", - "Docket": "0519", - "FirstName": "Reyes", - "LastName": "Eichmann", - "StatusName": "Holdover", - "Stipend": 0 - }, - { - "BoardID": 7, - "AssignmentId": 1213, - "PersonId": 1391, - "StatusId": 101, - "BoardName": "Neighborhood Housing Trust Fund", - "AppointDtTm": "2018-08-14T00:00:00.000Z", - "ExpireDtTm": "2020-01-01T00:00:00.000Z", - "Docket": "1246", - "FirstName": "Henry", - "LastName": "Reichel", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 7, - "AssignmentId": 1241, - "PersonId": 1430, - "StatusId": 101, - "BoardName": "Neighborhood Housing Trust Fund", - "AppointDtTm": "2018-08-14T00:00:00.000Z", - "ExpireDtTm": "2020-01-01T00:00:00.000Z", - "Docket": "1247", - "FirstName": "Deontae", - "LastName": "Ankunding", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 7, - "AssignmentId": 1408, - "PersonId": 1610, - "StatusId": 101, - "BoardName": "Neighborhood Housing Trust Fund", - "AppointDtTm": "2018-08-14T00:00:00.000Z", - "ExpireDtTm": "2020-01-01T00:00:00.000Z", - "Docket": "1248", - "FirstName": "Elenor", - "LastName": "Dickens", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 10, - "AssignmentId": 659, - "PersonId": 586, - "StatusId": 101, - "BoardName": "City of Boston School Trust Fund", - "AppointDtTm": null, - "ExpireDtTm": null, - "Docket": null, - "FirstName": "Steve", - "LastName": "Hagenes", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 10, - "AssignmentId": 660, - "PersonId": 1202, - "StatusId": 101, - "BoardName": "City of Boston School Trust Fund", - "AppointDtTm": null, - "ExpireDtTm": null, - "Docket": null, - "FirstName": "Dan", - "LastName": "Reynolds", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 10, - "AssignmentId": 661, - "PersonId": 1213, - "StatusId": 101, - "BoardName": "City of Boston School Trust Fund", - "AppointDtTm": null, - "ExpireDtTm": null, - "Docket": null, - "FirstName": "Blaze", - "LastName": "Lynch", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 10, - "AssignmentId": 662, - "PersonId": 1074, - "StatusId": 101, - "BoardName": "City of Boston School Trust Fund", - "AppointDtTm": null, - "ExpireDtTm": null, - "Docket": null, - "FirstName": "Carter", - "LastName": "Erdman", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 10, - "AssignmentId": 688, - "PersonId": 991, - "StatusId": 101, - "BoardName": "City of Boston School Trust Fund", - "AppointDtTm": null, - "ExpireDtTm": null, - "Docket": null, - "FirstName": "Jaqueline", - "LastName": "Hintz", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 10, - "AssignmentId": 690, - "PersonId": 270, - "StatusId": 101, - "BoardName": "City of Boston School Trust Fund", - "AppointDtTm": null, - "ExpireDtTm": null, - "Docket": null, - "FirstName": "Judah", - "LastName": "Johns", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 10, - "AssignmentId": 1043, - "PersonId": 863, - "StatusId": 101, - "BoardName": "City of Boston School Trust Fund", - "AppointDtTm": null, - "ExpireDtTm": null, - "Docket": null, - "FirstName": "Georgette", - "LastName": "Hegmann", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 11, - "AssignmentId": 614, - "PersonId": 1592, - "StatusId": 101, - "BoardName": "George Robert White Fund", - "AppointDtTm": null, - "ExpireDtTm": null, - "Docket": null, - "FirstName": "Korey", - "LastName": "Hills", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 11, - "AssignmentId": 654, - "PersonId": 47, - "StatusId": 101, - "BoardName": "George Robert White Fund", - "AppointDtTm": null, - "ExpireDtTm": null, - "Docket": null, - "FirstName": "Sedrick", - "LastName": "Kohler", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 11, - "AssignmentId": 1022, - "PersonId": 757, - "StatusId": 101, - "BoardName": "George Robert White Fund", - "AppointDtTm": null, - "ExpireDtTm": null, - "Docket": null, - "FirstName": "Santina", - "LastName": "Satterfield", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 11, - "AssignmentId": 1395, - "PersonId": 1593, - "StatusId": 101, - "BoardName": "George Robert White Fund", - "AppointDtTm": null, - "ExpireDtTm": null, - "Docket": null, - "FirstName": "Marion", - "LastName": "Fadel", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 11, - "AssignmentId": 1396, - "PersonId": 1594, - "StatusId": 101, - "BoardName": "George Robert White Fund", - "AppointDtTm": null, - "ExpireDtTm": null, - "Docket": null, - "FirstName": "Edgar", - "LastName": "Klocko", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 13, - "AssignmentId": 1026, - "PersonId": 858, - "StatusId": 101, - "BoardName": "Fund for Parks & Recreation in Boston", - "AppointDtTm": null, - "ExpireDtTm": null, - "Docket": null, - "FirstName": "Macie", - "LastName": "Kessler", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 13, - "AssignmentId": 1027, - "PersonId": 1518, - "StatusId": 101, - "BoardName": "Fund for Parks & Recreation in Boston", - "AppointDtTm": null, - "ExpireDtTm": null, - "Docket": null, - "FirstName": "Laurence", - "LastName": "Bernhard", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 15, - "AssignmentId": 634, - "PersonId": 1027, - "StatusId": 101, - "BoardName": "Resident Advisory Board", - "AppointDtTm": null, - "ExpireDtTm": null, - "Docket": null, - "FirstName": "Cassandra", - "LastName": "O'Kon", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 15, - "AssignmentId": 642, - "PersonId": 1026, - "StatusId": 101, - "BoardName": "Resident Advisory Board", - "AppointDtTm": null, - "ExpireDtTm": null, - "Docket": null, - "FirstName": "Ervin", - "LastName": "Wunsch", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 15, - "AssignmentId": 645, - "PersonId": 1007, - "StatusId": 101, - "BoardName": "Resident Advisory Board", - "AppointDtTm": null, - "ExpireDtTm": null, - "Docket": null, - "FirstName": "Hudson", - "LastName": "Muller", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 15, - "AssignmentId": 646, - "PersonId": 738, - "StatusId": 101, - "BoardName": "Resident Advisory Board", - "AppointDtTm": null, - "ExpireDtTm": null, - "Docket": null, - "FirstName": "Casey", - "LastName": "Rolfson", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 15, - "AssignmentId": 648, - "PersonId": 1023, - "StatusId": 101, - "BoardName": "Resident Advisory Board", - "AppointDtTm": null, - "ExpireDtTm": null, - "Docket": null, - "FirstName": "River", - "LastName": "Wiegand", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 15, - "AssignmentId": 675, - "PersonId": 1029, - "StatusId": 101, - "BoardName": "Resident Advisory Board", - "AppointDtTm": null, - "ExpireDtTm": null, - "Docket": null, - "FirstName": "Urban", - "LastName": "Gorczany", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 15, - "AssignmentId": 679, - "PersonId": 1033, - "StatusId": 101, - "BoardName": "Resident Advisory Board", - "AppointDtTm": null, - "ExpireDtTm": null, - "Docket": null, - "FirstName": "Gisselle", - "LastName": "Hagenes", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 15, - "AssignmentId": 681, - "PersonId": 599, - "StatusId": 101, - "BoardName": "Resident Advisory Board", - "AppointDtTm": null, - "ExpireDtTm": null, - "Docket": null, - "FirstName": "Maegan", - "LastName": "Auer", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 15, - "AssignmentId": 683, - "PersonId": 952, - "StatusId": 101, - "BoardName": "Resident Advisory Board", - "AppointDtTm": null, - "ExpireDtTm": null, - "Docket": null, - "FirstName": "Marilyne", - "LastName": "Vandervort", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 15, - "AssignmentId": 684, - "PersonId": 1028, - "StatusId": 101, - "BoardName": "Resident Advisory Board", - "AppointDtTm": null, - "ExpireDtTm": null, - "Docket": null, - "FirstName": "Pete", - "LastName": "Schmidt", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 15, - "AssignmentId": 820, - "PersonId": 1036, - "StatusId": 101, - "BoardName": "Resident Advisory Board", - "AppointDtTm": null, - "ExpireDtTm": null, - "Docket": null, - "FirstName": "Susan", - "LastName": "Dare", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 15, - "AssignmentId": 825, - "PersonId": 838, - "StatusId": 101, - "BoardName": "Resident Advisory Board", - "AppointDtTm": null, - "ExpireDtTm": null, - "Docket": null, - "FirstName": "Idella", - "LastName": "Schumm", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 15, - "AssignmentId": 1356, - "PersonId": 1018, - "StatusId": 101, - "BoardName": "Resident Advisory Board", - "AppointDtTm": null, - "ExpireDtTm": null, - "Docket": null, - "FirstName": "Rosamond", - "LastName": "Kozey", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 15, - "AssignmentId": 1357, - "PersonId": 1553, - "StatusId": 101, - "BoardName": "Resident Advisory Board", - "AppointDtTm": null, - "ExpireDtTm": null, - "Docket": null, - "FirstName": "Ursula", - "LastName": "Lubowitz", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 15, - "AssignmentId": 1358, - "PersonId": 1554, - "StatusId": 101, - "BoardName": "Resident Advisory Board", - "AppointDtTm": null, - "ExpireDtTm": null, - "Docket": null, - "FirstName": "Jalyn", - "LastName": "Kerluke", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 15, - "AssignmentId": 1359, - "PersonId": 1555, - "StatusId": 101, - "BoardName": "Resident Advisory Board", - "AppointDtTm": null, - "ExpireDtTm": null, - "Docket": null, - "FirstName": "Jed", - "LastName": "Toy", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 15, - "AssignmentId": 1360, - "PersonId": 1556, - "StatusId": 101, - "BoardName": "Resident Advisory Board", - "AppointDtTm": null, - "ExpireDtTm": null, - "Docket": null, - "FirstName": "Kadin", - "LastName": "Mueller", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 15, - "AssignmentId": 1361, - "PersonId": 1558, - "StatusId": 101, - "BoardName": "Resident Advisory Board", - "AppointDtTm": null, - "ExpireDtTm": null, - "Docket": null, - "FirstName": "Mireya", - "LastName": "Fay", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 15, - "AssignmentId": 1362, - "PersonId": 1560, - "StatusId": 101, - "BoardName": "Resident Advisory Board", - "AppointDtTm": null, - "ExpireDtTm": null, - "Docket": null, - "FirstName": "Audreanne", - "LastName": "Gerhold", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 15, - "AssignmentId": 1363, - "PersonId": 1561, - "StatusId": 101, - "BoardName": "Resident Advisory Board", - "AppointDtTm": null, - "ExpireDtTm": null, - "Docket": null, - "FirstName": "Velma", - "LastName": "Rolfson", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 15, - "AssignmentId": 1365, - "PersonId": 1564, - "StatusId": 101, - "BoardName": "Resident Advisory Board", - "AppointDtTm": null, - "ExpireDtTm": null, - "Docket": null, - "FirstName": "Terrell", - "LastName": "Grant", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 15, - "AssignmentId": 1366, - "PersonId": 1565, - "StatusId": 101, - "BoardName": "Resident Advisory Board", - "AppointDtTm": null, - "ExpireDtTm": null, - "Docket": null, - "FirstName": "Janet", - "LastName": "Bauch", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 15, - "AssignmentId": 1367, - "PersonId": 1011, - "StatusId": 101, - "BoardName": "Resident Advisory Board", - "AppointDtTm": null, - "ExpireDtTm": null, - "Docket": null, - "FirstName": "Misty", - "LastName": "Botsford", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 15, - "AssignmentId": 1368, - "PersonId": 1566, - "StatusId": 101, - "BoardName": "Resident Advisory Board", - "AppointDtTm": null, - "ExpireDtTm": null, - "Docket": null, - "FirstName": "Micaela", - "LastName": "Lebsack", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 15, - "AssignmentId": 1369, - "PersonId": 1567, - "StatusId": 101, - "BoardName": "Resident Advisory Board", - "AppointDtTm": null, - "ExpireDtTm": null, - "Docket": null, - "FirstName": "Jeffery", - "LastName": "Bauch", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 15, - "AssignmentId": 1370, - "PersonId": 1569, - "StatusId": 101, - "BoardName": "Resident Advisory Board", - "AppointDtTm": null, - "ExpireDtTm": null, - "Docket": null, - "FirstName": "Philip", - "LastName": "Bruen", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 15, - "AssignmentId": 1371, - "PersonId": 1570, - "StatusId": 101, - "BoardName": "Resident Advisory Board", - "AppointDtTm": null, - "ExpireDtTm": null, - "Docket": null, - "FirstName": "Sandy", - "LastName": "Mosciski", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 15, - "AssignmentId": 1372, - "PersonId": 1571, - "StatusId": 101, - "BoardName": "Resident Advisory Board", - "AppointDtTm": null, - "ExpireDtTm": null, - "Docket": null, - "FirstName": "Georgiana", - "LastName": "Connelly", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 15, - "AssignmentId": 1373, - "PersonId": 1572, - "StatusId": 101, - "BoardName": "Resident Advisory Board", - "AppointDtTm": null, - "ExpireDtTm": null, - "Docket": null, - "FirstName": "Sonia", - "LastName": "Cronin", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 15, - "AssignmentId": 1374, - "PersonId": 1020, - "StatusId": 101, - "BoardName": "Resident Advisory Board", - "AppointDtTm": null, - "ExpireDtTm": null, - "Docket": null, - "FirstName": "Talia", - "LastName": "Rice", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 15, - "AssignmentId": 1375, - "PersonId": 1286, - "StatusId": 101, - "BoardName": "Resident Advisory Board", - "AppointDtTm": null, - "ExpireDtTm": null, - "Docket": null, - "FirstName": "Lane", - "LastName": "Ortiz", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 15, - "AssignmentId": 1376, - "PersonId": 1574, - "StatusId": 101, - "BoardName": "Resident Advisory Board", - "AppointDtTm": null, - "ExpireDtTm": null, - "Docket": null, - "FirstName": "Kristy", - "LastName": "Schiller", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 15, - "AssignmentId": 1377, - "PersonId": 1575, - "StatusId": 101, - "BoardName": "Resident Advisory Board", - "AppointDtTm": null, - "ExpireDtTm": null, - "Docket": null, - "FirstName": "Haylie", - "LastName": "Grant", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 15, - "AssignmentId": 1378, - "PersonId": 1576, - "StatusId": 101, - "BoardName": "Resident Advisory Board", - "AppointDtTm": null, - "ExpireDtTm": null, - "Docket": null, - "FirstName": "Rudy", - "LastName": "Johnson", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 15, - "AssignmentId": 1379, - "PersonId": 1584, - "StatusId": 101, - "BoardName": "Resident Advisory Board", - "AppointDtTm": null, - "ExpireDtTm": null, - "Docket": null, - "FirstName": "Wallace", - "LastName": "Schuppe", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 15, - "AssignmentId": 1382, - "PersonId": 1585, - "StatusId": 101, - "BoardName": "Resident Advisory Board", - "AppointDtTm": null, - "ExpireDtTm": null, - "Docket": null, - "FirstName": "Manley", - "LastName": "Ernser", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 15, - "AssignmentId": 1383, - "PersonId": 1586, - "StatusId": 101, - "BoardName": "Resident Advisory Board", - "AppointDtTm": null, - "ExpireDtTm": null, - "Docket": null, - "FirstName": "Mozelle", - "LastName": "Lynch", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 15, - "AssignmentId": 1384, - "PersonId": 1587, - "StatusId": 101, - "BoardName": "Resident Advisory Board", - "AppointDtTm": null, - "ExpireDtTm": null, - "Docket": null, - "FirstName": "Solon", - "LastName": "Krajcik", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 15, - "AssignmentId": 1385, - "PersonId": 1577, - "StatusId": 101, - "BoardName": "Resident Advisory Board", - "AppointDtTm": null, - "ExpireDtTm": null, - "Docket": null, - "FirstName": "Delphia", - "LastName": "Schroeder", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 15, - "AssignmentId": 1386, - "PersonId": 1578, - "StatusId": 101, - "BoardName": "Resident Advisory Board", - "AppointDtTm": null, - "ExpireDtTm": null, - "Docket": null, - "FirstName": "Marlee", - "LastName": "Kilback", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 15, - "AssignmentId": 1387, - "PersonId": 1580, - "StatusId": 101, - "BoardName": "Resident Advisory Board", - "AppointDtTm": null, - "ExpireDtTm": null, - "Docket": null, - "FirstName": "Ines", - "LastName": "Kiehn", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 15, - "AssignmentId": 1388, - "PersonId": 1591, - "StatusId": 101, - "BoardName": "Resident Advisory Board", - "AppointDtTm": null, - "ExpireDtTm": null, - "Docket": null, - "FirstName": "Levi", - "LastName": "Terry", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 15, - "AssignmentId": 1389, - "PersonId": 1590, - "StatusId": 101, - "BoardName": "Resident Advisory Board", - "AppointDtTm": null, - "ExpireDtTm": null, - "Docket": null, - "FirstName": "Vanessa", - "LastName": "Klocko", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 15, - "AssignmentId": 1390, - "PersonId": 601, - "StatusId": 101, - "BoardName": "Resident Advisory Board", - "AppointDtTm": null, - "ExpireDtTm": null, - "Docket": null, - "FirstName": "Emmet", - "LastName": "Beatty", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 15, - "AssignmentId": 1391, - "PersonId": 1588, - "StatusId": 101, - "BoardName": "Resident Advisory Board", - "AppointDtTm": null, - "ExpireDtTm": null, - "Docket": null, - "FirstName": "Madge", - "LastName": "Turner", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 15, - "AssignmentId": 1393, - "PersonId": 1589, - "StatusId": 101, - "BoardName": "Resident Advisory Board", - "AppointDtTm": null, - "ExpireDtTm": null, - "Docket": null, - "FirstName": "Reanna", - "LastName": "Ziemann", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 15, - "AssignmentId": 1394, - "PersonId": 1562, - "StatusId": 101, - "BoardName": "Resident Advisory Board", - "AppointDtTm": null, - "ExpireDtTm": null, - "Docket": null, - "FirstName": "Asia", - "LastName": "Senger", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 16, - "AssignmentId": 1145, - "PersonId": 1316, - "StatusId": 101, - "BoardName": "Boston Cultural Council", - "AppointDtTm": "2016-11-21T00:00:00.000Z", - "ExpireDtTm": "2019-10-01T00:00:00.000Z", - "Docket": null, - "FirstName": "Dee", - "LastName": "Bruen", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 16, - "AssignmentId": 1151, - "PersonId": 1312, - "StatusId": 101, - "BoardName": "Boston Cultural Council", - "AppointDtTm": "2016-11-21T00:00:00.000Z", - "ExpireDtTm": "2019-10-01T00:00:00.000Z", - "Docket": "1583", - "FirstName": "Krystel", - "LastName": "Will", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 16, - "AssignmentId": 1155, - "PersonId": 1328, - "StatusId": 101, - "BoardName": "Boston Cultural Council", - "AppointDtTm": "2018-06-18T00:00:00.000Z", - "ExpireDtTm": "2020-10-01T00:00:00.000Z", - "Docket": "1027", - "FirstName": "Asha", - "LastName": "Rippin", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 16, - "AssignmentId": 1156, - "PersonId": 1329, - "StatusId": 101, - "BoardName": "Boston Cultural Council", - "AppointDtTm": "2018-06-18T00:00:00.000Z", - "ExpireDtTm": "2020-10-01T00:00:00.000Z", - "Docket": "1028", - "FirstName": "Electa", - "LastName": "Veum", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 16, - "AssignmentId": 1200, - "PersonId": 1380, - "StatusId": 101, - "BoardName": "Boston Cultural Council", - "AppointDtTm": "2015-03-18T00:00:00.000Z", - "ExpireDtTm": "2018-10-01T00:00:00.000Z", - "Docket": "0559", - "FirstName": "Lucile", - "LastName": "Schaden", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 16, - "AssignmentId": 1201, - "PersonId": 1381, - "StatusId": 101, - "BoardName": "Boston Cultural Council", - "AppointDtTm": "2015-03-17T00:00:00.000Z", - "ExpireDtTm": "2018-10-01T00:00:00.000Z", - "Docket": "0560", - "FirstName": "Raquel", - "LastName": "Ankunding", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 16, - "AssignmentId": 1281, - "PersonId": 1473, - "StatusId": 101, - "BoardName": "Boston Cultural Council", - "AppointDtTm": "2017-01-20T00:00:00.000Z", - "ExpireDtTm": "2019-10-01T00:00:00.000Z", - "Docket": "0284", - "FirstName": "Laurie", - "LastName": "Boehm", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 16, - "AssignmentId": 1282, - "PersonId": 1474, - "StatusId": 101, - "BoardName": "Boston Cultural Council", - "AppointDtTm": "2017-01-20T00:00:00.000Z", - "ExpireDtTm": "2019-10-01T00:00:00.000Z", - "Docket": "0285", - "FirstName": "Estella", - "LastName": "Oberbrunner", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 16, - "AssignmentId": 1283, - "PersonId": 1475, - "StatusId": 101, - "BoardName": "Boston Cultural Council", - "AppointDtTm": "2017-01-20T00:00:00.000Z", - "ExpireDtTm": "2019-10-01T00:00:00.000Z", - "Docket": "0286", - "FirstName": "Geovanny", - "LastName": "Schaden", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 16, - "AssignmentId": 1285, - "PersonId": 1478, - "StatusId": 101, - "BoardName": "Boston Cultural Council", - "AppointDtTm": "2017-03-23T00:00:00.000Z", - "ExpireDtTm": "2019-10-01T00:00:00.000Z", - "Docket": "0479", - "FirstName": "Irving", - "LastName": "Kulas", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 16, - "AssignmentId": 1286, - "PersonId": 1479, - "StatusId": 101, - "BoardName": "Boston Cultural Council", - "AppointDtTm": "2017-03-23T00:00:00.000Z", - "ExpireDtTm": "2019-10-01T00:00:00.000Z", - "Docket": "0480", - "FirstName": "Delmer", - "LastName": "Wehner", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 16, - "AssignmentId": 1344, - "PersonId": 1540, - "StatusId": 101, - "BoardName": "Boston Cultural Council", - "AppointDtTm": "2018-06-18T00:00:00.000Z", - "ExpireDtTm": "2019-10-01T00:00:00.000Z", - "Docket": "1025", - "FirstName": "Lavern", - "LastName": "Beer", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 16, - "AssignmentId": 1345, - "PersonId": 1541, - "StatusId": 601, - "BoardName": "Boston Cultural Council", - "AppointDtTm": "2018-06-18T00:00:00.000Z", - "ExpireDtTm": "2019-10-01T00:00:00.000Z", - "Docket": "1026", - "FirstName": "Deangelo", - "LastName": "Lind", - "StatusName": "active", - "Stipend": 0 - }, - { - "BoardID": 16, - "AssignmentId": 1351, - "PersonId": 1472, - "StatusId": 101, - "BoardName": "Boston Cultural Council", - "AppointDtTm": "2017-01-20T00:00:00.000Z", - "ExpireDtTm": "2019-10-01T00:00:00.000Z", - "Docket": null, - "FirstName": "Hector", - "LastName": "Mraz", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 16, - "AssignmentId": 1405, - "PersonId": 1606, - "StatusId": 101, - "BoardName": "Boston Cultural Council", - "AppointDtTm": "2018-08-14T00:00:00.000Z", - "ExpireDtTm": "2020-10-01T00:00:00.000Z", - "Docket": null, - "FirstName": "Lauryn", - "LastName": "Wintheiser", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 16, - "AssignmentId": 1406, - "PersonId": 1607, - "StatusId": 101, - "BoardName": "Boston Cultural Council", - "AppointDtTm": "2018-08-14T00:00:00.000Z", - "ExpireDtTm": "2020-10-01T00:00:00.000Z", - "Docket": "1250", - "FirstName": "Stephon", - "LastName": "Mills", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 16, - "AssignmentId": 1407, - "PersonId": 1608, - "StatusId": 101, - "BoardName": "Boston Cultural Council", - "AppointDtTm": "2018-08-14T00:00:00.000Z", - "ExpireDtTm": "2020-10-01T00:00:00.000Z", - "Docket": null, - "FirstName": "Virgie", - "LastName": "O'Hara", - "StatusName": "Active", - "Stipend": 0 - }, - { - "BoardID": 18, - "AssignmentId": 255, - "PersonId": 836, - "StatusId": 301, - "BoardName": "Neighborhood Jobs Trust", - "AppointDtTm": "2016-01-11T00:00:00.000Z", - "ExpireDtTm": "2018-01-12T00:00:00.000Z", - "Docket": null, - "FirstName": "Bernice", - "LastName": "Schamberger", - "StatusName": "Holdover", - "Stipend": 0 - }, - { - "BoardID": 18, - "AssignmentId": 256, - "PersonId": 1518, - "StatusId": 201, - "BoardName": "Neighborhood Jobs Trust", - "AppointDtTm": null, - "ExpireDtTm": null, - "Docket": null, - "FirstName": "Darrin", - "LastName": "Shields", - "StatusName": "Ex Officio", - "Stipend": 0 - }, - { - "BoardID": 18, - "AssignmentId": 437, - "PersonId": 724, - "StatusId": 301, - "BoardName": "Neighborhood Jobs Trust", - "AppointDtTm": "2016-01-11T00:00:00.000Z", - "ExpireDtTm": "2018-01-12T00:00:00.000Z", - "Docket": null, - "FirstName": "Holly", - "LastName": "O'Kon", - "StatusName": "Holdover", - "Stipend": 0 - } -] \ No newline at end of file diff --git a/services-js/commissions-app/fixtures/Boards.json b/services-js/commissions-app/fixtures/Boards.json deleted file mode 100644 index 0fddb31f1..000000000 --- a/services-js/commissions-app/fixtures/Boards.json +++ /dev/null @@ -1,372 +0,0 @@ -[ - { - "BoardID": 1, - "PolicyTypeId": null, - "BoardName": "Trustees of Charitable Donations for Inhabitants of Boston", - "Contact": "Raegan Morar", - "Legislation": "http://archives.lib.state.ma.us/actsResolves/1970/1970acts0368.pdf", - "Seats": 12, - "Alternates": 0, - "CouncilCofirm": false, - "Residency": true, - "Term": "3 years", - "Qualification": null, - "Schedule": "Once per month", - "Location": "City Hall-rm M-5", - "Stipend": 0, - "ClerkBoard": true, - "IsLive": true, - "Description": "Is an independent public charitable corporation that assists City of Boston residents with a one-time donation in various areas of need.", - "Email": "Joshuah80@boston.gov", - "DepartmentId": 124, - "AuthorityId": 100, - "Address1": "1 City Hall Square", - "Address2": null, - "City": "Boston", - "Zipcode": "02118", - "Phone": "594.741.2079 x976", - "FileName": "TrusteesofCharitableDonationsforInhabitantsofBoston.pdf", - "FilePath": "E:\\Intranet\\cityclerk\\commissions\\attachments\\", - "Quorum": 0, - "LinkPath": "https://www.boston.gov/departments/treasury/trustees-charitable-donations-inhabitants-boston", - "AddBy": "system", - "AddDtTm": "2013-09-05T16:22:44.577Z", - "ModBy": "trisha.finnigan@boston.go", - "ModDtTm": "2018-07-31T15:29:22.940Z", - "BoardId": null, - "ActiveCount": null - }, - { - "BoardID": 2, - "PolicyTypeId": 9, - "BoardName": "Boston School Committee Nominating Panel", - "Contact": "Alvera Champlin", - "Legislation": "http://archives.lib.state.ma.us/actsResolves/1991/1991acts0108.pdf", - "Seats": 13, - "Alternates": 0, - "CouncilCofirm": false, - "Residency": false, - "Term": "2 years", - "Qualification": "4 members appointed by the mayor, 1 person selected by following organizations:\r\n1 teacher selected by the BTU\r\n1 principal selected by Boston Association of School Administrators and Supervisors\r\n1 parent selected by Citywide Parents Council\r\n1 parent selected by Bilingual Education Citywide Parent Advisory Council\r\n1 parent selected by Boston Special Needs Parent Advisory Council\r\n1 parent selected by the Citywide Educational Coalition\r\n1 representative from the Boston Business Community\r\n1 president of a college or university selected by the Chancellor of Higher Education\r\n1 representative of the Commissioner of Education", - "Schedule": "a few times a year, from October to early December to interview for the school committee", - "Location": "room 608 or 241", - "Stipend": 0, - "ClerkBoard": true, - "IsLive": true, - "Description": "Nominates persons for consideration by the Mayor of Boston for appointment to the Boston School Committee.", - "Email": "Antwon96@boston.gov", - "DepartmentId": 108, - "AuthorityId": 102, - "Address1": null, - "Address2": null, - "City": null, - "Zipcode": "02118", - "Phone": "276.077.3839 x69248", - "FileName": "BostonSchoolCommitteeNominatingPanel.pdf", - "FilePath": "E:\\Intranet\\cityclerk\\commissions\\attachments\\", - "Quorum": 0, - "LinkPath": "https://www.boston.gov/departments/schools/Boston-School-Committee-Nominating-Panel", - "AddBy": "system", - "AddDtTm": "2013-09-05T16:22:44.577Z", - "ModBy": "trisha.finnigan@boston.go", - "ModDtTm": "2018-08-10T11:19:22.360Z", - "BoardId": 2, - "ActiveCount": 10 - }, - { - "BoardID": 4, - "PolicyTypeId": 9, - "BoardName": "Boston School Committee", - "Contact": "Antonetta Effertz", - "Legislation": "http://archives.lib.state.ma.us/actsResolves/1991/1991acts0108.pdf", - "Seats": 7, - "Alternates": 0, - "CouncilCofirm": false, - "Residency": true, - "Term": "4 years", - "Qualification": null, - "Schedule": "Wednesdays at 6 p.m, 2x/month", - "Location": "26 court St., Boston", - "Stipend": 7500, - "ClerkBoard": true, - "IsLive": true, - "Description": null, - "Email": "Ivory_Jaskolski@boston.gov", - "DepartmentId": 108, - "AuthorityId": 102, - "Address1": "26 Court Street", - "Address2": null, - "City": "Boston", - "Zipcode": "02108", - "Phone": "(874) 928-6528 x056", - "FileName": "BostonSchoolCommittee.pdf", - "FilePath": "E:\\Intranet\\cityclerk\\commissions\\attachments\\", - "Quorum": 0, - "LinkPath": "https://www.boston.gov/departments/schools/Boston-School-Committee", - "AddBy": "system", - "AddDtTm": "2013-09-05T16:22:44.577Z", - "ModBy": "trisha.finnigan@boston.go", - "ModDtTm": "2018-07-27T14:27:03.680Z", - "BoardId": 4, - "ActiveCount": 7 - }, - { - "BoardID": 7, - "PolicyTypeId": 8, - "BoardName": "Neighborhood Housing Trust Fund", - "Contact": "Jamey Kessler", - "Legislation": null, - "Seats": 7, - "Alternates": 0, - "CouncilCofirm": false, - "Residency": false, - "Term": "2 years", - "Qualification": "City of Boston Collector-Treasurer\r\nDesignate of Boston City Council\r\nRemaining 5 board members appointed by Mayor", - "Schedule": null, - "Location": "TBA", - "Stipend": 0, - "ClerkBoard": true, - "IsLive": true, - "Description": null, - "Email": "Isai.Bruen92@boston.gov", - "DepartmentId": 124, - "AuthorityId": 100, - "Address1": "26 Court Street", - "Address2": null, - "City": "Boston", - "Zipcode": "02108", - "Phone": "1-394-710-4387", - "FileName": "NeighborhoodHousingTrustFund.pdf", - "FilePath": "E:\\Intranet\\cityclerk\\commissions\\attachments\\", - "Quorum": 0, - "LinkPath": "https://www.boston.gov/departments/treasury/Neighborhood-Housing-Trust-Fund", - "AddBy": "system", - "AddDtTm": "2013-09-05T16:22:44.577Z", - "ModBy": "trisha.finnigan@boston.go", - "ModDtTm": "2018-09-20T15:02:11.007Z", - "BoardId": 7, - "ActiveCount": 4 - }, - { - "BoardID": 10, - "PolicyTypeId": 9, - "BoardName": "City of Boston School Trust Fund", - "Contact": "Evert Padberg", - "Legislation": null, - "Seats": 0, - "Alternates": 0, - "CouncilCofirm": false, - "Residency": false, - "Term": null, - "Qualification": "City of Boston School Committee", - "Schedule": null, - "Location": "TBA", - "Stipend": 0, - "ClerkBoard": false, - "IsLive": true, - "Description": null, - "Email": "Monique_Rice@boston.gov", - "DepartmentId": 108, - "AuthorityId": 102, - "Address1": null, - "Address2": null, - "City": "Boston", - "Zipcode": "02201", - "Phone": null, - "FileName": null, - "FilePath": null, - "Quorum": 0, - "LinkPath": "https://www.boston.gov/departments/schools/city-boston-school-trust-fund", - "AddBy": "system", - "AddDtTm": "2013-09-05T16:22:44.577Z", - "ModBy": "trisha.finnigan@boston.go", - "ModDtTm": "2018-08-10T09:14:41.090Z", - "BoardId": 10, - "ActiveCount": 7 - }, - { - "BoardID": 11, - "PolicyTypeId": null, - "BoardName": "George Robert White Fund", - "Contact": "Verona Brekke", - "Legislation": null, - "Seats": 5, - "Alternates": 0, - "CouncilCofirm": false, - "Residency": false, - "Term": null, - "Qualification": "Mayor\r\nCity Auditor\r\nPresident, Boston City Council\r\nChairman, Boston Chamber of Commerce\r\nPresident, Boston Bar Association", - "Schedule": null, - "Location": "TBA", - "Stipend": 0, - "ClerkBoard": false, - "IsLive": true, - "Description": "Through a bequest of George Robert White in 1922 funds “works of public utility” for residents of Boston, to hold and manage the facilities constructed by the Fund and oversee its investments.", - "Email": "Kaycee49@boston.gov", - "DepartmentId": 124, - "AuthorityId": 102, - "Address1": "1City Hall Square", - "Address2": "M36", - "City": "Boston", - "Zipcode": "02201", - "Phone": "1-076-154-5781 x6085", - "FileName": "GeorgeRobertWhiteFund.pdf", - "FilePath": "E:\\Intranet\\cityclerk\\commissions\\attachments\\", - "Quorum": 0, - "LinkPath": "https://www.boston.gov/departments/treasury/George-Robert-White-Fund", - "AddBy": "system", - "AddDtTm": "2013-09-05T16:22:44.577Z", - "ModBy": "trisha.finnigan@boston.go", - "ModDtTm": "2018-07-27T14:31:22.073Z", - "BoardId": 11, - "ActiveCount": 5 - }, - { - "BoardID": 13, - "PolicyTypeId": 10, - "BoardName": "Fund for Parks & Recreation in Boston", - "Contact": "Nakia Medhurst", - "Legislation": null, - "Seats": 3, - "Alternates": 0, - "CouncilCofirm": false, - "Residency": true, - "Term": "N/A", - "Qualification": "The Managing Committee members are the Commissioner of Parks and Recreation, ex officio (chairman); Collector-Treasurer, ex officio, and a member of the City Council or designee of the City Council.", - "Schedule": "quarterly", - "Location": "varies", - "Stipend": 0, - "ClerkBoard": false, - "IsLive": true, - "Description": "Assists in the maintenance and preservation of Boston parks that provide recreational facilities and programs.", - "Email": "Oceane_DAmore@boston.gov", - "DepartmentId": 118, - "AuthorityId": 102, - "Address1": "1010 Mass Ave", - "Address2": "3 rd Floor", - "City": "Boston", - "Zipcode": "02118", - "Phone": "108-305-5501 x16400", - "FileName": "FundforParksRecreationinBoston.pdf", - "FilePath": "E:\\Intranet\\cityclerk\\commissions\\attachments\\", - "Quorum": 0, - "LinkPath": "https://www.boston.gov/departments/parks-and-recreation/fund-parks-and-recreation-boston", - "AddBy": "system", - "AddDtTm": "2013-09-05T16:22:44.577Z", - "ModBy": "trisha.finnigan@boston.go", - "ModDtTm": "2018-07-27T14:30:43.633Z", - "BoardId": 13, - "ActiveCount": 2 - }, - { - "BoardID": 15, - "PolicyTypeId": 12, - "BoardName": "Resident Advisory Board", - "Contact": "Mr. Donny McGlynn", - "Legislation": "https://www.bostonhousing.org/BHA/media/PDF-Files/RAB-bylaws-incorporating-amendments-through-2-14.pdf", - "Seats": 30, - "Alternates": 17, - "CouncilCofirm": false, - "Residency": true, - "Term": "2 years", - "Qualification": "BHA resident", - "Schedule": "Meet once a month on the second Thursday of the month from 6 to 8 p.m. approximately", - "Location": "56 Chauncy St.", - "Stipend": 0, - "ClerkBoard": false, - "IsLive": true, - "Description": null, - "Email": "Jaeden.Lubowitz80@boston.gov", - "DepartmentId": 106, - "AuthorityId": 103, - "Address1": null, - "Address2": null, - "City": null, - "Zipcode": null, - "Phone": "873.958.2754 x0236", - "FileName": "ResidentAdvisoryBoard.pdf", - "FilePath": "E:\\intranet\\cityclerk\\commissions\\attachments\\", - "Quorum": 0, - "LinkPath": "https://www.boston.gov/departments/housing-authority/Resident-Advisory-Board", - "AddBy": "system", - "AddDtTm": "2013-09-05T16:22:44.577Z", - "ModBy": "trisha.finnigan@boston.go", - "ModDtTm": "2018-08-10T12:05:18.297Z", - "BoardId": 15, - "ActiveCount": 47 - }, - { - "BoardID": 16, - "PolicyTypeId": 14, - "BoardName": "Boston Cultural Council", - "Contact": "Tressie Murazik", - "Legislation": "http://www.amlegal.com/nxt/gateway.dll/Massachusetts/boston/chaptervadministration?\r\n\r\nf=templates$fn=default.htm$3.0$vid=amlegal:boston_ma$anc=JD_5-9", - "Seats": 0, - "Alternates": 0, - "CouncilCofirm": false, - "Residency": true, - "Term": "3 years", - "Qualification": "Term: Three years, maximum of 2 consecutive terms or a total of 6 years. Members must remain off the council for a 1-year interval before serving additional terms.", - "Schedule": "3-4 per year", - "Location": "City Hal", - "Stipend": 0, - "ClerkBoard": true, - "IsLive": true, - "Description": "Reviews applications during a series of fall meetings conducted to evaluate the overall quality of proposed programming and its potential benefit to a diverse audience in neighborhoods throughout Boston.", - "Email": "Guadalupe_Carter42@boston.gov", - "DepartmentId": 169, - "AuthorityId": 102, - "Address1": "One CIty Hall Plaza", - "Address2": "Room 802", - "City": "Boston", - "Zipcode": "02201", - "Phone": "1-997-769-4403", - "FileName": "BostonCulturalCouncil.docx", - "FilePath": "E:\\intranet\\cityclerk\\commissions\\attachments\\", - "Quorum": 0, - "LinkPath": "https://www.boston.gov/departments/arts-and-culture/Boston-Cultural-Council", - "AddBy": "system", - "AddDtTm": "2013-09-05T16:22:44.577Z", - "ModBy": "trisha.finnigan@boston.go", - "ModDtTm": "2018-07-27T14:09:00.947Z", - "BoardId": 16, - "ActiveCount": 16 - }, - { - "BoardID": 18, - "PolicyTypeId": 12, - "BoardName": "Neighborhood Jobs Trust", - "Contact": "Idell Schmeler", - "Legislation": null, - "Seats": 3, - "Alternates": 0, - "CouncilCofirm": false, - "Residency": true, - "Term": "2 years", - "Qualification": "City Councilor appointed by the Mayor\r\nDirector of Jobs and Community Services\r\nCity's Treasurer or their designee", - "Schedule": "quarterly", - "Location": "City Hall", - "Stipend": 0, - "ClerkBoard": true, - "IsLive": true, - "Description": null, - "Email": "Tony.Hane@boston.gov", - "DepartmentId": 112, - "AuthorityId": 102, - "Address1": "43 Hawkins Street", - "Address2": "2B", - "City": "Boston", - "Zipcode": "02114", - "Phone": "(806) 785-1001", - "FileName": "NeighborhoodJobsTrust.pdf", - "FilePath": "E:\\Intranet\\cityclerk\\commissions\\attachments\\", - "Quorum": 0, - "LinkPath": "https://www.boston.gov/departments/economic-development/Neighborhood-Jobs-Trust", - "AddBy": "system", - "AddDtTm": "2013-09-05T16:22:44.577Z", - "ModBy": "trisha.finnigan@boston.go", - "ModDtTm": "2018-07-27T14:49:19.520Z", - "BoardId": null, - "ActiveCount": null - } -] \ No newline at end of file diff --git a/services-js/commissions-app/fixtures/Departments.json b/services-js/commissions-app/fixtures/Departments.json deleted file mode 100644 index a7906db6a..000000000 --- a/services-js/commissions-app/fixtures/Departments.json +++ /dev/null @@ -1,162 +0,0 @@ -[ - { - "DepartmentId": 124, - "DepartmentName": "Treasury", - "Manager": "Kaci Kshlerin DDS", - "Title": "First Assistant Collector-Treasurer", - "Phone": null, - "Email": null, - "PSDeptId": 138000, - "ApptDtTm": "2017-11-13T00:00:00.000Z", - "ExpireDtTm": null, - "LinkPath": "https://www.boston.gov/departments/treasury", - "AddBy": "system", - "AddDtTm": "2013-09-05T16:22:15.190Z", - "ModBy": "Unknown", - "ModDtTm": "2017-11-14T09:30:14.917Z" - }, - { - "DepartmentId": 108, - "DepartmentName": "Boston Public Schools", - "Manager": "Hanna Hickle III", - "Title": "Interim Superintendent", - "Phone": "1-941-947-6803", - "Email": null, - "PSDeptId": null, - "ApptDtTm": null, - "ExpireDtTm": null, - "LinkPath": "https://www.boston.gov/departments/schools", - "AddBy": "system", - "AddDtTm": "2013-09-05T16:22:15.190Z", - "ModBy": "trishaf", - "ModDtTm": "2014-02-19T08:59:05.920Z" - }, - { - "DepartmentId": 108, - "DepartmentName": "Boston Public Schools", - "Manager": "Hanna Hickle III", - "Title": "Interim Superintendent", - "Phone": "1-941-947-6803", - "Email": null, - "PSDeptId": null, - "ApptDtTm": null, - "ExpireDtTm": null, - "LinkPath": "https://www.boston.gov/departments/schools", - "AddBy": "system", - "AddDtTm": "2013-09-05T16:22:15.190Z", - "ModBy": "trishaf", - "ModDtTm": "2014-02-19T08:59:05.920Z" - }, - { - "DepartmentId": 124, - "DepartmentName": "Treasury", - "Manager": "Kaci Kshlerin DDS", - "Title": "First Assistant Collector-Treasurer", - "Phone": null, - "Email": null, - "PSDeptId": 138000, - "ApptDtTm": "2017-11-13T00:00:00.000Z", - "ExpireDtTm": null, - "LinkPath": "https://www.boston.gov/departments/treasury", - "AddBy": "system", - "AddDtTm": "2013-09-05T16:22:15.190Z", - "ModBy": "Unknown", - "ModDtTm": "2017-11-14T09:30:14.917Z" - }, - { - "DepartmentId": 108, - "DepartmentName": "Boston Public Schools", - "Manager": "Hanna Hickle III", - "Title": "Interim Superintendent", - "Phone": "1-941-947-6803", - "Email": null, - "PSDeptId": null, - "ApptDtTm": null, - "ExpireDtTm": null, - "LinkPath": "https://www.boston.gov/departments/schools", - "AddBy": "system", - "AddDtTm": "2013-09-05T16:22:15.190Z", - "ModBy": "trishaf", - "ModDtTm": "2014-02-19T08:59:05.920Z" - }, - { - "DepartmentId": 124, - "DepartmentName": "Treasury", - "Manager": "Kaci Kshlerin DDS", - "Title": "First Assistant Collector-Treasurer", - "Phone": null, - "Email": null, - "PSDeptId": 138000, - "ApptDtTm": "2017-11-13T00:00:00.000Z", - "ExpireDtTm": null, - "LinkPath": "https://www.boston.gov/departments/treasury", - "AddBy": "system", - "AddDtTm": "2013-09-05T16:22:15.190Z", - "ModBy": "Unknown", - "ModDtTm": "2017-11-14T09:30:14.917Z" - }, - { - "DepartmentId": 118, - "DepartmentName": "Parks And Recreation", - "Manager": "Leora Senger", - "Title": "Commissioner", - "Phone": null, - "Email": null, - "PSDeptId": 300000, - "ApptDtTm": "2014-09-04T00:00:00.000Z", - "ExpireDtTm": null, - "LinkPath": "https://www.boston.gov/departments/parks-and-recreation", - "AddBy": "system", - "AddDtTm": "2013-09-05T16:22:15.190Z", - "ModBy": "Unknown", - "ModDtTm": "2014-09-10T09:33:59.537Z" - }, - { - "DepartmentId": 106, - "DepartmentName": "Housing Authority", - "Manager": "Jessica Bernhard V", - "Title": "Director", - "Phone": "317.814.9592 x21621", - "Email": null, - "PSDeptId": null, - "ApptDtTm": null, - "ExpireDtTm": null, - "LinkPath": "https://www.boston.gov/departments/housing-authority", - "AddBy": "system", - "AddDtTm": "2013-09-05T16:22:15.190Z", - "ModBy": "Unknown", - "ModDtTm": "2015-03-04T09:28:05.953Z" - }, - { - "DepartmentId": 169, - "DepartmentName": "Arts Commissioner", - "Manager": "Brad Bednar", - "Title": "Arts Commissioner", - "Phone": null, - "Email": null, - "PSDeptId": null, - "ApptDtTm": "2014-12-15T00:00:00.000Z", - "ExpireDtTm": null, - "LinkPath": null, - "AddBy": "Unknown", - "AddDtTm": "2015-02-05T15:33:33.203Z", - "ModBy": "trisha.finnigan@boston.go", - "ModDtTm": "2018-08-08T16:09:13.747Z" - }, - { - "DepartmentId": 112, - "DepartmentName": "Economic Development", - "Manager": "Darius Cassin", - "Title": "Chief", - "Phone": null, - "Email": null, - "PSDeptId": null, - "ApptDtTm": "2014-02-10T00:00:00.000Z", - "ExpireDtTm": null, - "LinkPath": "https://www.boston.gov/departments/economic-development", - "AddBy": "system", - "AddDtTm": "2013-09-05T16:22:15.190Z", - "ModBy": "051018", - "ModDtTm": "2014-02-20T15:25:14.387Z" - } -] \ No newline at end of file diff --git a/services-js/commissions-app/fixtures/PolicyTypes.json b/services-js/commissions-app/fixtures/PolicyTypes.json deleted file mode 100644 index c30e0d9ea..000000000 --- a/services-js/commissions-app/fixtures/PolicyTypes.json +++ /dev/null @@ -1,58 +0,0 @@ -[ - { - "PolicyTypeId": 8, - "PolicyType": "Administration and Finance", - "AddBy": "Unknown", - "AddDtTm": "2014-07-02T09:45:32.633Z", - "ModBy": null, - "ModDtTm": null - }, - { - "PolicyTypeId": 9, - "PolicyType": "Education", - "AddBy": "Unknown", - "AddDtTm": "2014-07-02T09:45:43.037Z", - "ModBy": "Unknown", - "ModDtTm": "2015-09-15T09:29:46.843Z" - }, - { - "PolicyTypeId": 10, - "PolicyType": "Energy,Environment and Open Space", - "AddBy": "Unknown", - "AddDtTm": "2014-07-02T09:45:48.863Z", - "ModBy": null, - "ModDtTm": null - }, - { - "PolicyTypeId": 11, - "PolicyType": "Health and Human Services", - "AddBy": "Unknown", - "AddDtTm": "2014-07-02T09:45:55.357Z", - "ModBy": null, - "ModDtTm": null - }, - { - "PolicyTypeId": 12, - "PolicyType": "Housing and Economic Development", - "AddBy": "Unknown", - "AddDtTm": "2014-07-02T09:46:03.613Z", - "ModBy": null, - "ModDtTm": null - }, - { - "PolicyTypeId": 13, - "PolicyType": "Transportation and Public Works", - "AddBy": "Unknown", - "AddDtTm": "2014-07-02T09:46:12.657Z", - "ModBy": null, - "ModDtTm": null - }, - { - "PolicyTypeId": 14, - "PolicyType": "Arts and Culture", - "AddBy": "Unknown", - "AddDtTm": "2014-07-02T09:46:17.423Z", - "ModBy": null, - "ModDtTm": null - } -] \ No newline at end of file diff --git a/services-js/commissions-app/next.config.js b/services-js/commissions-app/next.config.js deleted file mode 100644 index 8aa7f1b57..000000000 --- a/services-js/commissions-app/next.config.js +++ /dev/null @@ -1,6 +0,0 @@ -const path = require('path'); -const withPolyfill = require('@cityofboston/next-client-common/with-polyfill')(); - -module.exports = withPolyfill({ - distDir: path.join('..', 'build', '.next'), -}); diff --git a/services-js/commissions-app/package.json b/services-js/commissions-app/package.json deleted file mode 100644 index d9ee0c57c..000000000 --- a/services-js/commissions-app/package.json +++ /dev/null @@ -1,114 +0,0 @@ -{ - "name": "services-js.commissions-app", - "version": "0.0.0", - "description": "Boards and Commissions GraphQL server and webapp", - "private": true, - "license": "CC0-1.0", - "scripts": { - "prebuild": "rimraf build && yarn run generate-graphql-schema && yarn run generate-graphql-types", - "build": "concurrently \"yarn run build:server\" \"yarn run build:next\"", - "build:server": "tsc -p tsconfig.server.json", - "build:next": "next build src", - "dev": "yarn run prebuild && tsc-watch -p tsconfig.server.json --onSuccess \"yarn run start\"", - "storybook": "start-storybook -p 9001 -s static", - "watch-dependencies": "lerna run --parallel --scope commissions-app --include-filtered-dependencies watch", - "prepare": "yarn run build", - "prepare-deploy": "build-storybook -s static", - "start": "node build/server/start", - "pretest": "yarn run prebuild && tsc --noEmit", - "test": "jest", - "percy": "scripts/percy.js", - "generate-graphql-schema": "mkdir -p graphql && ts2gql src/server/graphql/schema.ts > graphql/schema.graphql", - "generate-graphql-types": "npx apollo codegen:generate --includes=\"src/client/**/*.ts\" --localSchemaFile=graphql/schema.graphql --target=typescript --outputFlat --no-addTypename src/client/graphql/queries.ts", - "generate-fixtures": "ts-node --project ./tsnode.config.json ./scripts/generate-fixtures.ts", - "generate-db-types": "ts-node --project ./tsnode.config.json ./scripts/generate-db-types.ts", - "codebuild-deploy": "npx -p ../../deploy-tools.tgz codebuild-service-deploy deploy/Dockerfile", - "postdeploy": "source /app/scripts/cacheBuster.sh commissions-app" - }, - "jest": { - "testPathIgnorePatterns": [ - "/build/", - "/node_modules/", - "/scripts/" - ] - }, - "resolutions": { - "graphql": "0.13.2" - }, - "dependencies": { - "@babel/runtime": "^7.6.0", - "@cityofboston/form-common": "^0.0.0", - "@cityofboston/hapi-common": "^0.0.0", - "@cityofboston/hapi-next": "^0.0.0", - "@cityofboston/mssql-common": "^0.0.0", - "@cityofboston/next-client-common": "^0.0.0", - "@cityofboston/percy-common": "^0.0.0", - "@cityofboston/srv-decrypt-env": "^0.0.0", - "@emotion/core": "^10.0.10", - "@types/yup": "^0.26.0", - "apollo-server-hapi": "^2.5.0", - "boom": "^7.2.0", - "core-js": "3.19.3", - "dataloader": "^1.4.0", - "dotenv": "^5.0.1", - "emotion": "^10.0.9", - "formik": "^1.4.2", - "graphql": "^14.3.0", - "handlebars": "^4.0.12", - "@hapi/hapi": ">=20.1.0", - "hapi-accept-language2": "^2.0.3", - "inert": "^5.1.0", - "isomorphic-fetch": "^2.2.1", - "mjml": "^4.3.1", - "mssql": "^4.3.6", - "next": "^9.0.6", - "node-cleanup": "^2.1.2", - "postmark": "^1.6.1", - "react": "16.8.5", - "react-dom": "16.8.5", - "rollbar": "^2.5.1", - "word-wrap": "^1.2.3", - "yup": "0.27.0" - }, - "devDependencies": { - "@babel/cli": "^7.6.0", - "@babel/core": "^7.6.0", - "@cityofboston/config-babel": "^0.0.0", - "@cityofboston/config-typescript": "^0.0.0", - "@cityofboston/graphql-typescript": "^0.0.0", - "@cityofboston/mssql-typescript": "^0.0.0", - "@cityofboston/react-fleet": "^0.0.0", - "@cityofboston/storybook-common": "^0.0.0", - "@percy/storybook": "^3.0.2", - "@storybook/addon-a11y": "^5.0.10", - "@storybook/addon-actions": "^5.0.10", - "@storybook/addon-knobs": "^5.0.10", - "@storybook/addon-storyshots": "^5.0.10", - "@storybook/addon-viewport": "^5.0.10", - "@storybook/addons": "^5.0.10", - "@storybook/react": "^5.0.10", - "@types/graphql": "^14.2.0", - "@types/isomorphic-fetch": "^0.0.34", - "@types/jest": "24.x.x", - "@types/mssql": "^4.0.10", - "@types/node": "^8.0.0", - "@types/react": "^16.8.5", - "@types/react-dom": "^16.8.4", - "@types/storybook__addon-actions": "^3.4.2", - "@types/storybook__addon-storyshots": "^3.4.8", - "@types/storybook__react": "^4.0.1", - "babel-core": "^7.0.0-0", - "concurrently": "^3.5.1", - "eslint-plugin-graphql": "^3.0.3", - "faker": "^4.1.0", - "jest": "^24.8.0", - "prettier": "^1.17.0", - "raw-loader": "^0.5.1", - "react-test-renderer": "16.8.5", - "rimraf": "^2.6.2", - "ts-node": "^6.0.5", - "ts2gql": "CityOfBoston/ts2gql#4f10fcce", - "tsc-watch": "^1.0.26", - "typescript": "^3.4.5" - } -} diff --git a/services-js/commissions-app/scripts/generate-db-types.ts b/services-js/commissions-app/scripts/generate-db-types.ts deleted file mode 100644 index d59c5966f..000000000 --- a/services-js/commissions-app/scripts/generate-db-types.ts +++ /dev/null @@ -1,44 +0,0 @@ -/* eslint no-console: 0 */ -import fs from 'fs'; -import dotenv from 'dotenv'; - -import { toTypeScript } from '@cityofboston/mssql-typescript'; - -dotenv.config(); - -(async function go() { - const username = process.env.COMMISSIONS_DB_USERNAME; - const password = process.env.COMMISSIONS_DB_PASSWORD; - const database = process.env.COMMISSIONS_DB_DATABASE; - const domain = process.env.COMMISSIONS_DB_DOMAIN; - const serverName = process.env.COMMISSIONS_DB_SERVER; - - if (!username) { - throw new Error('Must specify COMMISSIONS_DB_USERNAME'); - } - - if (!password) { - throw new Error('Must specify COMMISSIONS_DB_PASSWORD'); - } - - if (!database) { - throw new Error('Must specify COMMISSIONS_DB_DATABASE'); - } - - if (!serverName) { - throw new Error('Must specify COMMISSIONS_DB_SERVER'); - } - - const ts = await toTypeScript({ - user: username, - password, - database, - server: serverName, - domain, - }); - - fs.writeFileSync('./src/server/dao/CommissionsDb.d.ts', ts, 'utf-8'); -})().catch(err => { - console.error(err); - process.exit(-1); -}); diff --git a/services-js/commissions-app/scripts/generate-fixtures.ts b/services-js/commissions-app/scripts/generate-fixtures.ts deleted file mode 100644 index 23b1aaea8..000000000 --- a/services-js/commissions-app/scripts/generate-fixtures.ts +++ /dev/null @@ -1,169 +0,0 @@ -/* eslint no-console: 0 */ -import fs from 'fs'; -import path from 'path'; - -import dotenv from 'dotenv'; -import faker from 'faker'; -import { ConnectionPool } from 'mssql'; - -import { createConnectionPool } from '@cityofboston/mssql-common'; - -import CommissionsDao, { DbMember } from '../src/server/dao/CommissionsDao'; - -// Used to expose protected members for easier fixture-getting. -class CommissionsDaoInternal extends CommissionsDao { - constructor(pool: ConnectionPool) { - super(pool); - } - - fetchAuthorities(ids: number[]) { - return super.fetchAuthorities(ids); - } - - fetchDepartments(ids: number[]) { - return super.fetchDepartments(ids); - } -} - -dotenv.config(); - -const FIXTURE_DIR = `fixtures`; - -(async function go() { - const username = process.env.COMMISSIONS_DB_USERNAME; - const password = process.env.COMMISSIONS_DB_PASSWORD; - const database = process.env.COMMISSIONS_DB_DATABASE; - const domain = process.env.COMMISSIONS_DB_DOMAIN; - const serverName = process.env.COMMISSIONS_DB_SERVER; - - if (!username) { - throw new Error('Must specify COMMISSIONS_DB_USERNAME'); - } - - if (!password) { - throw new Error('Must specify COMMISSIONS_DB_PASSWORD'); - } - - if (!database) { - throw new Error('Must specify COMMISSIONS_DB_DATABASE'); - } - - if (!serverName) { - throw new Error('Must specify COMMISSIONS_DB_SERVER'); - } - - const pool = await createConnectionPool( - { - username, - password, - database, - domain, - server: serverName, - }, - err => { - console.error(err); - process.exit(-1); - } - ); - - const fixtureDir = path.resolve(FIXTURE_DIR); - - if (!fs.existsSync(fixtureDir)) { - fs.mkdirSync(fixtureDir); - } - - const commissionsDao = new CommissionsDaoInternal(pool); - const boards = (await commissionsDao.fetchBoards()).slice(0, 10); - - // All data checked in to GitHub needs to have names, emails, phone numbers, - // and personal address replaced with fake data. - boards.forEach(board => { - if (board.Contact) { - board.Contact = faker.name.findName(); - } - if (board.Email) { - board.Email = faker.internet.email(null, null, 'boston.gov'); - } - if (board.Phone) { - board.Phone = faker.phone.phoneNumber(); - } - }); - - fs.writeFileSync( - path.join(FIXTURE_DIR, 'Boards.json'), - JSON.stringify(boards, null, 2), - 'utf-8' - ); - - const authorities = await commissionsDao.fetchAuthorities( - boards.map(({ AuthorityId }) => AuthorityId!).filter(id => id !== null) - ); - - fs.writeFileSync( - path.join(FIXTURE_DIR, 'Authorities.json'), - JSON.stringify(authorities, null, 2), - 'utf-8' - ); - - const departments = await commissionsDao.fetchDepartments( - boards.map(({ DepartmentId }) => DepartmentId!).filter(id => id !== null) - ); - - departments.forEach(department => { - if (!department) { - return; - } - - if (department.Manager) { - department.Manager = faker.name.findName(); - } - if (department.Email) { - department.Email = faker.internet.email(null, null, 'boston.gov'); - } - if (department.Phone) { - department.Phone = faker.phone.phoneNumber(); - } - }); - - fs.writeFileSync( - path.join(FIXTURE_DIR, 'Departments.json'), - JSON.stringify(departments, null, 2), - 'utf-8' - ); - - const policyTypes = await commissionsDao.fetchPolicyTypes(); - - fs.writeFileSync( - path.join(FIXTURE_DIR, 'PolicyTypes.json'), - JSON.stringify(policyTypes, null, 2), - 'utf-8' - ); - - let members: DbMember[] = []; - members = members.concat.apply( - members, - await Promise.all( - boards.map(({ BoardID }) => commissionsDao.fetchBoardMembers(BoardID)) - ) - ); - - members.forEach(member => { - if (member.FirstName) { - member.FirstName = faker.name.firstName(); - } - if (member.LastName) { - member.LastName = faker.name.lastName(); - } - }); - - fs.writeFileSync( - path.join(FIXTURE_DIR, 'BoardMembers.json'), - JSON.stringify(members, null, 2), - 'utf-8' - ); - - await pool.close(); -})().catch(err => { - console.error(err); - process.exit(-1); -}); diff --git a/services-js/commissions-app/scripts/percy.js b/services-js/commissions-app/scripts/percy.js deleted file mode 100755 index 7ee9fb230..000000000 --- a/services-js/commissions-app/scripts/percy.js +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env node - -const { travisSnapshot } = require('@cityofboston/percy-common'); - -travisSnapshot({ - referenceBranch: 'develop', - project: 'CityOfBoston/Boards-and-Commissions', - packageName: 'commissions-app', -}); diff --git a/services-js/commissions-app/src/client/ApplicantInformationSection.tsx b/services-js/commissions-app/src/client/ApplicantInformationSection.tsx deleted file mode 100644 index 3075d86c8..000000000 --- a/services-js/commissions-app/src/client/ApplicantInformationSection.tsx +++ /dev/null @@ -1,177 +0,0 @@ -import React from 'react'; -import { css } from 'emotion'; - -import { MEDIA_MEDIUM, SectionHeader } from '@cityofboston/react-fleet'; - -import { FieldProps } from './ApplicationForm'; - -/** - * Fleet grid classes behave as block elements at narrow viewports, which - * prevents fields from being positioned in the same row when on mobile. - * - * If flexbox support is not available, fields will be treated as block elements. - */ - -const COLUMN_GAP = 0.75; - -const COMMON_GROUP_STYLING = ` - display: flex; - - > div:not(:last-of-type) { - margin-right: ${COLUMN_GAP}rem; - - ${MEDIA_MEDIUM} { - margin-right: ${COLUMN_GAP * 2}rem; - } - } -`; - -const NAME_STREET_STYLING = css(` - ${COMMON_GROUP_STYLING} - - > div { - &:first-of-type { - flex-grow: 1; - } - - &:last-of-type { - flex-basis: 4rem; - } - } -`); - -const STATE_ZIP_STYLING = css(` - ${COMMON_GROUP_STYLING} - - > div { - &:first-of-type { - flex-basis: 8rem; - white-space: nowrap; - } - - &:last-of-type { - flex-grow: 1; - } - } -`); - -const SPLIT_STYLING = css(` - ${MEDIA_MEDIUM} { - display: flex; - - > div { - flex-basis: 50%; - - &:first-of-type { - margin-right: ${COLUMN_GAP * 2}rem; - } - } - } -`); - -interface Props { - // Slight hackery to avoid passing all the Formik props this deeply. We prefer - // this over Formik’s built-in Field component because that requires context - // to work. - makeField: (props: FieldProps) => JSX.Element; -} - -export default function ApplicantInformationSection({ - makeField, -}: Props): JSX.Element { - return ( -
        - - -
        -
        - {makeField({ - label: 'First Name', - name: 'firstName', - placeholder: 'First Name', - required: true, - maxLength: 25, - })} - - {makeField({ label: 'Initial', name: 'middleName', maxLength: 50 })} -
        - - {makeField({ - label: 'Last Name', - name: 'lastName', - placeholder: 'Last Name', - required: true, - maxLength: 50, - })} -
        - -
        - {makeField({ - label: 'Street Address', - name: 'streetAddress', - placeholder: 'Street Address', - required: true, - maxLength: 50, - })} - - {makeField({ - label: 'Unit', - name: 'unit', - placeholder: 'Unit', - maxLength: 50, - })} -
        - -
        - {makeField({ - label: 'City', - name: 'city', - placeholder: 'City', - required: true, - maxLength: 50, - })} - -
        - {makeField({ - label: 'State', - name: 'state', - placeholder: 'State', - required: true, - maxLength: 50, - })} - - {makeField({ - label: 'Zip', - name: 'zip', - placeholder: 'Zip Code', - required: true, - maxLength: 5, - })} -
        -
        - - {makeField({ - label: 'Phone', - name: 'phone', - placeholder: '(XXX) XXX-XXXX', - maxLength: 50, - })} - - {makeField({ - label: 'Email', - name: 'email', - placeholder: 'Email', - required: true, - maxLength: 50, - })} - - {makeField({ - label: 'Confirm Email', - name: 'confirmEmail', - placeholder: 'Confirm Email', - required: true, - maxLength: 50, - })} -
        - ); -} diff --git a/services-js/commissions-app/src/client/ApplicationForm.stories.tsx b/services-js/commissions-app/src/client/ApplicationForm.stories.tsx deleted file mode 100644 index efb1e19dd..000000000 --- a/services-js/commissions-app/src/client/ApplicationForm.stories.tsx +++ /dev/null @@ -1,108 +0,0 @@ -import React from 'react'; -import { storiesOf } from '@storybook/react'; -import ApplicationForm, { Props } from './ApplicationForm'; -import { action } from '@storybook/addon-actions'; - -const COMMISSIONS = [ - { - name: 'Aberdeen Architectural Conservation District Commission', - id: 0, - openSeats: 2, - homepageUrl: 'http://', - }, - { - name: - 'Back Bay West/Bay State Road Architectural Conservation District Commission', - id: 1, - openSeats: 1, - homepageUrl: 'http://', - }, - { - name: 'Living Wage Advisory Committee', - id: 2, - openSeats: 1, - homepageUrl: 'http://', - }, - { - name: 'Licensing Board for the City of Boston', - id: 3, - openSeats: 0, - homepageUrl: 'http://', - }, -]; - -const DEFAULT_PROPS: Props = { - commissions: COMMISSIONS, - values: { - firstName: '', - middleName: '', - lastName: '', - streetAddress: '', - unit: '', - city: '', - state: '', - zip: '', - phone: '', - email: '', - confirmEmail: '', - commissionIds: [], - degreeAttained: '', - educationalInstitution: '', - otherInformation: '', - // coverLetter: null, - // resume: null, - }, - errors: {}, - touched: {}, - handleBlur: action('handleBlur'), - handleChange: action('handleChange'), - handleSubmit: action('handleSubmit'), - setFieldValue: action('setFieldValue'), - isSubmitting: false, - isValid: false, - submissionError: false, - clearSubmissionError: action('clearSubmissionError'), -}; - -storiesOf('ApplicationForm', module) - .addDecorator(story =>
        {story()}
        ) - .add('blank', () => ) - .add('errors', () => ( - - )) - .add('filled in', () => ( - - )) - .add('submitting', () => ) - .add('submission error', () => ( - - )); diff --git a/services-js/commissions-app/src/client/ApplicationForm.tsx b/services-js/commissions-app/src/client/ApplicationForm.tsx deleted file mode 100644 index 6e5e95338..000000000 --- a/services-js/commissions-app/src/client/ApplicationForm.tsx +++ /dev/null @@ -1,233 +0,0 @@ -import React from 'react'; -import { FormikProps } from 'formik'; -import { css } from 'emotion'; - -import { ApplyFormValues } from '../lib/validationSchema'; - -import { Commission } from './graphql/fetch-commissions'; - -import { - // FileInput, - SectionHeader, - Textarea, - TextInput, - StatusModal, -} from '@cityofboston/react-fleet'; - -import ApplicantInformationSection from './ApplicantInformationSection'; -import CommissionsListSection from './CommissionsListSection'; -import { PARAGRAPH_STYLING } from './common/style-common'; - -type RequiredFormikProps = Pick< - FormikProps, - | 'values' - | 'errors' - | 'touched' - | 'handleBlur' - | 'handleChange' - | 'handleSubmit' - | 'setFieldValue' - | 'isSubmitting' - | 'isValid' ->; - -export interface Props extends RequiredFormikProps { - commissions: Commission[]; - submissionError?: boolean; - clearSubmissionError: () => void; -} - -export interface FieldProps { - name: string; - label: string; - placeholder?: string; - required?: boolean; - maxLength?: number; -} - -// todo: https://github.com/CityOfBoston/digital/pull/97/files#r224776915 - -const FORM_STYLING = css({ - WebkitTextSizeAdjust: - '100%' /* Prevent font scaling in OSX landscape while allowing user zoom */, - 'input, textarea': { - borderRadius: 0, - }, -}); - -export default function ApplicationForm(props: Props): JSX.Element { - const { - values, - errors, - touched, - handleBlur, - handleChange, - handleSubmit, - setFieldValue, - isSubmitting, - isValid, - submissionError, - clearSubmissionError, - } = props; - - const makeField = ({ - name, - label, - placeholder, - required, - maxLength, - }: FieldProps) => ( - - ); - - const { firstName, lastName } = props.values; - const defaultSubject = 'Boards and Commissions application submission'; - let mailSubject = - firstName && firstName.length > 0 && lastName && lastName.length > 0 - ? `${firstName} ${lastName} ${defaultSubject}` - : `${defaultSubject}`; - let mailtoStr = `mailto:boardsandcomissions@boston.gov?subject=${mailSubject}`; - - return ( -
        -

        - Boards and Commissions Application Form -

        - -

        - Please note that many of these Boards and Commissions require City of - Boston residency as well as specific qualifications for members. Please - familiarize yourself with each board or commissions's enabling - legislation. If you have any questions, email{' '} - - boardsandcommissions@boston.gov - - . -

        - - - -
        - -
        - - - {makeField({ - label: 'Degree Attained', - name: 'degreeAttained', - placeholder: 'Degree Attained', - maxLength: 100, - })} - - {makeField({ - label: 'Educational Institution', - name: 'educationalInstitution', - placeholder: 'Educational Institution', - maxLength: 100, - })} - -