From 84bb17ba1d68eac13eb49ec7d6431dd6d0acda6b Mon Sep 17 00:00:00 2001 From: Gorjan Petrovski Date: Tue, 27 Jul 2021 11:32:07 +0200 Subject: [PATCH] feat(preferences): 2FA activation dialog with mocked state (#605) --- app/assets/icons/ic-info.svg | 3 + app/assets/javascripts/components/Button.tsx | 28 +++ .../javascripts/components/CircleProgress.tsx | 38 ++++ .../components/CircleProgressTime.tsx | 27 +++ .../javascripts/components/DecoratedInput.tsx | 4 +- app/assets/javascripts/components/Icon.tsx | 2 + .../javascripts/components/IconButton.tsx | 2 +- .../preferences/PreferencesMenu.tsx | 19 -- .../preferences/PreferencesMenuView.tsx | 20 +++ .../preferences/PreferencesView.tsx | 28 ++- .../preferences/components/Content.tsx | 14 +- .../javascripts/preferences/models/index.ts | 2 - .../preferences/models/two-factor-auth.ts | 81 --------- .../preferences/panes/HelpFeedback.tsx | 11 +- .../preferences/panes/Security.tsx | 14 +- .../preferences/panes/TwoFactorAuth.tsx | 110 ------------ .../two-factor-auth/AuthAppInfoPopup.tsx | 59 ++++++ .../panes/two-factor-auth/SaveSecretKey.tsx | 90 ++++++++++ .../panes/two-factor-auth/ScanQRCode.tsx | 78 ++++++++ .../TwoFactorActivationView.tsx | 18 ++ .../two-factor-auth/TwoFactorAuthView.tsx | 53 ++++++ .../panes/two-factor-auth/TwoFactorDialog.tsx | 64 +++++++ .../two-factor-auth/TwoFactorDisabledView.tsx | 14 ++ .../two-factor-auth/TwoFactorEnabledView.tsx | 45 +++++ .../panes/two-factor-auth/Verification.tsx | 65 +++++++ .../two-factor-auth/download-secret-key.tsx | 13 ++ .../panes/two-factor-auth/index.tsx | 10 ++ .../panes/two-factor-auth/model.ts | 168 ++++++++++++++++++ .../preferences.ts => preferences-menu.ts} | 33 ++-- package.json | 2 +- yarn.lock | 8 +- 31 files changed, 845 insertions(+), 278 deletions(-) create mode 100644 app/assets/icons/ic-info.svg create mode 100644 app/assets/javascripts/components/Button.tsx create mode 100644 app/assets/javascripts/components/CircleProgress.tsx create mode 100644 app/assets/javascripts/components/CircleProgressTime.tsx delete mode 100644 app/assets/javascripts/preferences/PreferencesMenu.tsx create mode 100644 app/assets/javascripts/preferences/PreferencesMenuView.tsx delete mode 100644 app/assets/javascripts/preferences/models/index.ts delete mode 100644 app/assets/javascripts/preferences/models/two-factor-auth.ts delete mode 100644 app/assets/javascripts/preferences/panes/TwoFactorAuth.tsx create mode 100644 app/assets/javascripts/preferences/panes/two-factor-auth/AuthAppInfoPopup.tsx create mode 100644 app/assets/javascripts/preferences/panes/two-factor-auth/SaveSecretKey.tsx create mode 100644 app/assets/javascripts/preferences/panes/two-factor-auth/ScanQRCode.tsx create mode 100644 app/assets/javascripts/preferences/panes/two-factor-auth/TwoFactorActivationView.tsx create mode 100644 app/assets/javascripts/preferences/panes/two-factor-auth/TwoFactorAuthView.tsx create mode 100644 app/assets/javascripts/preferences/panes/two-factor-auth/TwoFactorDialog.tsx create mode 100644 app/assets/javascripts/preferences/panes/two-factor-auth/TwoFactorDisabledView.tsx create mode 100644 app/assets/javascripts/preferences/panes/two-factor-auth/TwoFactorEnabledView.tsx create mode 100644 app/assets/javascripts/preferences/panes/two-factor-auth/Verification.tsx create mode 100644 app/assets/javascripts/preferences/panes/two-factor-auth/download-secret-key.tsx create mode 100644 app/assets/javascripts/preferences/panes/two-factor-auth/index.tsx create mode 100644 app/assets/javascripts/preferences/panes/two-factor-auth/model.ts rename app/assets/javascripts/preferences/{models/preferences.ts => preferences-menu.ts} (69%) diff --git a/app/assets/icons/ic-info.svg b/app/assets/icons/ic-info.svg new file mode 100644 index 000000000..47ea73219 --- /dev/null +++ b/app/assets/icons/ic-info.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/assets/javascripts/components/Button.tsx b/app/assets/javascripts/components/Button.tsx new file mode 100644 index 000000000..4a383edda --- /dev/null +++ b/app/assets/javascripts/components/Button.tsx @@ -0,0 +1,28 @@ +import { FunctionComponent } from 'preact'; + +const baseClass = `rounded px-4 py-1.75 font-bold text-sm fit-content cursor-pointer`; + +const normalClass = `${baseClass} bg-default color-text border-solid border-gray-300 border-1 \ +focus:bg-contrast hover:bg-contrast`; +const primaryClass = `${baseClass} no-border bg-info color-info-contrast hover:brightness-130 \ +focus:brightness-130`; + +export const Button: FunctionComponent<{ + className?: string; + type: 'normal' | 'primary'; + label: string; + onClick: () => void; +}> = ({ type, label, className = '', onClick }) => { + const buttonClass = type === 'primary' ? primaryClass : normalClass; + return ( + + ); +}; diff --git a/app/assets/javascripts/components/CircleProgress.tsx b/app/assets/javascripts/components/CircleProgress.tsx new file mode 100644 index 000000000..16fa917dc --- /dev/null +++ b/app/assets/javascripts/components/CircleProgress.tsx @@ -0,0 +1,38 @@ +import { FunctionComponent } from 'preact'; + +export const CircleProgress: FunctionComponent<{ + percent: number; + className?: string; +}> = ({ percent, className = '' }) => { + const size = 16; + const ratioStrokeRadius = 0.25; + const outerRadius = size / 2; + + const radius = outerRadius * (1 - ratioStrokeRadius); + const stroke = outerRadius - radius; + + const circumference = radius * 2 * Math.PI; + const offset = circumference - (percent / 100) * circumference; + + const transition = `transition: 0.35s stroke-dashoffset;`; + const transform = `transform: rotate(-90deg);`; + const transformOrigin = `transform-origin: 50% 50%;`; + const dasharray = `stroke-dasharray: ${circumference} ${circumference};`; + const dashoffset = `stroke-dashoffset: ${offset};`; + const style = `${transition} ${transform} ${transformOrigin} ${dasharray} ${dashoffset}`; + return ( +
+ + + +
+ ); +}; diff --git a/app/assets/javascripts/components/CircleProgressTime.tsx b/app/assets/javascripts/components/CircleProgressTime.tsx new file mode 100644 index 000000000..1f7b6b7d9 --- /dev/null +++ b/app/assets/javascripts/components/CircleProgressTime.tsx @@ -0,0 +1,27 @@ +import { FunctionalComponent } from 'preact'; +import { useEffect, useState } from 'preact/hooks'; +import { CircleProgress } from './CircleProgress'; + +/** + * Circular progress bar which runs in a specified time interval + * @param time - time interval in ms + */ +export const CircleProgressTime: FunctionalComponent<{ time: number }> = ({ + time, +}) => { + const [percent, setPercent] = useState(0); + const interval = time / 100; + useEffect(() => { + const tick = setInterval(() => { + if (percent === 100) { + setPercent(0); + } else { + setPercent(percent + 1); + } + }, interval); + return () => { + clearInterval(tick); + }; + }); + return ; +}; diff --git a/app/assets/javascripts/components/DecoratedInput.tsx b/app/assets/javascripts/components/DecoratedInput.tsx index 860b6b362..f649ab38f 100644 --- a/app/assets/javascripts/components/DecoratedInput.tsx +++ b/app/assets/javascripts/components/DecoratedInput.tsx @@ -26,12 +26,12 @@ export const DecoratedInput: FunctionalComponent = ({ const classes = `${base} ${stateClasses} ${className}`; return ( -
+
{left}
diff --git a/app/assets/javascripts/components/Icon.tsx b/app/assets/javascripts/components/Icon.tsx index c6f9f632c..1a30ee761 100644 --- a/app/assets/javascripts/components/Icon.tsx +++ b/app/assets/javascripts/components/Icon.tsx @@ -25,6 +25,7 @@ import ThemesIcon from '../../icons/ic-themes.svg'; import UserIcon from '../../icons/ic-user.svg'; import CopyIcon from '../../icons/ic-copy.svg'; import DownloadIcon from '../../icons/ic-download.svg'; +import InfoIcon from '../../icons/ic-info.svg'; import { toDirective } from './utils'; import { FunctionalComponent } from 'preact'; @@ -56,6 +57,7 @@ const ICONS = { user: UserIcon, copy: CopyIcon, download: DownloadIcon, + info: InfoIcon, }; export type IconType = keyof typeof ICONS; diff --git a/app/assets/javascripts/components/IconButton.tsx b/app/assets/javascripts/components/IconButton.tsx index 8c0920c64..e15517419 100644 --- a/app/assets/javascripts/components/IconButton.tsx +++ b/app/assets/javascripts/components/IconButton.tsx @@ -27,7 +27,7 @@ export const IconButton: FunctionComponent = ({ }; return (