Skip to content

Commit

Permalink
TUP-517 and TUP-472: MFA pairing UI/accessibility tweaks (#265)
Browse files Browse the repository at this point in the history
* MFA pairing UI/accessibility tweaks

* formatting

* additional UI tweaks

* MFA pairing UI/accessibility tweaks - design changes (#270)

* refactor: move link to to docs (TUP-535:4,5)

* refactor!: prepare for c-nav--piped (TUP-535:3)

The pipe i slost, but will return when core-styles.base.css is v2.12.1.

Currently, this requires TACC/Core-CMS to load TACC/Core-Styles v2.12.1.

Maybe (later), Portal should use diff version than CMS.

BREAKING CHANGE: This removes the pipe.

* refactor: change 5m msg, get help link (TUP-535:6)

- Change text of the "5 minutes" message.
- Add either link to trigger modal open or link to open modal.*

* This prevents two modals being hidden into the makup.

* refactor: change qr error to reuse a help modal

* style: npx nx format:write

* refactor: simplify `qr-code-box` markup and styles

* refactor: use lists for mfa panels

* fix: missing space before "Get help." link

* refactor: simpler messages (TUP-535:2.1.1+2.1.2)

- use one tag and one class
- remove extra markup
- refactor panel layout to support new message position

* refactor: let modal manage duplicate instances

- remove conditional modal opening code
- do not say "please" in messages

* fix: restore code that I had made text for testing

* chore: remove now-unnecessary id form markup

* feat: new message if QR code alt. is unavilable

* fix: add periods to end of sentences…

* chore: remove now-unnecessary id from props

* chore: remove now-unnecessary id from props

* fix: load v2.13 c-nav component

* chore: improve a comment

* style: npx nx format:write

* fix: bad grammar in error message

* fix: bad grammar, remove please

* feat: TextCopyField.jsx to .tsx, and enable it

* feat: TextCopyModal

* fix: FieldWrapper, opt. req. & let desc. be React

* fix: FieldWrapperFormik, let desc. be React

* feat: text copy modal (TUP-535:2.1.3)

* chore: uninstall react-copy-to-clipboard

* feat: install @tacc/core-styles 2.14.0

* refactor: FieldWrapper mirror FeildWrapperFormik

Make FieldWrapper look like FeildWrapperFormik.

Lets FieldWrapper be used independent of Formik.

* feat: style text copy modal (TUP-535:2.1.3)

* npx nx format:write

* fix: qr code box was not 200px until image loaded

* fix: remove test logic for msg. about qr alt. code

* fix: pass qr alt. verification code

* feat: allow markup in label e.g. <a>

* fix: markup in label should not be spaced by flex

* fix: let core-styles style form error text

* fix: use FieldWrapper consistently and correctly

Prevents form error messages overlapping qr code messages*.

* These should be called mfa messages. Expect a commit/fix.

* chore: rename `qr-code-message` to `mfa-message`

* chore: remove unused `ButtonWrapper`

* chore: load sibling core-components from rel. path

* chore: remove unused `SectionMessage` import

* fix: resolve "circular dependency"

I.E. Moved FieldWrapper to core-components.

Error: https://github.com/TACC/tup-ui/actions/runs/5650505534/job/15306952950?pr=270

Docs: https://nx.dev/recipes/other/resolve-circular-dependencies

* nx format:write

* fix: loose ends after "circular dependency" fix

* fix: fieldwrapper css duplication too confusing

* chore: remove excess `<span>` tag

* chore: no field wrapper desc unless desc exists

* npx nx format:write

* fix: grammar error from design

* chore: use installed @core-styles, not CDN

* refactor: simplify TextCopyField (no CopyField)

* refactor: simplify TextCopyField (no ButtonWrapper)

* refactor: TextCopyModal hint → <TextCopyModalHint>

* feat: support and add id attr to <Button>

* npx nx format:write

* fix: qr code should resize w/ browser base font

* refactor: simplify qr code styles

* refactor: use variable for qr code size

* fix: increase qr coe size back to 200px not 160px

* fix(a11y): status msg box needs role before msg

https://www.w3.org/WAI/WCAG22/Techniques/aria/ARIA22

> Check that the container destined to hold the status message has a role attribute with a value of status before the status message occurs.

* fix(a11y): no use <label> text for <button> text

I.e. let screen reader read the button text.

* Revert "fix(a11y): status msg box needs role before msg"

This reverts commit f2f325d.

* fix: add and pass id to TextCopyField

* feat(a11y): title text for qr code img

* npx nx format:write

* feat: allow custom `tagName` for `<Message>`

* chore: describe FieldWrapperFormik global css

* refactor: replace FieldWrapper w/ upcoming s-form

Core-Styles will add s-form.

Allows Portals to create well-styled forms w/out copious class names.

* chore: remove FieldWrapper (not Formik)

* refactor:return feild wrap CSS to comp. as global

Return FieldWrapperFormik CSS back to component, but as global.

* npx nx format:write

* refactor: simpler id assignment

* fix: static mfa panel width so mfa-msg is centered

* refactor: no modal for text copty

* fix: restore accientally deleted conditions

* chore: remove component changes moved to PR #277

#277

* chore: remove component changes moved to PR #276

#276

* chore: remove stray changes

* fix: restore TypeScript TextCopyModal

* fix: data.otpkey as var not text

* fix: disable read-only fields, just to be safe

* refactor: use core-styles v2.15, not form.cms.css (#279)

* fix: alt. qr otp label as block instead of inline

Part of the message for QR code alternative OTP was moved to new line.

* fix: add space between mfa msg and section bottom

If section short enough to scroll, MFA message touches section bottom.

* fix: QR loading div was higher than button and img

* fix: darker danger color was not taking effect

The need for !important is likely caused by:
vitejs/vite#4448

Possible solution:
vitejs/vite#4448 (comment)

* fix: core-styles v2.16.2

* fix: remove (now?) unnecessary <br />

The label is display block, so <br /> is not needed to make new line.

* fix: match action spacing, drop related deviations

1. Match spacing between action or action box.
2. Remove deviant font size on an aciton.

* refactor: use SectionHeader / less duplicate code

* chore: nx format:write

---------

Co-authored-by: Jake Rosenberg <[email protected]>
Co-authored-by: Jake Rosenberg <[email protected]>
Co-authored-by: Wesley B <[email protected]>
  • Loading branch information
4 people authored Aug 2, 2023
1 parent b09ef73 commit 037b2c4
Show file tree
Hide file tree
Showing 21 changed files with 396 additions and 349 deletions.
21 changes: 21 additions & 0 deletions apps/tup-ui/src/main.global.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
/* To use new c-nav component not yet in TACC/Core-CMS core-styles.base.css */
/* TODO: Remove when TACC/Core-CMS loads TACC/Core-Styles v2.13+ */
/* CAVEAT: Duplicates older, unchanged, global c-nav styles */
@import url("@tacc/core-styles/dist/components/c-nav.css");

/* To use s-affixed-input-wrapper which had been unused outside TACC/Core-CMS */
/* TODO: Remove when TACC/Core-CMS loads TACC/Core-Styles v2.14+ */
@import url("@tacc/core-styles/dist/trumps/s-affixed-input-wrapper.css");

/* To use new form styles in core-styles.base.css */
/* TODO: Remove when TACC/Core-CMS loads TACC/Core-Styles v2.15+ */
@import url("@tacc/core-styles/dist/elements/form.css");
@import url("@tacc/core-styles/dist/trumps/s-form.css");

/* To overwrite @tacc/core-styles CEPv2 spacing */
:root {
/* TACC/Core-Styles/blob/823b7b9/src/lib/_imports/settings/space.css */
Expand Down Expand Up @@ -54,3 +68,10 @@ h3 {
table {
width: 100%;
}

/* Forms */
/* To hide icon of a Message that is used as a form error */
/* FAQ: Used by MfaValidationPanel, MfaSmsPanel */
.s-form input + [role="status"] > .icon {
display: none;
}
7 changes: 7 additions & 0 deletions apps/tup-ui/src/main.global.for-core-styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,10 @@ hr {
margin-right: 0.25ch;
text-transform: none;
}

/* TODO: Remove this after:
0. CEP v2 colors are removed from Core-Styles settings/color--portal */
:root {
/* FAQ: The `!important` is only necessary for dev (and thus prod) server */
--global-color-danger--normal: #dc393b !important;
}
4 changes: 2 additions & 2 deletions apps/tup-ui/src/pages/Mfa/Mfa.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ import { Outlet } from 'react-router-dom';
const Mfa: React.FC = () => {
return (
<RequireAuth>
<div className={styles['mfa-layout']}>
<section className={styles['mfa-layout']}>
<MfaHeader />
{/* Default to a "success" view if user has a verified token */}
<MfaWrapper>
<Outlet />
</MfaWrapper>
</div>
</section>
</RequireAuth>
);
};
Expand Down
1 change: 1 addition & 0 deletions libs/core-components/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ export { default as InfiniteScrollTable } from './lib/InfiniteScrollTable';
export { default as Sidebar } from './lib/Sidebar';
export { default as HistoryBadge } from './lib/HistoryBadge';
export { default as Collapse } from './lib/Collapse';
export { default as TextCopyField } from './lib/TextCopyField';
export * from './lib/Form';
3 changes: 3 additions & 0 deletions libs/core-components/src/lib/Button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ type ButtonProps = React.PropsWithChildren<{
className?: string;
iconNameBefore?: string;
iconNameAfter?: string;
id?: string;
dataTestid?: string;
disabled?: boolean;
onClick?: (e: React.MouseEvent<HTMLButtonElement>) => void;
Expand All @@ -54,6 +55,7 @@ const Button: React.FC<ButtonProps> = ({
className,
iconNameBefore,
iconNameAfter,
id,
type = 'secondary',
size = '',
dataTestid,
Expand All @@ -74,6 +76,7 @@ const Button: React.FC<ButtonProps> = ({

return (
<button
id={id}
className={`
${styles['root']}
c-button
Expand Down
7 changes: 5 additions & 2 deletions libs/core-components/src/lib/Message/Message.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,13 +87,14 @@ const Message = ({
onDismiss,
canDismiss,
isVisible,
tagName,
scope,
type,
}) => {
const typeMap = TYPE_MAP[type];
const scopeMap = SCOPE_MAP[scope || DEFAULT_SCOPE];
const { iconName, iconText, className: typeClassName } = typeMap;
const { role, tagName, className: scopeClassName } = scopeMap;
const { role, tagName: autoTagName, className: scopeClassName } = scopeMap;

const hasDismissSupport = scope === 'section';

Expand Down Expand Up @@ -137,7 +138,7 @@ const Message = ({
// Avoid manually syncing Reactstrap <Fade>'s default props
// eslint-disable-next-line react/jsx-props-no-spreading
{...fadeProps}
tag={tagName}
tag={tagName || autoTagName}
className={`${className} ${containerStyleNames}`}
role={role}
in={isVisible}
Expand Down Expand Up @@ -186,6 +187,8 @@ Message.propTypes = {
onDismiss: PropTypes.func,
/** How to place the message within the layout */
scope: PropTypes.oneOf(SCOPES), // RFE: Require scope; change all instances
/** Message HTML tag (overwrites tag based on scope) */
tagName: PropTypes.string,
/** Message type or severity */
type: PropTypes.oneOf(TYPES).isRequired,
};
Expand Down
91 changes: 0 additions & 91 deletions libs/core-components/src/lib/TextCopyField/TextCopyField.jsx

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
@import url('@tacc/core-styles/dist/settings/color--portal.css');

.input {
/* composes: input from '@tacc/core-styles/dist/components/...form.css'; */
}

.copy-button {
/* So JavaScript can set this (JavaScript also needs the value) */
--transition-duration: 0;
/* WARNING: Must match JavaScript `transitionDuration` */
--transition-duration: 0.15;

transition: color var(--transition-duration),
background-color var(--transition-duration);
Expand Down
75 changes: 75 additions & 0 deletions libs/core-components/src/lib/TextCopyField/TextCopyField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import React, { useCallback, useState } from 'react';

import Button from '../Button';

import styles from './TextCopyField.module.css';

type TextCopyFieldProps = {
value: string;
placeholder?: string;
className?: string;
id?: string;
buttonClassName?: string;
};

const TextCopyField: React.FC<TextCopyFieldProps> = ({
value,
placeholder,
className,
id,
buttonClassName,
}) => {
/* WARNING: Must match CSS `--transition-duration` */
const transitionDuration = 0.15; // second(s)
const stateDuration = 1; // second(s)
const stateTimeout = transitionDuration + stateDuration; // second(s)

const [isCopied, setIsCopied] = useState(false);

const onCopy = useCallback(() => {
navigator.clipboard.writeText(value);
setIsCopied(true);

const timeout = setTimeout(() => {
setIsCopied(false);
clearTimeout(timeout);
}, stateTimeout * 1000);
}, [value, setIsCopied, stateTimeout]);
const isEmpty = !value || value.length === 0;
const onChange = (event: React.ChangeEvent<HTMLInputElement>) => {
// Swallow keyboard events on the Input control, but
// still allow selecting the text. readOnly property of
// Input is not adequate for this purpose because it
// prevents text selection
event.preventDefault();
};

return (
<>
<Button
className={`${styles['copy-button']} ${
isCopied ? styles['is-copied'] : ''
} ${buttonClassName}`}
size="small"
onClick={onCopy}
disabled={isEmpty}
iconNameBefore={isCopied ? 'approved-reverse' : 'link'}
>
Copy
</Button>
<input
id={id}
type="text"
onChange={onChange}
value={value}
className={className}
placeholder={placeholder}
data-testid="input"
disabled
readOnly
/>
</>
);
};

export default TextCopyField;
22 changes: 13 additions & 9 deletions libs/tup-components/src/accounts/ManageAccountMfa.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,9 @@ const MfaUnpair: React.FC<{ pairing: MfaTokenResponse }> = ({ pairing }) => {
)}
<p>
<label htmlFor="current-mfa-token">Enter MFA Token:&nbsp;</label>
<br />
<input
value={currentToken}
autoComplete="off"
onChange={(e) => setCurrentToken(e.target.value)}
id="current-mfa-token"
/>
Expand Down Expand Up @@ -116,6 +116,12 @@ const MfaUnpair: React.FC<{ pairing: MfaTokenResponse }> = ({ pairing }) => {
);
};

const MfaSectionHeader: React.FC = () => (
<div className={styles['tap-header']}>
<strong>MFA Pairing</strong>
</div>
);

export const AccountMfa: React.FC = () => {
const TOKEN_TYPE = {
sms: 'SMS Token',
Expand All @@ -125,13 +131,10 @@ export const AccountMfa: React.FC = () => {
if (isError) {
return (
<>
<div className={styles['tap-header']}>
<strong>MFA Pairing</strong>
</div>
<MfaSectionHeader />
<SectionMessage type="error">
There was an error retrieving your multifactor authentication status.
Your account may be in a non-valid state. if this error persists
please{' '}
Your account may be in a non-valid state. If this error persists,{' '}
<TicketCreateModal display="link">submit a ticket</TicketCreateModal>{' '}
with this information and TACC User Services will assist you.
</SectionMessage>
Expand All @@ -142,9 +145,10 @@ export const AccountMfa: React.FC = () => {
const hasPairing = data?.token?.rollout_state === 'enrolled';
return (
<>
<div className={styles['tap-header']}>
<strong>MFA Pairing</strong>
</div>
<MfaSectionHeader />
<span className={styles['tap-description']}>
Set up multi-factor authentication using a token app or SMS.
</span>
{!hasPairing && (
<Link to="/mfa" className={styles['tap-href']}>
<Button type="primary">Pair Device</Button>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
/* So login form is built off CMS styles */
@import url('@tacc/core-styles/dist/elements/form.cms.css') layer(base);

/* CONTAINER */

.root {
Expand Down Expand Up @@ -54,10 +51,8 @@
display: none;
}
.field {
max-width: unset; /* undo forms.cms.css `input...` */

/* To use larger field inputs, always (not just coarse pointer devices) */
padding: 12px 12px; /* mimic forms.cms.css `@media (pointer: coarse)` */
padding: 12px 12px; /* mimic Core Styles forms.css @media (pointer: coarse) */
}


Expand Down
Loading

0 comments on commit 037b2c4

Please sign in to comment.