From cd6400e018e3b2361ca74c4f677783b55e5797e4 Mon Sep 17 00:00:00 2001 From: Phillip Kelly Date: Mon, 21 Oct 2024 11:50:53 -0400 Subject: [PATCH 01/11] DIG-5136: Preferred/Chosen Name and Gender Identity for COB Employees --- services-js/access-boston/CHANGELOG.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 services-js/access-boston/CHANGELOG.md diff --git a/services-js/access-boston/CHANGELOG.md b/services-js/access-boston/CHANGELOG.md new file mode 100644 index 000000000..c1a7e6ad2 --- /dev/null +++ b/services-js/access-boston/CHANGELOG.md @@ -0,0 +1,18 @@ +# Changelog + +## access-boston/v2024.5 (2024-10-11) + +### Features + +- Create new workflow for `Preferred/Chosen Name and Gender Identity` for COB Employees + +### Core +- + +### Doc +- + +### Misc +- + +--- From 3ce465a35e18ab17a34a81b91ff931b26801d993 Mon Sep 17 00:00:00 2001 From: "Phillip B. Kelly" Date: Fri, 1 Nov 2024 14:06:10 -0400 Subject: [PATCH 02/11] DIG-5226: Preferred/Chosen Name Frontend to Backend Workflow (#1027) * DIG-5226: Preferred/Chosen Name front end to back end workflow * DIG-5226: Basic App Setup * DIG-5226: Basic App Setup - successView * DIG-5226: Basic App Setup - new QuestionComponent * DIG-5226: Basic App Setup - Setup all views + buttons * DIG-5226: Basic App Setup - Storybook stories Added --- services-js/access-boston/CHANGELOG.md | 2 +- services-js/access-boston/fixtures/apps.yaml | 3 + .../common/AccessBostonHeader.stories.tsx | 1 + .../src/client/common/AppWrapper.stories.tsx | 1 + .../client/graphql/fetch-account-and-apps.ts | 1 + .../src/client/graphql/fetch-account.ts | 1 + .../src/client/graphql/queries.ts | 2 + .../components/QuestionComponent.tsx | 148 +++++++++++++++ .../components/section.tsx | 53 ++++++ .../client/preferred-chosen-name/state/app.ts | 52 ++++++ .../preferred-chosen-name/styling/index.tsx | 120 ++++++++++++ .../src/client/preferred-chosen-name/types.ts | 39 ++++ .../views/Index.stories.tsx | 56 ++++++ .../preferred-chosen-name/views/Index.tsx | 126 +++++++++++++ .../views/successView.tsx | 53 ++++++ .../preferred-chosen-name/views/views.tsx | 175 ++++++++++++++++++ .../views/welcomView.tsx | 99 ++++++++++ .../storage/PreferredChosenNameRequest.ts | 25 +++ services-js/access-boston/src/lib/apps.json | 4 + .../src/pages/preferred-chosen-name.tsx | 46 +++++ .../src/server/graphql/schema.ts | 4 + .../src/server/services/SamlAuthFake.ts | 2 + .../stories/ChangePasswordPage.stories.tsx | 1 + .../stories/ForgotPasswordPage.stories.tsx | 1 + .../stories/GroupManagementPage.stories.tsx | 1 + .../src/stories/IndexPage.stories.tsx | 1 + .../src/stories/RegisterMfaPage.stories.tsx | 1 + .../src/stories/RegisterPage.stories.tsx | 2 + 28 files changed, 1019 insertions(+), 1 deletion(-) create mode 100644 services-js/access-boston/src/client/preferred-chosen-name/components/QuestionComponent.tsx create mode 100644 services-js/access-boston/src/client/preferred-chosen-name/components/section.tsx create mode 100644 services-js/access-boston/src/client/preferred-chosen-name/state/app.ts create mode 100644 services-js/access-boston/src/client/preferred-chosen-name/styling/index.tsx create mode 100644 services-js/access-boston/src/client/preferred-chosen-name/types.ts create mode 100644 services-js/access-boston/src/client/preferred-chosen-name/views/Index.stories.tsx create mode 100644 services-js/access-boston/src/client/preferred-chosen-name/views/Index.tsx create mode 100644 services-js/access-boston/src/client/preferred-chosen-name/views/successView.tsx create mode 100644 services-js/access-boston/src/client/preferred-chosen-name/views/views.tsx create mode 100644 services-js/access-boston/src/client/preferred-chosen-name/views/welcomView.tsx create mode 100644 services-js/access-boston/src/client/storage/PreferredChosenNameRequest.ts create mode 100644 services-js/access-boston/src/pages/preferred-chosen-name.tsx diff --git a/services-js/access-boston/CHANGELOG.md b/services-js/access-boston/CHANGELOG.md index c1a7e6ad2..8a5aa4113 100644 --- a/services-js/access-boston/CHANGELOG.md +++ b/services-js/access-boston/CHANGELOG.md @@ -7,7 +7,7 @@ - Create new workflow for `Preferred/Chosen Name and Gender Identity` for COB Employees ### Core -- +- DIG-5226: Preferred/Chosen Name front end to back end workflow ### Doc - diff --git a/services-js/access-boston/fixtures/apps.yaml b/services-js/access-boston/fixtures/apps.yaml index 0acbfe5aa..f8fcafacc 100644 --- a/services-js/access-boston/fixtures/apps.yaml +++ b/services-js/access-boston/fixtures/apps.yaml @@ -137,6 +137,9 @@ categories: - title: Manage My Device(s) url: https://desktop.pingone.com/boston/Selection?cmd=devices mfa_device_required: true + - title: Add a preferred/chosen name + url: /preferred-chosen-name + # target: _blank - title: Support Tools apps: diff --git a/services-js/access-boston/src/client/common/AccessBostonHeader.stories.tsx b/services-js/access-boston/src/client/common/AccessBostonHeader.stories.tsx index 844785237..2ee33a423 100644 --- a/services-js/access-boston/src/client/common/AccessBostonHeader.stories.tsx +++ b/services-js/access-boston/src/client/common/AccessBostonHeader.stories.tsx @@ -15,6 +15,7 @@ const ACCOUNT: Account = { mfaRequiredDate: null, groups: [''], email: '', + cobAgency: 'CH', }; storiesOf('AccessBostonHeader', module).add('default', () => ( diff --git a/services-js/access-boston/src/client/common/AppWrapper.stories.tsx b/services-js/access-boston/src/client/common/AppWrapper.stories.tsx index 91aefef03..a0905c0e6 100644 --- a/services-js/access-boston/src/client/common/AppWrapper.stories.tsx +++ b/services-js/access-boston/src/client/common/AppWrapper.stories.tsx @@ -17,6 +17,7 @@ const ACCOUNT: Account = { mfaRequiredDate: null, groups: [''], email: '', + cobAgency: 'CH', }; storiesOf('Common/AppWrapper', module).add('default', () => ( 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 index f151c51e0..dfdc0a9ae 100644 --- 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 @@ -22,6 +22,7 @@ const QUERY = gql` mfaRequiredDate groups email + cobAgency } notice { diff --git a/services-js/access-boston/src/client/graphql/fetch-account.ts b/services-js/access-boston/src/client/graphql/fetch-account.ts index b3221f26b..fa9f0f367 100644 --- a/services-js/access-boston/src/client/graphql/fetch-account.ts +++ b/services-js/access-boston/src/client/graphql/fetch-account.ts @@ -16,6 +16,7 @@ const QUERY = gql` mfaRequiredDate groups email + cobAgency } } `; diff --git a/services-js/access-boston/src/client/graphql/queries.ts b/services-js/access-boston/src/client/graphql/queries.ts index a7a7cd36c..a3f60101d 100644 --- a/services-js/access-boston/src/client/graphql/queries.ts +++ b/services-js/access-boston/src/client/graphql/queries.ts @@ -68,6 +68,7 @@ export interface FetchAccountAndApps_account { mfaRequiredDate: string | null; groups: string[] | null; email: string; + cobAgency: string | null; } export interface FetchAccountAndApps_notice { @@ -121,6 +122,7 @@ export interface FetchAccount_account { mfaRequiredDate: string | null; groups: string[] | null; email: string; + cobAgency: string | null; } export interface FetchAccount { diff --git a/services-js/access-boston/src/client/preferred-chosen-name/components/QuestionComponent.tsx b/services-js/access-boston/src/client/preferred-chosen-name/components/QuestionComponent.tsx new file mode 100644 index 000000000..1b42fdabd --- /dev/null +++ b/services-js/access-boston/src/client/preferred-chosen-name/components/QuestionComponent.tsx @@ -0,0 +1,148 @@ +/** @jsx jsx */ + +import { css, jsx } from '@emotion/core'; + +import { MouseEvent, ReactNode } from 'react'; +import { MEDIA_SMALL /*, MEDIA_LARGE*/ } from '@cityofboston/react-fleet'; + +interface Props { + children: ReactNode; + allowProceed?: boolean; + quitBtn?: boolean; + handleProceed?: (ev: MouseEvent) => void; + handleStepBack?: (ev: MouseEvent) => void; + handleReset?: (ev: MouseEvent) => void; + handleQuit?: (ev: MouseEvent) => void; + nextButtonText?: string; + prevBtnText?: string; + quitBtnText?: string; +} + +/** + * 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, + nextButtonText, + prevBtnText, + allowProceed, + handleProceed, + handleStepBack, + handleQuit, + quitBtn, + quitBtnText, + } = props; + + return ( +
+ {children} + +
+ {handleStepBack && ( + + )} + + {handleProceed && ( + + )} + + {quitBtn && handleQuit && ( +
+ +
+ )} +
+
+ ); +} + +const CONTAINER_STYLING = css({ + display: 'flex', + flexDirection: 'column', + lineHeight: '1.5rem', + margin: 'auto', + width: '100%', + + p: { + lineHeight: '2rem', + }, +}); + +const BUTTON_CONTAINER_STYLING = css({ + display: 'flex', + justifyContent: 'flex-end', + gap: '8px 8px', + width: '100%', + marginLeft: 'auto', + marginRight: 'auto', + padding: '0 60px 0', + marginBottom: '4rem', + position: 'relative', + + textAlign: 'center', + + [MEDIA_SMALL]: { + textAlign: 'left', + + '> div': { + display: 'flex', + + '&.ta-r > button': { + marginLeft: 'auto', + }, + }, + + '.lnk': { + paddingLeft: 0, + }, + }, + + '.btn': { + fontSize: '16px', + fontFamily: ' Montserrat', + }, + + '.btn-alt': { + color: '#005EA2', + background: 'white', + borderRadius: '4px', + border: '2px solid #005EA2', + }, + + '.successView__Btn-wrapper': { + width: '100%', + display: 'flex', + justifyContent: 'center', + + '.btn': { + width: '181px', + height: '44px', + padding: '12px 20px', + justifyContent: 'center', + alignItems: 'center', + gap: '8px', + }, + }, +}); diff --git a/services-js/access-boston/src/client/preferred-chosen-name/components/section.tsx b/services-js/access-boston/src/client/preferred-chosen-name/components/section.tsx new file mode 100644 index 000000000..2519afcb8 --- /dev/null +++ b/services-js/access-boston/src/client/preferred-chosen-name/components/section.tsx @@ -0,0 +1,53 @@ +/** @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/preferred-chosen-name/state/app.ts b/services-js/access-boston/src/client/preferred-chosen-name/state/app.ts new file mode 100644 index 000000000..017adb554 --- /dev/null +++ b/services-js/access-boston/src/client/preferred-chosen-name/state/app.ts @@ -0,0 +1,52 @@ +/* eslint no-console: 0 */ + +import { + View, + CommonAttributes, + PreferredChosenNameInformation, +} from '../types'; +import { getViews } from '../../storage/PreferredChosenNameRequest'; + +export const AppTitle: string = 'Preferred / Chosen Name'; +export type ActionTypes = 'APP/CHANGE_VIEW' | 'APP/RESET_STATE'; + +interface Action { + type: ActionTypes; + view: View; + payload: CommonAttributes; + altWorkflow: boolean; +} + +export const initialState = new PreferredChosenNameInformation(); +// export const completedStates = { +// welcome: false, +// enterName: false, +// approval: false, +// success: false, +// }; + +export const newInitState = { + ...initialState, + // ...completedStates, +}; + +export const reducer = (state: any, action: Partial) => { + 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; + default: + return state; + } +}; diff --git a/services-js/access-boston/src/client/preferred-chosen-name/styling/index.tsx b/services-js/access-boston/src/client/preferred-chosen-name/styling/index.tsx new file mode 100644 index 000000000..d5bcace9b --- /dev/null +++ b/services-js/access-boston/src/client/preferred-chosen-name/styling/index.tsx @@ -0,0 +1,120 @@ +/** @jsx jsx */ + +import { css } from '@emotion/core'; +import { CHARLES_BLUE, MEDIA_SMALL_MAX } from '@cityofboston/react-fleet'; + +export const SECTION_STYLING = css({ + color: 'black', +}); + +export const SECTIONHEADER_STYLING = css({ + marginBottom: '3em', +}); + +export const SUBHEADER_STYLING = css({ + color: 'black', + fontSize: '20px', + fontWeight: 'bold', + fontFamily: 'Montserrat', + textTransform: 'uppercase', +}); + +export const INSTRUCTIONS_STYLING = css({ + fontSize: '15px', + fontFamily: 'Lora', + color: 'black', + marginBottom: '1.5em', +}); + +export const TEXTINPUT_STYLING = css({ + color: 'red', + '.txt label span.t--req': { + color: '#fb4d42', + marginLeft: '1em', + }, +}); + +// ------------------------------------- // +// Preferred Name Styling + +export const PREFERRED_NAME_STYLING = css({ + display: 'plex', + maxWidth: '694px', + margin: 'auto', + + color: `${CHARLES_BLUE}`, + fontFamily: 'Lora', + fontSize: '22px', + fontStyle: 'normal', + fontWeight: 'normal', + lineHeight: '150%', + + [MEDIA_SMALL_MAX]: { + fontSize: '16px', + }, + + 'h2, h3': { + fontFamily: 'Montserrat', + fontSize: '32px', + fontStyle: 'normal', + fontWeight: 'bold', + lineHeight: 'normal', + textTransform: 'uppercase', + padding: '10px', + margin: '0 0 27px 0', + }, + + '.BorderedAppWrapper': { + display: 'plex', + + border: '1px solid #A9AEB1', + borderRadius: '4px', + + '.btn': { + borderRadius: '4px', + }, + }, + + '.AppInnerContainer': { + display: 'plex', + + '.headerBlock': { + display: 'plex', + alignContent: 'center', + padding: '30px 10px 30px 49px', + background: '#F0F0F0', + borderBottom: '1px solid #A9AEB1', + + [MEDIA_SMALL_MAX]: { + paddingLeft: '26px ', + fontSize: '12px', + }, + + h3: { + margin: 0, + padding: 0, + + [MEDIA_SMALL_MAX]: { + fontSize: '16px', + }, + }, + }, + + '.row': { + padding: '60px', + paddingBottom: '1rem', + + [MEDIA_SMALL_MAX]: { + padding: '24px', + }, + }, + + '.bodyText': { + lineHeight: '2rem', + }, + }, + + '.bodyTextLabel': { + fontWeight: 'bold', + }, +}); diff --git a/services-js/access-boston/src/client/preferred-chosen-name/types.ts b/services-js/access-boston/src/client/preferred-chosen-name/types.ts new file mode 100644 index 000000000..405c23750 --- /dev/null +++ b/services-js/access-boston/src/client/preferred-chosen-name/types.ts @@ -0,0 +1,39 @@ +export type PreferredChosenNameStep = + | 'welcome' + | 'enterName' + | 'approval' + | 'success'; + +export type View = + | 'welcomeView' + | 'enterNameView' + | 'approvalView' + | 'successView'; + +export type Action = '' | 'new'; + +export interface CommonAttributes { + step: number | null; + view: number; + + employeeId: string; + employeeType: string; + fname: string; + lname: string; + chosenFirstName: string; + chosenLastName: string; + email: string; +} + +export class PreferredChosenNameInformation implements CommonAttributes { + step: number | null = null; + view: number = 0; + + employeeId: string = ''; + employeeType: string = ''; + fname: string = ''; + lname: string = ''; + chosenFirstName: string = ''; + chosenLastName: string = ''; + email: string = ''; +} diff --git a/services-js/access-boston/src/client/preferred-chosen-name/views/Index.stories.tsx b/services-js/access-boston/src/client/preferred-chosen-name/views/Index.stories.tsx new file mode 100644 index 000000000..f87b196b1 --- /dev/null +++ b/services-js/access-boston/src/client/preferred-chosen-name/views/Index.stories.tsx @@ -0,0 +1,56 @@ +import React from 'react'; +import { storiesOf } from '@storybook/react'; + +import PageWrapper from '../../PageWrapper'; + +import { AppTitle } from '../state/app'; + +import WelcomeView, { EnterNameView, ApprovalView, successView } from './views'; +import SuccessView from './successView'; + +const viewAccountObj = { + cobAgency: 'CH', + firstName: 'Felipe', + lastName: 'Rivera', + email: 'felipe.rivera@boston.gov', +}; + +storiesOf('Preferred-Name', module) + .add('Welcome', () => ( + + {}} + appTitle={AppTitle} + account={viewAccountObj} + /> + + )) + .add('Enter Names', () => ( + + {}} + handleStepBack={() => {}} + appTitle={AppTitle} + account={viewAccountObj} + /> + + )) + .add('Approval', () => ( + + {}} + handleStepBack={() => {}} + appTitle={AppTitle} + account={viewAccountObj} + /> + + )) + .add('Success', () => ( + + {}} + appTitle={AppTitle} + account={viewAccountObj} + /> + + )); diff --git a/services-js/access-boston/src/client/preferred-chosen-name/views/Index.tsx b/services-js/access-boston/src/client/preferred-chosen-name/views/Index.tsx new file mode 100644 index 000000000..5885b7a86 --- /dev/null +++ b/services-js/access-boston/src/client/preferred-chosen-name/views/Index.tsx @@ -0,0 +1,126 @@ +/** @jsx jsx */ + +import { jsx } from '@emotion/core'; +import { useReducer } from 'react'; + +import { Account } from '../../../client/graphql/fetch-account'; + +// LAYOUT Components +import PageWrapper from '../../PageWrapper'; +import WelcomeView, { EnterNameView, ApprovalView } from './views'; + +import { + getViews, + // getSteps +} from '../../storage/PreferredChosenNameRequest'; +import { reducer as stateReducer, newInitState, AppTitle } from '../state/app'; +import SuccessView from './successView'; + +interface Props { + account: Account; +} + +export default function Index(props: Props) { + const { account } = props; + const viewAccountObj = { + cobAgency: account.cobAgency || '', + firstName: account.firstName || '', + lastName: account.lastName || '', + email: account.email || '', + }; + + const [state, dispatchState] = useReducer(stateReducer, newInitState); + // const fetchedSteps: Array = getSteps(); + const fetchedViews: Array = getViews(); + + const closeTab = () => { + if (window) window.close(); + }; + + const changeView = (newView: any) => + dispatchState({ type: 'APP/CHANGE_VIEW', view: newView }); + + // const updateUserState = (data: any) => + // dispatchState({ type: 'APP/RESET_STATE', 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 advanceStep = () => { + console.log('advanceStep!!!!!'); + const nextView = state.view + 1; + console.log(`nextView: `, nextView, state, fetchedViews); + + if (nextView < fetchedViews.length) { + changeView(fetchedViews[nextView]); + // updateUserState({}); + // advanceStep(); + } else { + changeView(fetchedViews[0]); + } + }; + + const defaultView = ( + + + + ); + + const enterNameView = ( + + + + ); + + const approvalView = ( + + + + ); + + const successView = ( + + + + ); + + switch (fetchedViews[state.view]) { + case 'welcomeView': + return defaultView; + case 'enterNameView': + return enterNameView; + case 'approvalView': + return approvalView; + case 'successView': + return successView; + default: + return defaultView; + } +} diff --git a/services-js/access-boston/src/client/preferred-chosen-name/views/successView.tsx b/services-js/access-boston/src/client/preferred-chosen-name/views/successView.tsx new file mode 100644 index 000000000..b4c12b480 --- /dev/null +++ b/services-js/access-boston/src/client/preferred-chosen-name/views/successView.tsx @@ -0,0 +1,53 @@ +/** @jsx jsx */ + +import { jsx } from '@emotion/core'; +import { MouseEvent } from 'react'; + +//--- HTML Struct & Styling ---// +import QuestionComponent from '../components/QuestionComponent'; +import { PREFERRED_NAME_STYLING } from '../styling/index'; + +interface account { + cobAgency: string; + firstName: string; + lastName: string; + email: string; +} + +interface SuccessProps { + handleQuit: (ev: MouseEvent) => void; + appTitle: string; + account: account; +} + +export default function SuccessView(props: SuccessProps) { + const { handleQuit, account } = props; + + console.log(`SuccessView: account: `, account); + + return ( +
+

Chosen Name

+ +
+
+ +
+
+

+ Changes to your name will be reflected across your City of + Boston accounts.Lorem ipsum dolor sit amet. Qui adipisci + voluptatem quo fugit +

+
+
+
+
+
+
+ ); +} diff --git a/services-js/access-boston/src/client/preferred-chosen-name/views/views.tsx b/services-js/access-boston/src/client/preferred-chosen-name/views/views.tsx new file mode 100644 index 000000000..25d6f9240 --- /dev/null +++ b/services-js/access-boston/src/client/preferred-chosen-name/views/views.tsx @@ -0,0 +1,175 @@ +/** @jsx jsx */ + +import { jsx } from '@emotion/core'; +import { MouseEvent } from 'react'; + +//--- HTML Struct & Styling ---// +// import TextInput from '../../common/TextInput'; +import QuestionComponent from '../components/QuestionComponent'; +import { PREFERRED_NAME_STYLING } from '../styling/index'; + +interface account { + cobAgency: string; + firstName: string; + lastName: string; + email: string; +} + +// interface DefaultProps { +// handleProceed: any; +// handleStepBack: any; +// resetState: () => void; +// appTitle: string; +// handleQuit: any; +// account: account; +// } + +interface WelcomeProps { + handleProceed: (ev: MouseEvent) => void; + appTitle: string; + account: account; +} + +export default function WelcomeView(props: WelcomeProps) { + const { handleProceed, account } = props; + + const handle_proceed = (evt: MouseEvent) => { + return handleProceed(evt); + }; + + console.log(`WelcomeView/DefaultView: account: `, account); + + return ( +
+
+
+
+

Chosen Name

+
+ + +
+
+

+ Lorem ipsum dolor sit amet. Qui adipisci voluptatem quo fugit + nesciunt qui galisum quam est numquam tenetur vel cumque + repellendus. Sed voluptatum voluptas aut accusantium + asperiores 33 ipsum eveniet qui possimus possimus qui quaerat + ratione. Link +

+ +

+ Lorem ipsum dolor sit amet. Qui adipisci voluptatem quo fugit + nesciunt qui galisum quam est numquam tenetur vel cumque + repellendus. Link +

+
+
+
+
+
+
+ ); +} + +interface EnterNameProps { + handleProceed: (ev: MouseEvent) => void; + handleStepBack: (ev: MouseEvent) => void; + appTitle: string; + account: account; +} + +export function EnterNameView(props: EnterNameProps) { + const { handleProceed, handleStepBack, account } = props; + + const handle_proceed = (evt: MouseEvent) => { + return handleProceed(evt); + }; + + const handle_stepBack = (evt: MouseEvent) => { + return handleStepBack(evt); + }; + + console.log(`EnterNameView: account: `, account); + + return ( +
+

Chosen Name

+ +
+
+ +
+
+

+ Changes to your name will be reflected across your City of + Boston accounts.Lorem ipsum dolor sit amet. Qui adipisci + voluptatem quo fugit +

+
+
+
+
+
+
+ ); +} + +interface ApprovalProps { + handleProceed: (ev: MouseEvent) => void; + handleStepBack: (ev: MouseEvent) => void; + appTitle: string; + account: account; +} + +export function ApprovalView(props: ApprovalProps) { + const { handleProceed, handleStepBack, account } = props; + + const handle_proceed = (evt: MouseEvent) => { + return handleProceed(evt); + }; + + const handle_stepBack = (evt: MouseEvent) => { + return handleStepBack(evt); + }; + + console.log(`ApprovalView: account: `, account); + + return ( +
+

Chosen Name

+ +
+
+ +
+
+

+ Changes to your name will be reflected across your City of + Boston accounts.Lorem ipsum dolor sit amet. Qui adipisci + voluptatem quo fugit +

+
+
+
+
+
+
+ ); +} diff --git a/services-js/access-boston/src/client/preferred-chosen-name/views/welcomView.tsx b/services-js/access-boston/src/client/preferred-chosen-name/views/welcomView.tsx new file mode 100644 index 000000000..f86d489c0 --- /dev/null +++ b/services-js/access-boston/src/client/preferred-chosen-name/views/welcomView.tsx @@ -0,0 +1,99 @@ +/** @jsx jsx */ + +import { jsx } from '@emotion/core'; +import { useEffect, useState } from 'react'; + +// import { Account } from '../../graphql/fetch-account'; + +//--- HTML Struct & Styling ---// +import QuestionComponent from '../../common/QuestionComponent'; +import TextInput from '../../common/TextInput'; +import { SectionHeader } from '@cityofboston/react-fleet'; +import Section from '../components/section'; +import { + SECTION_STYLING, + SECTIONHEADER_STYLING, + SUBHEADER_STYLING, + INSTRUCTIONS_STYLING, + TEXTINPUT_STYLING, +} from '../styling'; + +interface account { + cobAgency: string; + firstName: string; + lastName: string; + email: string; +} + +interface Props { + handleProceed: any; + handleStepBack: any; + resetState: () => void; + appTitle: string; + handleQuit: any; + account: account; +} + +export default function IntroView(props: Props) { + const { handleProceed, handleStepBack, handleQuit, account } = 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); + }; + + const handle_stepBack = () => { + return handleStepBack(value); + }; + + console.log(`welcomeView: account: `, account); + + return ( + +
+ +
+ Instructions [cobAgency: {account.cobAgency}] +
+ +
+ Please enter the Employee ID or User ID number of the person to be + verified. +
+ +
+ setQuery(e.target.value)} + /> +
+
+
+ ); +} diff --git a/services-js/access-boston/src/client/storage/PreferredChosenNameRequest.ts b/services-js/access-boston/src/client/storage/PreferredChosenNameRequest.ts new file mode 100644 index 000000000..9f6d252ac --- /dev/null +++ b/services-js/access-boston/src/client/storage/PreferredChosenNameRequest.ts @@ -0,0 +1,25 @@ +import { View, PreferredChosenNameStep } from '../preferred-chosen-name/types'; + +const STEPS: PreferredChosenNameStep[] = [ + 'welcome', + 'enterName', + 'approval', + 'success', +]; + +export const getSteps = () => { + return [...STEPS]; +}; + +const VIEWS: View[] = [ + 'welcomeView', + 'enterNameView', + 'approvalView', + 'successView', +]; +const VIEWSALT: View[] = ['welcomeView', 'enterNameView', 'successView']; + +export const getViews = (alt?: boolean) => { + const retViews = alt && alt === true ? [...VIEWSALT] : [...VIEWS]; + return retViews; +}; diff --git a/services-js/access-boston/src/lib/apps.json b/services-js/access-boston/src/lib/apps.json index 3c01dbe31..e8ed104da 100644 --- a/services-js/access-boston/src/lib/apps.json +++ b/services-js/access-boston/src/lib/apps.json @@ -186,6 +186,10 @@ "groups": [ "SG_AB_AGILEPOINT" ] + }, + { + "title": "Add a preferred/chosen name", + "url": "/preferred-chosen-name", } ] }, diff --git a/services-js/access-boston/src/pages/preferred-chosen-name.tsx b/services-js/access-boston/src/pages/preferred-chosen-name.tsx new file mode 100644 index 000000000..79039679a --- /dev/null +++ b/services-js/access-boston/src/pages/preferred-chosen-name.tsx @@ -0,0 +1,46 @@ +import React from 'react'; +import Head from 'next/head'; + +import { PUBLIC_CSS_URL } from '@cityofboston/react-fleet'; + +import AppWrapper from '../client/common/AppWrapper'; +import Index from '../client/preferred-chosen-name/views/Index'; + +import fetchAccount, { Account } from '../client/graphql/fetch-account'; +import { GetInitialPropsDependencies, GetInitialProps } from './_app'; + +interface Props { + account: Account; +} + +export default class IdentityVerification extends React.Component { + static getInitialProps: GetInitialProps = async ( + _ctx, + { fetchGraphql }: GetInitialPropsDependencies + ): Promise => { + const account = await fetchAccount(fetchGraphql); + + return { + account, + }; + }; + + render() { + const { account } = this.props; + console.log(`preferred-name (account): `, account); + + return ( + <> + + + Access Boston - Preferred/Chosen Name + + + + {/* */} + + + + ); + } +} diff --git a/services-js/access-boston/src/server/graphql/schema.ts b/services-js/access-boston/src/server/graphql/schema.ts index fe95c543d..6e304f799 100644 --- a/services-js/access-boston/src/server/graphql/schema.ts +++ b/services-js/access-boston/src/server/graphql/schema.ts @@ -89,6 +89,7 @@ export interface Account { mfaRequiredDate: string | null; groups: string[] | null; email: string; + cobAgency: string | null; } export interface Apps { @@ -159,6 +160,7 @@ const queryRootResolvers: QueryRootResolvers = { mfaRequiredDate, groups, email, + cobAgency, } = loginSession; let mgmt_groups: Array = []; if (typeof groups === 'object' && groups.length > 0) { @@ -179,6 +181,7 @@ const queryRootResolvers: QueryRootResolvers = { mfaRequiredDate: mfaRequiredDate ? mfaRequiredDate : null, groups: mgmt_groups, email: email, + cobAgency, }; } else if (forgotPasswordAuth) { return { @@ -194,6 +197,7 @@ const queryRootResolvers: QueryRootResolvers = { mfaRequiredDate: null, groups: [''], email: '', + cobAgency: '', }; } else { // This must have the message "Forbidden" because it’s matched explicitly diff --git a/services-js/access-boston/src/server/services/SamlAuthFake.ts b/services-js/access-boston/src/server/services/SamlAuthFake.ts index cb8be2a3c..0535344d2 100644 --- a/services-js/access-boston/src/server/services/SamlAuthFake.ts +++ b/services-js/access-boston/src/server/services/SamlAuthFake.ts @@ -71,6 +71,8 @@ export default class SamlAuthFake implements Required { hasMfaDevice: !isNewUser, userAccessToken: 'jfqWE7DExC4nUa7pvkABezkM4oNT', userMfaRegistrationDate: '04/17/2019', + // cobAgency: 'BPHC', + // cobAgency: 'BPL', cobAgency: 'CH', }; return Promise.resolve(result); diff --git a/services-js/access-boston/src/stories/ChangePasswordPage.stories.tsx b/services-js/access-boston/src/stories/ChangePasswordPage.stories.tsx index da4b565c8..6a2ff8b47 100644 --- a/services-js/access-boston/src/stories/ChangePasswordPage.stories.tsx +++ b/services-js/access-boston/src/stories/ChangePasswordPage.stories.tsx @@ -15,6 +15,7 @@ const ACCOUNT: Account = { mfaRequiredDate: null, groups: [''], email: 'jondoe@boston.gov', + cobAgency: 'CH', }; storiesOf('ChangePasswordPage', module) diff --git a/services-js/access-boston/src/stories/ForgotPasswordPage.stories.tsx b/services-js/access-boston/src/stories/ForgotPasswordPage.stories.tsx index 7485b5e6c..9f37b0b0f 100644 --- a/services-js/access-boston/src/stories/ForgotPasswordPage.stories.tsx +++ b/services-js/access-boston/src/stories/ForgotPasswordPage.stories.tsx @@ -15,6 +15,7 @@ const ACCOUNT: Account = { mfaRequiredDate: null, groups: [''], email: 'jondoe@boston.gov', + cobAgency: 'CH', }; storiesOf('ForgotPasswordPage', module) diff --git a/services-js/access-boston/src/stories/GroupManagementPage.stories.tsx b/services-js/access-boston/src/stories/GroupManagementPage.stories.tsx index d2c78c4a6..70a5c198d 100644 --- a/services-js/access-boston/src/stories/GroupManagementPage.stories.tsx +++ b/services-js/access-boston/src/stories/GroupManagementPage.stories.tsx @@ -16,6 +16,7 @@ const ACCOUNT: Account = { mfaRequiredDate: '2019-03-19T15:49:37.758Z', groups: [''], email: 'jondoe@boston.gov', + cobAgency: 'CH', }; storiesOf('GroupManagementPage', module).add('default', () => ( diff --git a/services-js/access-boston/src/stories/IndexPage.stories.tsx b/services-js/access-boston/src/stories/IndexPage.stories.tsx index c6e855630..c9c3aacbd 100644 --- a/services-js/access-boston/src/stories/IndexPage.stories.tsx +++ b/services-js/access-boston/src/stories/IndexPage.stories.tsx @@ -20,6 +20,7 @@ const ACCOUNT: Account = { mfaRequiredDate: '2019-03-19T15:49:37.758Z', groups: [''], email: 'jondoe@boston.gov', + cobAgency: 'CH', }; const appsRegistry = makeAppsRegistry(APPS_YAML, true); diff --git a/services-js/access-boston/src/stories/RegisterMfaPage.stories.tsx b/services-js/access-boston/src/stories/RegisterMfaPage.stories.tsx index 8468d25fb..52672db4d 100644 --- a/services-js/access-boston/src/stories/RegisterMfaPage.stories.tsx +++ b/services-js/access-boston/src/stories/RegisterMfaPage.stories.tsx @@ -15,6 +15,7 @@ const ACCOUNT: Account = { mfaRequiredDate: null, groups: [''], email: 'jondoe@boston.gov', + cobAgency: 'CH', }; storiesOf('RegisterMfaPage', module) diff --git a/services-js/access-boston/src/stories/RegisterPage.stories.tsx b/services-js/access-boston/src/stories/RegisterPage.stories.tsx index 726802062..c1cc601a0 100644 --- a/services-js/access-boston/src/stories/RegisterPage.stories.tsx +++ b/services-js/access-boston/src/stories/RegisterPage.stories.tsx @@ -17,6 +17,7 @@ storiesOf('RegisterPage', module) mfaRequiredDate: null, groups: [''], email: 'jondoe@boston.gov', + cobAgency: 'CH', }} /> )) @@ -33,6 +34,7 @@ storiesOf('RegisterPage', module) mfaRequiredDate: null, groups: [''], email: 'jondoe@boston.gov', + cobAgency: 'CH', }} /> )); From 33739fb75e44f78ae006d9f895462c92634084d6 Mon Sep 17 00:00:00 2001 From: Phillip Kelly Date: Fri, 1 Nov 2024 14:13:38 -0400 Subject: [PATCH 03/11] DIG-5136: Preferred/Chosen Name - remove storybook file --- .../src/client/preferred-chosen-name/views/Index.stories.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services-js/access-boston/src/client/preferred-chosen-name/views/Index.stories.tsx b/services-js/access-boston/src/client/preferred-chosen-name/views/Index.stories.tsx index f87b196b1..25fe4cab0 100644 --- a/services-js/access-boston/src/client/preferred-chosen-name/views/Index.stories.tsx +++ b/services-js/access-boston/src/client/preferred-chosen-name/views/Index.stories.tsx @@ -5,7 +5,7 @@ import PageWrapper from '../../PageWrapper'; import { AppTitle } from '../state/app'; -import WelcomeView, { EnterNameView, ApprovalView, successView } from './views'; +import WelcomeView, { EnterNameView, ApprovalView } from './views'; import SuccessView from './successView'; const viewAccountObj = { From fe599755c58360d7cd07613bae64eaf250c9e5f2 Mon Sep 17 00:00:00 2001 From: Kane Xu Date: Thu, 7 Nov 2024 11:54:30 -0500 Subject: [PATCH 04/11] DIG-5324: Success Screen (#1029) * DIG-5324: Success Screen * Update SamlAuthFake.ts * Completed Success Screen PC view mockup * Completed Success Screen PC view mockup * Success Screen PC View Cleanup, Mobile View Impl * fixed row overflow with word break wrap --- .../components/AlertComponent.tsx | 65 ++++ .../components/QuestionComponent.tsx | 21 +- .../components/RowColumns.tsx | 61 ++++ .../components/TagComponent.tsx | 55 ++++ .../preferred-chosen-name/styling/index.tsx | 2 +- .../views/successView.tsx | 45 ++- .../src/server/services/SamlAuthFake.ts | 2 +- .../__snapshots__/Storyshots.test.ts.snap | 290 ++++++++++++++++++ 8 files changed, 518 insertions(+), 23 deletions(-) create mode 100644 services-js/access-boston/src/client/preferred-chosen-name/components/AlertComponent.tsx create mode 100644 services-js/access-boston/src/client/preferred-chosen-name/components/RowColumns.tsx create mode 100644 services-js/access-boston/src/client/preferred-chosen-name/components/TagComponent.tsx diff --git a/services-js/access-boston/src/client/preferred-chosen-name/components/AlertComponent.tsx b/services-js/access-boston/src/client/preferred-chosen-name/components/AlertComponent.tsx new file mode 100644 index 000000000..c990355b5 --- /dev/null +++ b/services-js/access-boston/src/client/preferred-chosen-name/components/AlertComponent.tsx @@ -0,0 +1,65 @@ +/** @jsx jsx */ +import { css, jsx } from '@emotion/core'; +import { ReactNode } from 'react'; + +interface AlertProps { + text: ReactNode; +} + +/** + * AlertComponent to display a success message with a left-aligned icon and message text. + */ +export default function AlertComponent({ text }: AlertProps): JSX.Element { + return ( +
+
+ + + +
+
{text}
+
+ ); +} + +const ALERT_CONTAINER_STYLING = css({ + display: 'flex', + alignItems: 'center', + padding: '16px', + backgroundColor: '#ecf3ec', + borderLeft: '10px solid #00a91c', + color: '#000000', + width: '100%', + lineHeight: '1.5rem', + borderRadius: '0px', + + '@media (max-width: 600px)': { + alignItems: 'start', + padding: '10px' + + }, +}); + +const ICON_CONTAINER_STYLING = css({ + marginRight: '16px', + display: 'flex', + + '@media (max-width: 600px)': { + marginRight: '8px', + padding: '2px' + }, +}); + +const ICON_STYLING = css({ + width: '32px', + height: '32px', + + '@media (max-width: 600px)': { + width: '20px', + height: '20px', + }, +}); + +const TEXT_CONTAINER_STYLING = css({ + flex: 1, +}); \ No newline at end of file diff --git a/services-js/access-boston/src/client/preferred-chosen-name/components/QuestionComponent.tsx b/services-js/access-boston/src/client/preferred-chosen-name/components/QuestionComponent.tsx index 1b42fdabd..b1bcb5132 100644 --- a/services-js/access-boston/src/client/preferred-chosen-name/components/QuestionComponent.tsx +++ b/services-js/access-boston/src/client/preferred-chosen-name/components/QuestionComponent.tsx @@ -3,7 +3,7 @@ import { css, jsx } from '@emotion/core'; import { MouseEvent, ReactNode } from 'react'; -import { MEDIA_SMALL /*, MEDIA_LARGE*/ } from '@cityofboston/react-fleet'; +import { MEDIA_SMALL } from '@cityofboston/react-fleet'; interface Props { children: ReactNode; @@ -87,7 +87,7 @@ const CONTAINER_STYLING = css({ p: { lineHeight: '2rem', - }, + } }); const BUTTON_CONTAINER_STYLING = css({ @@ -98,7 +98,7 @@ const BUTTON_CONTAINER_STYLING = css({ marginLeft: 'auto', marginRight: 'auto', padding: '0 60px 0', - marginBottom: '4rem', + marginBottom: '2rem', position: 'relative', textAlign: 'center', @@ -120,7 +120,7 @@ const BUTTON_CONTAINER_STYLING = css({ }, '.btn': { - fontSize: '16px', + fontSize: '22px', fontFamily: ' Montserrat', }, @@ -137,12 +137,21 @@ const BUTTON_CONTAINER_STYLING = css({ justifyContent: 'center', '.btn': { - width: '181px', - height: '44px', + width: '240px', + height: '60px', padding: '12px 20px', justifyContent: 'center', alignItems: 'center', gap: '8px', + + '@media (max-width: 600px)': { + height: '50px', + width: '100%', + }, }, }, + + '@media (max-width: 600px)': { + padding: '0px 20px' + }, }); diff --git a/services-js/access-boston/src/client/preferred-chosen-name/components/RowColumns.tsx b/services-js/access-boston/src/client/preferred-chosen-name/components/RowColumns.tsx new file mode 100644 index 000000000..f00f548d4 --- /dev/null +++ b/services-js/access-boston/src/client/preferred-chosen-name/components/RowColumns.tsx @@ -0,0 +1,61 @@ +/** @jsx jsx */ +import { css, jsx } from '@emotion/core'; +import { ChosenNameTag, EmailAddressTag } from './TagComponent'; + +export default function RowColumns({ + chosenName, + emailAddress, +}: { + chosenName: string; + emailAddress: string; +}) { + return ( +
+
+ +
{chosenName}
+
+
+ +
{emailAddress}
+
+
+ ); +} + +const INDEX_CONTAINER_STYLING = css({ + display: 'flex', + flexDirection: 'column', +}); + +const INDEX_ITEM_STYLING = css({ + display: 'flex', + alignItems: 'center', + borderBottom: '1px solid #ccc', + padding: '24px 56px 24px 56px', + + '@media (max-width: 600px)': { + flexDirection: 'column', + alignItems: 'flex-start', + padding: '16px 20px' + }, +}); + +const VALUE_STYLING = css({ + textAlign: 'right', + flex: '1', + whiteSpace: 'normal', + overflow: 'hidden', + wordBreak: 'break-all', + overflowWrap: 'break-word', + display: 'flex', + justifyContent: 'flex-end', + alignItems: 'center', + + '@media (max-width: 600px)': { + textAlign: 'left', + justifyContent: 'flex-start', + width: '100%', + paddingTop: '8px' + }, +}); \ No newline at end of file diff --git a/services-js/access-boston/src/client/preferred-chosen-name/components/TagComponent.tsx b/services-js/access-boston/src/client/preferred-chosen-name/components/TagComponent.tsx new file mode 100644 index 000000000..52c16ac9b --- /dev/null +++ b/services-js/access-boston/src/client/preferred-chosen-name/components/TagComponent.tsx @@ -0,0 +1,55 @@ +/** @jsx jsx */ +import { css, jsx } from '@emotion/core'; +import { ReactNode } from 'react'; + +interface TagComponentProps { + icon: ReactNode; + label: string; +} + +export function TagComponent({ icon, label }: TagComponentProps) { + return ( + + ); +} + +const TAG_CONTAINER_STYLING = css({ + display: 'flex', + alignItems: 'center', + marginRight: '10%', + gap: '18px', +}); + +const LABEL_STYLING = css({ + fontFamily: 'Montserrat', + fontWeight: 'bold', + textTransform: 'uppercase', +}); + +const ICON_STYLING = css({ + width: '32px', + height: '32px', + + // Hide icon on screens smaller than 600px + '@media (max-width: 600px)': { + display: 'none', + }, +}); + +const ChosenNameIcon = ( + + + +); + +const EmailAddressIcon = ( + + + +); + +export const ChosenNameTag = () => ; +export const EmailAddressTag = () => ; \ No newline at end of file diff --git a/services-js/access-boston/src/client/preferred-chosen-name/styling/index.tsx b/services-js/access-boston/src/client/preferred-chosen-name/styling/index.tsx index d5bcace9b..f827d28ce 100644 --- a/services-js/access-boston/src/client/preferred-chosen-name/styling/index.tsx +++ b/services-js/access-boston/src/client/preferred-chosen-name/styling/index.tsx @@ -68,7 +68,7 @@ export const PREFERRED_NAME_STYLING = css({ display: 'plex', border: '1px solid #A9AEB1', - borderRadius: '4px', + borderRadius: '4px', // Matches Figma Design '.btn': { borderRadius: '4px', diff --git a/services-js/access-boston/src/client/preferred-chosen-name/views/successView.tsx b/services-js/access-boston/src/client/preferred-chosen-name/views/successView.tsx index b4c12b480..d1716693b 100644 --- a/services-js/access-boston/src/client/preferred-chosen-name/views/successView.tsx +++ b/services-js/access-boston/src/client/preferred-chosen-name/views/successView.tsx @@ -1,13 +1,15 @@ /** @jsx jsx */ -import { jsx } from '@emotion/core'; +import { jsx, css } from '@emotion/core'; import { MouseEvent } from 'react'; //--- HTML Struct & Styling ---// import QuestionComponent from '../components/QuestionComponent'; +import AlertComponent from '../components/AlertComponent'; +import RowColumns from '../components/RowColumns'; import { PREFERRED_NAME_STYLING } from '../styling/index'; -interface account { +interface Account { cobAgency: string; firstName: string; lastName: string; @@ -17,7 +19,7 @@ interface account { interface SuccessProps { handleQuit: (ev: MouseEvent) => void; appTitle: string; - account: account; + account: Account; } export default function SuccessView(props: SuccessProps) { @@ -27,21 +29,21 @@ export default function SuccessView(props: SuccessProps) { return (
-

Chosen Name

-
-
+
+ + -
-
-

- Changes to your name will be reflected across your City of - Boston accounts.Lorem ipsum dolor sit amet. Qui adipisci - voluptatem quo fugit + quitBtnText="Log In" + handleQuit={handleQuit}> +

+
+

+ Please Note: To access the most recent update, please log in to your account

@@ -51,3 +53,16 @@ export default function SuccessView(props: SuccessProps) {
); } + +const BODY_TEXT_STYLING = css({ + paddingTop: '10px !important', + '@media (max-width: 600px)': { + padding: '0 20px 10px !important' + }, +}); + +const BODY_PARAGRAPH_STYLING = css({ + '@media (max-width: 600px)': { + lineHeight: '1.5rem !important' + }, +}) \ No newline at end of file diff --git a/services-js/access-boston/src/server/services/SamlAuthFake.ts b/services-js/access-boston/src/server/services/SamlAuthFake.ts index 0535344d2..bf734a8ad 100644 --- a/services-js/access-boston/src/server/services/SamlAuthFake.ts +++ b/services-js/access-boston/src/server/services/SamlAuthFake.ts @@ -58,7 +58,7 @@ export default class SamlAuthFake implements Required { // 'SG_AB_IAM_VENTURES', 'SG_AB_GRPMGMT_HYPERION', - // 'SG_AB_GRPMGMT_Lagan_Groups', + 'SG_AB_GRPMGMT_Lagan_Groups', // 'SG_AB_GRPMGMT_AUDITING', // 'SG_AB_GRPMGMT_TANIUM', // 'SG_AB_GRPMGMT_AGILEPOINT', 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 index 565e1bd86..9c30a85af 100644 --- a/services-js/access-boston/src/stories/__snapshots__/Storyshots.test.ts.snap +++ b/services-js/access-boston/src/stories/__snapshots__/Storyshots.test.ts.snap @@ -11052,6 +11052,296 @@ Array [ ] `; +exports[`Storyshots Preferred-Name Approval 1`] = ` +
+
+

+ Chosen Name +

+
+
+
+
+
+

+ Changes to your name will be reflected across your City of Boston accounts.Lorem ipsum dolor sit amet. Qui adipisci voluptatem quo fugit +

+
+
+
+ + +
+
+
+
+
+
+`; + +exports[`Storyshots Preferred-Name Enter Names 1`] = ` +
+
+

+ Chosen Name +

+
+
+
+
+
+

+ Changes to your name will be reflected across your City of Boston accounts.Lorem ipsum dolor sit amet. Qui adipisci voluptatem quo fugit +

+
+
+
+ + +
+
+
+
+
+
+`; + +exports[`Storyshots Preferred-Name Success 1`] = ` +
+
+
+
+ + + +
+
+ Your chosen name and email have been updated +
+
+
+
+
+
+
+

+ Chosen Name +

+

+ wdsadasds +

+
+
+

+ Email Address +

+

+ sadsadasd +

+
+
+
+
+
+

+ Changes to your name will be reflected across your City of Boston accounts. Lorem ipsum dolor sit amet. Qui adipisci voluptatem quo fugit +

+
+
+
+
+ +
+
+
+
+
+
+
+`; + +exports[`Storyshots Preferred-Name Welcome 1`] = ` +
+
+
+
+
+

+ Chosen Name +

+
+
+
+
+

+ Lorem ipsum dolor sit amet. Qui adipisci voluptatem quo fugit nesciunt qui galisum quam est numquam tenetur vel cumque repellendus. Sed voluptatum voluptas aut accusantium asperiores 33 ipsum eveniet qui possimus possimus qui quaerat ratione. + + Link + +

+

+ Lorem ipsum dolor sit amet. Qui adipisci voluptatem quo fugit nesciunt qui galisum quam est numquam tenetur vel cumque repellendus. + + Link + +

+
+
+
+ +
+
+
+
+
+
+`; + exports[`Storyshots RegisterMfaPage default 1`] = `
Date: Thu, 7 Nov 2024 12:03:33 -0500 Subject: [PATCH 05/11] EPIC--DIG-5136: Update Snapshots --- .../__snapshots__/AppsRegistry.test.ts.snap | 50 ++++ .../__snapshots__/Storyshots.test.ts.snap | 244 ++++++++++++++---- 2 files changed, 239 insertions(+), 55 deletions(-) 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 index 19c8fbf03..2e5057220 100644 --- a/services-js/access-boston/src/lib/__snapshots__/AppsRegistry.test.ts.snap +++ b/services-js/access-boston/src/lib/__snapshots__/AppsRegistry.test.ts.snap @@ -73,6 +73,16 @@ Array [ "title": "Manage My Device(s)", "url": "https://desktop.pingone.com/boston/Selection?cmd=devices", }, + Object { + "agencies": null, + "description": "", + "groups": null, + "iconUrl": null, + "mfaDeviceRequired": false, + "target": "", + "title": "Add a preferred/chosen name", + "url": "/preferred-chosen-name", + }, ], "icons": false, "showRequestAccessLink": false, @@ -218,6 +228,16 @@ Array [ "title": "Manage My Device(s)", "url": "https://desktop.pingone.com/boston/Selection?cmd=devices", }, + Object { + "agencies": null, + "description": "", + "groups": null, + "iconUrl": null, + "mfaDeviceRequired": false, + "target": "", + "title": "Add a preferred/chosen name", + "url": "/preferred-chosen-name", + }, ], "icons": false, "showRequestAccessLink": false, @@ -316,6 +336,16 @@ Array [ "title": "Manage My Device(s)", "url": "https://desktop.pingone.com/boston/Selection?cmd=devices", }, + Object { + "agencies": null, + "description": "", + "groups": null, + "iconUrl": null, + "mfaDeviceRequired": false, + "target": "", + "title": "Add a preferred/chosen name", + "url": "/preferred-chosen-name", + }, ], "icons": false, "showRequestAccessLink": false, @@ -449,6 +479,16 @@ Array [ "title": "Manage My Device(s)", "url": "https://desktop.pingone.com/boston/Selection?cmd=devices", }, + Object { + "agencies": null, + "description": "", + "groups": null, + "iconUrl": null, + "mfaDeviceRequired": false, + "target": "", + "title": "Add a preferred/chosen name", + "url": "/preferred-chosen-name", + }, ], "icons": false, "showRequestAccessLink": false, @@ -582,6 +622,16 @@ Array [ "title": "Manage My Device(s)", "url": "https://desktop.pingone.com/boston/Selection?cmd=devices", }, + Object { + "agencies": null, + "description": "", + "groups": null, + "iconUrl": null, + "mfaDeviceRequired": false, + "target": "", + "title": "Add a preferred/chosen name", + "url": "/preferred-chosen-name", + }, ], "icons": false, "showRequestAccessLink": false, 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 index 9c30a85af..b232216f4 100644 --- a/services-js/access-boston/src/stories/__snapshots__/Storyshots.test.ts.snap +++ b/services-js/access-boston/src/stories/__snapshots__/Storyshots.test.ts.snap @@ -8282,6 +8282,36 @@ exports[`Storyshots IndexPage change password success 1`] = ` } > + + + +
  • + + + Add a preferred/chosen name + + +
  • @@ -9322,6 +9352,36 @@ exports[`Storyshots IndexPage default 1`] = ` } > + + + +
  • + + + Add a preferred/chosen name + + +
  • @@ -10416,6 +10476,36 @@ exports[`Storyshots IndexPage hasn’t registered MFA 1`] = ` } > + + + +
  • + + + Add a preferred/chosen name + + +
  • @@ -11084,7 +11174,7 @@ exports[`Storyshots Preferred-Name Approval 1`] = `
    @@ -11324,7 +11458,7 @@ exports[`Storyshots Preferred-Name Welcome 1`] = `
    +
    +
    + + + +`; + +exports[`Storyshots Preferred-Name Welcome BPL 1`] = ` +
    +
    +
    +
    +

    -
    -

    - Lorem ipsum dolor sit amet. Qui adipisci voluptatem quo fugit nesciunt qui galisum quam est numquam tenetur vel cumque repellendus. Sed voluptatum voluptas aut accusantium asperiores 33 ipsum eveniet qui possimus possimus qui quaerat ratione. - - Link - -

    -

    - Lorem ipsum dolor sit amet. Qui adipisci voluptatem quo fugit nesciunt qui galisum quam est numquam tenetur vel cumque repellendus. - - Link - -

    -
    +

    + A chosen name could be a preferred nickname, middle name, a shortened version of a legal name, or a name that aligns with their gender identity or expression. +

    +
      + + What’s Changing: + +
    • + + Chosen Name: + + You can add a chosen name to your profile. This name will be displayed in internal City of Boston systems and communications. +
    • +
    • + + Email Address: + + You can update your email address to reflect your chosen name—or keep it as is, if you prefer. +
    • +
    +
      + + What’s Not Changing: + +
    • + + Legal Name: + + City Employees, your legal name used for tax documents (like W2s), paystubs, or other official legal documents will remain unchanged. To change your legal name, please follow the legal name link. +
    • +
    +

    + + Please note: + + Any changes made to your chosen name or email address will be reflected across all relevant systems and communications. +

    Date: Fri, 8 Nov 2024 16:01:12 -0500 Subject: [PATCH 07/11] DIG-5320: Chosen Name Screen (NEW)-COB Employees/Contractors (#1031) * PC & Mobile View * Fixed Mobile Styling Consistency --- .../components/QuestionComponent.tsx | 83 +++++----- .../components/TagComponent.tsx | 5 +- .../preferred-chosen-name/styling/index.tsx | 10 +- .../views/Index.stories.tsx | 5 +- .../views/enterNameView.tsx | 152 ++++++++++++++++++ .../views/successView.tsx | 2 +- .../views/welcomeView.tsx | 4 +- 7 files changed, 210 insertions(+), 51 deletions(-) create mode 100644 services-js/access-boston/src/client/preferred-chosen-name/views/enterNameView.tsx diff --git a/services-js/access-boston/src/client/preferred-chosen-name/components/QuestionComponent.tsx b/services-js/access-boston/src/client/preferred-chosen-name/components/QuestionComponent.tsx index b1bcb5132..8c35858a1 100644 --- a/services-js/access-boston/src/client/preferred-chosen-name/components/QuestionComponent.tsx +++ b/services-js/access-boston/src/client/preferred-chosen-name/components/QuestionComponent.tsx @@ -3,7 +3,6 @@ import { css, jsx } from '@emotion/core'; import { MouseEvent, ReactNode } from 'react'; -import { MEDIA_SMALL } from '@cityofboston/react-fleet'; interface Props { children: ReactNode; @@ -16,6 +15,7 @@ interface Props { nextButtonText?: string; prevBtnText?: string; quitBtnText?: string; + extraButtons?: ReactNode; } /** @@ -34,13 +34,14 @@ export default function QuestionComponent(props: Props): JSX.Element { handleQuit, quitBtn, quitBtnText, + extraButtons } = props; return (
    {children} -
    +
    {handleStepBack && ( + + +
    @@ -11413,7 +11587,7 @@ exports[`Storyshots Preferred-Name Welcome 1`] = ` className="b-c" >
    +
    @@ -115,7 +132,7 @@ const INFO_STYLING = css({ padding: '30px 60px', borderBottom: '1px solid #A9AEB1', '@media (max-width: 600px)': { - padding: '25px 15px' + padding: '25px 15px', }, }); @@ -128,7 +145,7 @@ const CURRENT_NAME_CONTAINER_STYLING = css({ }); const CURRENT_NAME_STYLING = css({ - flex: "1", + flex: '1', display: 'flex', flexDirection: 'column', padding: '15px 0px', @@ -138,29 +155,29 @@ const CURRENT_NAME_STYLING = css({ marginLeft: '50px', fontSize: '1.2em', }, - '@media (max-width: 600px)': { + '@media (max-width: 600px)': { padding: '5px 0px', - marginTop: '10px', - '.CurrentName': { - marginTop: '5px', - marginLeft: '0px', - fontSize: '1.2em', - }, + marginTop: '10px', + '.CurrentName': { + marginTop: '5px', + marginLeft: '0px', + fontSize: '1.2em', + }, }, }); const EDIT_BUTTON_STYLING = css({ color: '#005EA2', backgroundColor: 'white', - fontSize: "1em", + fontSize: '1em', border: '2px solid #005EA2', - flex: "0" + flex: '0', }); const RADIO_GROUP_STYLING = css({ padding: '50px 60px', '@media (max-width: 600px)': { - padding: '25px 15px' + padding: '25px 15px', }, display: 'flex', flexDirection: 'column', @@ -169,25 +186,25 @@ const RADIO_GROUP_STYLING = css({ const RADIO_STACK_STYLING = css({ cursor: 'pointer', - padding: "10px 5px 20px 5px", - border: '3px solid #A9AEB1' + padding: '10px 5px 20px 5px', + border: '3px solid #A9AEB1', }); const RADIO_SELECTED_STYLING = css({ borderColor: '#005EA2', - backgroundColor: '#D9E8F6' + backgroundColor: '#D9E8F6', }); const RADIO_OPTION_STYLING = css({ - alignItems: "center", - display: "flex", - gap: "12px", + alignItems: 'center', + display: 'flex', + gap: '12px', flexDirection: 'row', }); const RADIO_LABEL_STYLING = css({ - fontWeight: 'bold' -}); + fontWeight: 'bold', +}); const EMAIL_TEXT_STYLING = css({ fontSize: '1em', @@ -195,7 +212,7 @@ const EMAIL_TEXT_STYLING = css({ wordBreak: 'break-all', overflowWrap: 'break-word', margin: '10px 50px 0px', - '@media (max-width: 600px)': { - margin: '10px 44px' + '@media (max-width: 600px)': { + margin: '10px 44px', }, -}); \ No newline at end of file +}); diff --git a/services-js/access-boston/src/client/preferred-chosen-name/views/enterNameView.tsx b/services-js/access-boston/src/client/preferred-chosen-name/views/enterNameView.tsx index 7f9a0bc28..694965fd9 100644 --- a/services-js/access-boston/src/client/preferred-chosen-name/views/enterNameView.tsx +++ b/services-js/access-boston/src/client/preferred-chosen-name/views/enterNameView.tsx @@ -18,9 +18,7 @@ interface EnterNameProps { account: Account; } -export default function EnterNameProps({ handleProceed, account }: EnterNameProps) { - console.log(account); - +export default function EnterNameProps({ handleProceed }: EnterNameProps) { const [firstName, setFirstName] = useState(''); const [lastName, setLastName] = useState(''); @@ -32,7 +30,7 @@ export default function EnterNameProps({ handleProceed, account }: EnterNameProp return (
    - Chosen Name + Chosen Name
    @@ -56,22 +54,30 @@ export default function EnterNameProps({ handleProceed, account }: EnterNameProp > Clear - }> + } + >
    - Use the below fields to update your chosen name. You can choose to change your first name, last name, or both. -
    + Use the below fields to update your chosen name. You can choose to + change your first name, last name, or both. +
    setFirstName(e.target.value)} + onChange={e => setFirstName(e.target.value)} css={INPUT_STYLING} />
    -
    +
    setLastName(e.target.value)} + onChange={e => setLastName(e.target.value)} css={INPUT_STYLING} />
    @@ -85,28 +91,28 @@ export default function EnterNameProps({ handleProceed, account }: EnterNameProp } const HEADER_CONTAINER_STYLING = css({ - fontSize: "2em", - paddingBottom: "20px", - marginBottom: "30px", - '@media (max-width: 600px)': { - fontSize: "1.8em", - paddingBottom: "15px", - marginBottom: "25px", + fontSize: '2em', + paddingBottom: '20px', + marginBottom: '30px', + '@media (max-width: 600px)': { + fontSize: '1.8em', + paddingBottom: '15px', + marginBottom: '25px', }, }); const INFO_STYLING = css({ padding: '30px 60px', borderBottom: '1px solid #A9AEB1', - '@media (max-width: 600px)': { - padding: '25px 15px' + '@media (max-width: 600px)': { + padding: '25px 15px', }, }); const FORM_STYLING = css({ padding: '50px 60px', - '@media (max-width: 600px)': { - padding: '25px 15px' + '@media (max-width: 600px)': { + padding: '25px 15px', }, }); @@ -118,14 +124,14 @@ const CURRENT_NAME_STYLING = css({ marginLeft: '50px', fontSize: '1.2em', }, - '@media (max-width: 600px)': { + '@media (max-width: 600px)': { padding: '5px 0px', - marginTop: '10px', - '.CurrentName': { - marginTop: '5px', - marginLeft: '0px', - fontSize: '1.2em', - }, + marginTop: '10px', + '.CurrentName': { + marginTop: '5px', + marginLeft: '0px', + fontSize: '1.2em', + }, }, }); @@ -134,7 +140,7 @@ const LABEL_STYLING = css({ display: 'block', fontSize: '22px', marginBottom: '8px', - '@media (max-width: 600px)': { + '@media (max-width: 600px)': { fontSize: '16px', }, }); @@ -149,4 +155,4 @@ const INPUT_STYLING = css({ fontSize: '1em', borderRadius: '0', border: '1px solid', -}); \ No newline at end of file +}); diff --git a/services-js/access-boston/src/client/preferred-chosen-name/views/welcomeView.tsx b/services-js/access-boston/src/client/preferred-chosen-name/views/welcomeView.tsx index 51abbe286..7eee397eb 100644 --- a/services-js/access-boston/src/client/preferred-chosen-name/views/welcomeView.tsx +++ b/services-js/access-boston/src/client/preferred-chosen-name/views/welcomeView.tsx @@ -17,14 +17,12 @@ interface welcomeProps { account: Account; } -export default function WelcomeView({ handleProceed, account }: welcomeProps) { - console.log(account) - +export default function WelcomeView({ handleProceed }: welcomeProps) { return (
    -

    +

    - A chosen name could be a preferred nickname, middle name, a shortened version of a legal name, or a name that aligns with their gender identity or expression. + A chosen name could be a preferred nickname, middle name, a + shortened version of a legal name, or a name that aligns with + their gender identity or expression.

      What’s Changing:
    • Chosen Name: - You can add a chosen name to your profile. This name will be displayed in internal City of Boston systems and communications. + You can add a chosen name to your profile. This name will be + displayed in internal City of Boston systems and + communications.
    • Email Address: - You can update your email address to reflect your chosen name—or keep it as is, if you prefer. + You can update your email address to reflect your chosen + name—or keep it as is, if you prefer.
      What’s Not Changing:
    • Legal Name: - City Employees, your legal name used for tax documents (like W2s), paystubs, or other official legal documents will remain unchanged. To change your legal name, please follow the legal name link. + City Employees, your legal name used for tax documents (like + W2s), paystubs, or other official legal documents will remain + unchanged. To change your legal name, please follow the legal + name link.

    - Please note: - Any changes made to your chosen name or email address will be reflected across all relevant systems and communications. + Please note: + Any changes made to your chosen name or email address will be + reflected across all relevant systems and communications.

    @@ -74,7 +81,7 @@ const SNIPPET_CONTAINER_STYLING = css({ margin: '0px', '@media (max-width: 600px)': { lineHeight: '1.5rem !important', - padding: '25px 0px' + padding: '25px 0px', }, }, '& > p:last-of-type': { @@ -87,7 +94,7 @@ const SNIPPET_CONTAINER_STYLING = css({ '@media (max-width: 600px)': { marginBottom: '10px', }, - } + }, }, '& li': { marginLeft: '40px', @@ -95,12 +102,12 @@ const SNIPPET_CONTAINER_STYLING = css({ listStyleType: 'disc', listStyle: 'outside', '@media (max-width: 600px)': { - marginLeft: '20px', - marginBottom: '10px', + marginLeft: '20px', + marginBottom: '10px', }, }, '@media (max-width: 600px)': { - padding: '0px 15px' + padding: '0px 15px', }, }); @@ -111,5 +118,5 @@ const HEADER_STYLING = css({ '@media (max-width: 600px)': { content: '"Chosen Name"', }, - } -}); \ No newline at end of file + }, +}); diff --git a/services-js/access-boston/src/server/access-boston.ts b/services-js/access-boston/src/server/access-boston.ts index d264771ae..25f4ed9f9 100644 --- a/services-js/access-boston/src/server/access-boston.ts +++ b/services-js/access-boston/src/server/access-boston.ts @@ -14,11 +14,12 @@ import yar from 'yar'; import cleanup from 'node-cleanup'; import acceptLanguagePlugin from 'hapi-accept-language2'; import hapiDevErrors from 'hapi-dev-errors'; -const next = require('next'); +import next from 'next'; import { ApolloServer } from 'apollo-server-hapi'; import { parse, Compile } from 'velocityjs'; import { default as pingData } from './ping-templates/mockData'; +import { serverPayloadValidAndUseful } from './helpers'; import Rollbar from 'rollbar'; @@ -56,6 +57,13 @@ import Session from './Session'; import PingId, { pingIdFromProperties } from './services/PingId'; import PingIdFake from './services/PingIdFake'; +import { + basicAuthBase64Str, + requestNewNameEmail, + workflowArgs, + // workflowReqArgs, +} from './services/preferredName'; + require('dotenv').config(); interface ID_VERIFICATION { @@ -431,6 +439,168 @@ async function addVelocityTemplates(server: HapiServer) { }, }); + server.route({ + path: '/preferred-name-request', + method: ['POST'], + options: { + auth: false, + plugins: { + crumb: false, + }, + timeout: { server: 15000 }, + }, + handler: async (req, h) => { + // CHECK `token` is present in the request and matches `ENV` + if ( + !req['headers'] || + !req['headers']['token'] || + (req['headers']['token'] as string) !== + (process.env.PREFERRED_NAME__API_KEY as string) + ) { + return h.response({ error: 'Invalid or Missing Token' }).code(400); + } + + const WORKFLOW_URL__GENERATE_EMAIL: string = process.env + .WORKFLOW_URL__GENERATE_EMAIL as string; + const reqReqFields = ['id']; + const optFields = ['preferredFirstName', 'preferredLastName']; + const validRequestFields = reqReqFields.concat(optFields); + const serverPayloadValid = serverPayloadValidAndUseful( + req.payload, + validRequestFields, + 1, + optFields + ); + + try { + if ( + WORKFLOW_URL__GENERATE_EMAIL.length > 0 && + typeof req.payload === 'object' && + req.payload && + serverPayloadValid + ) { + if (dev) { + // USE FIXTURE + return await readFile( + path.resolve( + __dirname, + '../../fixtures/preferred-chosen-name/test/COB-Workflow-GenerateUniqueEmail/response/40000093.json' + ), + 'utf-8' + ); + } else { + let workflowArgs: workflowArgs = { + identityName: req.payload['id'], + }; + + if (req.payload['preferredFirstName']) + workflowArgs['preferredFirstName'] = + req.payload['preferredFirstName']; + if (req.payload['preferredLastName']) + workflowArgs['preferredLastName'] = + req.payload['preferredLastName']; + + return requestNewNameEmail({ + endpoint: WORKFLOW_URL__GENERATE_EMAIL, + requestJson: { workflowArgs }, + authStr: basicAuthBase64Str( + process.env.IDENTITYIQ_USERNAME, + process.env.IDENTITYIQ_PASSWORD + ), + }); + } + } else { + throw Boom.notFound(`No data is available for »${req}«`); + } + } catch (error) { + console.log(`/preferred-name-request (error): `, error); + return h.response({ error: 'Invalid JSON format Lv2' }).code(400); + } + }, + }); + + server.route({ + path: '/preferred-name-submit', + method: ['POST'], + options: { + auth: false, + plugins: { + crumb: false, + }, + timeout: { server: 15000 }, + }, + handler: async (req, h) => { + // CHECK `token` is present in the request and matches `ENV` + if ( + !req['headers'] || + !req['headers']['token'] || + (req['headers']['token'] as string) !== + (process.env.PREFERRED_NAME__API_KEY as string) + ) { + return h.response({ error: 'Invalid or Missing Token' }).code(400); + } + + const WORKFLOW_URL__UPDATENAME: string = process.env + .WORKFLOW_URL__UPDATENAME as string; + const reqReqFields = ['id']; + const optFields = ['preferredFirstName', 'preferredLastName', 'email']; + const validRequestFields = reqReqFields.concat(optFields); + const serverPayloadValid = serverPayloadValidAndUseful( + req.payload, + validRequestFields, + 1, + optFields + ); + + try { + if ( + WORKFLOW_URL__UPDATENAME.length > 0 && + typeof req.payload === 'object' && + req.payload && + serverPayloadValid + ) { + if (dev) { + // USE FIXTURE + return await readFile( + path.resolve( + __dirname, + '../../fixtures/preferred-chosen-name/test/COB-Workflow-PreferredNames/response/40000093.json' + ), + 'utf-8' + ); + } else { + let workflowArgs: workflowArgs = { + identityName: req.payload['id'], + }; + + if (req.payload['preferredFirstName']) + workflowArgs['preferredFirstName'] = + req.payload['preferredFirstName']; + if (req.payload['preferredLastName']) + workflowArgs['preferredLastName'] = + req.payload['preferredLastName']; + if (req.payload['email']) + workflowArgs['email'] = req.payload['email']; + + return requestNewNameEmail({ + endpoint: WORKFLOW_URL__UPDATENAME, + requestJson: { workflowArgs }, + authStr: basicAuthBase64Str( + process.env.IDENTITYIQ_USERNAME, + process.env.IDENTITYIQ_PASSWORD + ), + }); + } + } else { + throw Boom.notFound(`No data is available for »${req}«`); + } + } catch (error) { + console.log(`/preferred-name-request (error): `, error); + return h.response({ error: 'Invalid JSON format Lv2' }).code(400); + } + }, + }); + server.route({ path: '/fetchGraphql', method: ['POST'], @@ -445,8 +615,7 @@ async function addVelocityTemplates(server: HapiServer) { const fetchQ = async this_req => { const query = this_req.payload.query; const variables = this_req.payload.variables; - // console.log('query: ', query); - // console.log('variables: ', variables); + return await fetch( `${process.env.GROUP_MANAGEMENT_API_URL}` as string, { diff --git a/services-js/access-boston/src/server/helpers.ts b/services-js/access-boston/src/server/helpers.ts new file mode 100644 index 000000000..4116e40ac --- /dev/null +++ b/services-js/access-boston/src/server/helpers.ts @@ -0,0 +1,49 @@ +// hasExtraKeys +export const hasExtraKeys = (obj: object, allowedKeys: Array) => { + const objKeys = Object.keys(obj); + + for (const key of objKeys) { + if (!allowedKeys.includes(key)) { + return true; // Found an extra key + } + } + + return false; // No extra keys found +}; + +// isValidJSON +export const isValidJSON = (str: string) => { + try { + JSON.parse(str); + return true; + } catch (e) { + return false; + } +}; + +export const serverPayloadValidAndUseful = ( + payload: any, + validRequestFields: Array, + reqOptFields: number, + optFieldsArr: Array +) => { + const isObj = typeof payload === 'object'; + const objKeyGt0 = Object.keys(payload).length > 0; + const jsonValid = isValidJSON(JSON.stringify(payload)); + const noExtraKeys = !hasExtraKeys( + JSON.parse(JSON.stringify(payload)), + validRequestFields + ); + const optFieldMinReqMeet = validRequestFields.some(value => + optFieldsArr.includes(value) + ); + + return ( + isObj && + objKeyGt0 && + jsonValid && + noExtraKeys && + reqOptFields > 0 && + optFieldMinReqMeet + ); +}; diff --git a/services-js/access-boston/src/server/services/IdentityIq.ts b/services-js/access-boston/src/server/services/IdentityIq.ts index fd3cfc9f5..fc2aa91f4 100644 --- a/services-js/access-boston/src/server/services/IdentityIq.ts +++ b/services-js/access-boston/src/server/services/IdentityIq.ts @@ -86,6 +86,26 @@ export interface LaunchedWorkflowResponse { launcher: string; } +export interface LauncedPreferredNameWorkflowResponse { + status: string; + requestID: string; + warnings: any; + errors: any; + retryWait: number; + metaData: any; + attributes: { + result: { + DisplayName: string; + newEmail: string; + error: string; + }; + result1: string; + }; + complete: false; + success: false; + retry: false; +} + /** * Service to connect to IdentityIQ to change passwords and handle other * workflow tasks. @@ -265,6 +285,28 @@ export default class IdentityIq { return this.makeScimRequest('LaunchedWorkflows', 'POST', requestBody); } + /** + * Update user Preferred Chosen Name + * @param caseId + * @returns + */ + // async updatePrefferedChosenName( + // userId: string, + // { + // preferredFirstName, + // preferredLastName, + // email, + // }: { + // preferredFirstName?: string; + // preferredLastName?: string; + // email?: string; + // } + // ) { + // const requestBody: LaunchWorkflowRequest = { + // schemas: [], + // }; + // } + async fetchWorkflow(caseId: string): Promise { return this.makeScimRequest(`LaunchedWorkflows/${caseId}`); } diff --git a/services-js/access-boston/src/server/services/preferredName.ts b/services-js/access-boston/src/server/services/preferredName.ts new file mode 100644 index 000000000..df8142215 --- /dev/null +++ b/services-js/access-boston/src/server/services/preferredName.ts @@ -0,0 +1,50 @@ +import fetch from 'node-fetch'; + +export interface workflowReqArgs { + id: string; + preferredFirstName?: string; + preferredLastName?: string; + email?: string; +} + +export interface workflowArgs { + identityName: string; + preferredFirstName?: string; + preferredLastName?: string; + email?: string; +} + +export interface requestWorkflow { + workflowArgs: workflowArgs; +} + +export const basicAuthBase64Str = ( + user: string = '', + pass: string = '' +): string => { + return 'Basic ' + Buffer.from(user + ':' + pass).toString('base64'); +}; + +export const requestNewNameEmail = async (params: { + endpoint: string; + requestJson: requestWorkflow; + authStr: string; +}) => { + const { endpoint, requestJson, authStr } = params; + console.log(`requestJson: `, requestJson); + + return await fetch(endpoint, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: authStr, + }, + body: JSON.stringify(requestJson), + }) + .then(response => response.json()) + .then(response => response) + .catch(error => { + console.log('/preferred-name-request Error(requestNewNameEmail):', error); + return {}; + }); +}; 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 index b3e11b50e..7a6f30ea9 100644 --- a/services-js/access-boston/src/stories/__snapshots__/Storyshots.test.ts.snap +++ b/services-js/access-boston/src/stories/__snapshots__/Storyshots.test.ts.snap @@ -11272,7 +11272,9 @@ exports[`Storyshots Preferred-Name Confirmation 1`] = `

    - For more information, see the + + For more information, see the + Date: Wed, 13 Nov 2024 11:46:17 -0500 Subject: [PATCH 11/11] DIG-5136: Reviewed Enhancements 11.12 (#1034) --- .../components/AlertComponent.tsx | 2 +- .../components/QuestionComponent.tsx | 4 +- .../components/RowColumns.tsx | 20 ++- .../preferred-chosen-name/styling/index.tsx | 6 +- .../views/confirmationView.tsx | 118 +++++++++++------- .../views/enterNameView.tsx | 32 +++-- .../views/successView.tsx | 7 +- .../views/welcomeView.tsx | 16 ++- 8 files changed, 117 insertions(+), 88 deletions(-) diff --git a/services-js/access-boston/src/client/preferred-chosen-name/components/AlertComponent.tsx b/services-js/access-boston/src/client/preferred-chosen-name/components/AlertComponent.tsx index c990355b5..ddb67096f 100644 --- a/services-js/access-boston/src/client/preferred-chosen-name/components/AlertComponent.tsx +++ b/services-js/access-boston/src/client/preferred-chosen-name/components/AlertComponent.tsx @@ -25,7 +25,7 @@ export default function AlertComponent({ text }: AlertProps): JSX.Element { const ALERT_CONTAINER_STYLING = css({ display: 'flex', alignItems: 'center', - padding: '16px', + padding: '16px 40px 16px 30px', backgroundColor: '#ecf3ec', borderLeft: '10px solid #00a91c', color: '#000000', diff --git a/services-js/access-boston/src/client/preferred-chosen-name/components/QuestionComponent.tsx b/services-js/access-boston/src/client/preferred-chosen-name/components/QuestionComponent.tsx index 8c35858a1..d55c3b993 100644 --- a/services-js/access-boston/src/client/preferred-chosen-name/components/QuestionComponent.tsx +++ b/services-js/access-boston/src/client/preferred-chosen-name/components/QuestionComponent.tsx @@ -45,7 +45,7 @@ export default function QuestionComponent(props: Props): JSX.Element { {handleStepBack && (