diff --git a/src/apps/accounts/src/lib/assets/security/index.ts b/src/apps/accounts/src/lib/assets/security/index.ts index 6b2d378b3..80b9111c0 100644 --- a/src/apps/accounts/src/lib/assets/security/index.ts +++ b/src/apps/accounts/src/lib/assets/security/index.ts @@ -1,7 +1,13 @@ import { ReactComponent as MFAImage } from './mfa.svg' +import { ReactComponent as AppleStore } from './apple-store.svg' import diceIdLogo from './dicelogo.png' +import diceIdLogoBig from './dicelogobig.png' +import googlePlay from './google-play.png' export { + AppleStore, diceIdLogo, + diceIdLogoBig, + googlePlay, MFAImage, } diff --git a/src/apps/accounts/src/settings/tabs/AccountSettingsTabs.tsx b/src/apps/accounts/src/settings/tabs/AccountSettingsTabs.tsx index e0a968e3f..2c2b006d4 100644 --- a/src/apps/accounts/src/settings/tabs/AccountSettingsTabs.tsx +++ b/src/apps/accounts/src/settings/tabs/AccountSettingsTabs.tsx @@ -1,7 +1,7 @@ import { Dispatch, FC, SetStateAction, useMemo, useState } from 'react' import { useLocation } from 'react-router-dom' -import { UserProfile } from '~/libs/core' +import { useMemberTraits, UserProfile, UserTraits } from '~/libs/core' import { TabsNavbar } from '~/libs/ui' import { AccountSettingsTabsConfig, AccountSettingsTabViews, getHashFromTabId, getTabIdFromHash } from './config' @@ -22,6 +22,8 @@ const AccountSettingsTabs: FC = (props: AccountSetting const [activeTab, setActiveTab]: [string, Dispatch>] = useState(activeTabHash) + const memberTraits: UserTraits[] | undefined = useMemberTraits(props.profile.handle) + function handleTabChange(tabId: string): void { setActiveTab(tabId) window.location.hash = getHashFromTabId(tabId) @@ -36,7 +38,7 @@ const AccountSettingsTabs: FC = (props: AccountSetting /> {activeTab === AccountSettingsTabViews.account && ( - + )} {activeTab === AccountSettingsTabViews.preferences && ( diff --git a/src/apps/accounts/src/settings/tabs/account/AccountTab.tsx b/src/apps/accounts/src/settings/tabs/account/AccountTab.tsx index f6ceadfc0..536d321a6 100644 --- a/src/apps/accounts/src/settings/tabs/account/AccountTab.tsx +++ b/src/apps/accounts/src/settings/tabs/account/AccountTab.tsx @@ -1,6 +1,6 @@ import { FC } from 'react' -import { UserProfile } from '~/libs/core' +import { UserProfile, UserTraits } from '~/libs/core' import { AccountRole } from './account-role' import { SecuritySection } from './security' @@ -9,6 +9,7 @@ import styles from './AccountTab.module.scss' interface AccountTabProps { profile: UserProfile + memberTraits: UserTraits[] | undefined } const AccountTab: FC = (props: AccountTabProps) => ( @@ -17,9 +18,9 @@ const AccountTab: FC = (props: AccountTabProps) => ( - + - + ) diff --git a/src/apps/accounts/src/settings/tabs/account/security/Security.tsx b/src/apps/accounts/src/settings/tabs/account/security/Security.tsx index 25bca2dbc..6c05311df 100644 --- a/src/apps/accounts/src/settings/tabs/account/security/Security.tsx +++ b/src/apps/accounts/src/settings/tabs/account/security/Security.tsx @@ -1,18 +1,43 @@ -import { Dispatch, FC, SetStateAction, useState } from 'react' +import { Dispatch, FC, SetStateAction, useEffect, useState } from 'react' +import { toast } from 'react-toastify' import { Button, Collapsible, FormToggleSwitch } from '~/libs/ui' import { diceIdLogo, MFAImage, SettingSection } from '~/apps/accounts/src/lib' +import { MemberMFAStatus, updateMemberMFAStatusAsync, useMemberMFAStatus, UserProfile } from '~/libs/core' import { DiceSetupModal } from './dice-setup-modal' import styles from './Security.module.scss' -const Security: FC<{}> = () => { - const [mfaStatus, setMFAStatus]: [boolean, Dispatch>] = useState(false) +interface SecurityProps { + profile: UserProfile +} + +const Security: FC = (props: SecurityProps) => { const [setupDiceModalOpen, setSetupDiceModalOpen]: [boolean, Dispatch>] = useState(false) - function handleUserMFAChange(event: any): void { - console.log('handleUserMFAChange', event) - setMFAStatus(!mfaStatus) + const mfaStatusData: MemberMFAStatus | undefined = useMemberMFAStatus(props.profile.userId) + + const [mfaEnabled, setMFAEnabled]: [boolean, Dispatch>] = useState(false) + + useEffect(() => { + if (mfaStatusData) { + setMFAEnabled(mfaStatusData.mfaEnabled) + } + }, [mfaStatusData]) + + function handleUserMFAChange(): void { + updateMemberMFAStatusAsync(props.profile.userId, { + param: { + mfaEnabled: !mfaEnabled, + }, + }) + .then(() => { + setMFAEnabled(!mfaEnabled) + toast.success('Your Multi Factor Authentication (MFA) status was updated.') + }) + .catch(() => { + toast.error('Something went wrong. Please try again later.') + }) } function handleDiceModalStatus(): void { @@ -38,7 +63,8 @@ const Security: FC<{}> = () => { )} /> @@ -58,6 +84,7 @@ const Security: FC<{}> = () => { size='lg' className={styles.diceIdButton} onClick={handleDiceModalStatus} + disabled={!mfaEnabled || mfaStatusData?.diceEnabled} /> )} /> @@ -65,6 +92,7 @@ const Security: FC<{}> = () => { {setupDiceModalOpen && ( )} diff --git a/src/apps/accounts/src/settings/tabs/account/security/dice-setup-modal/DiceSetupModal.module.scss b/src/apps/accounts/src/settings/tabs/account/security/dice-setup-modal/DiceSetupModal.module.scss new file mode 100644 index 000000000..5855168b2 --- /dev/null +++ b/src/apps/accounts/src/settings/tabs/account/security/dice-setup-modal/DiceSetupModal.module.scss @@ -0,0 +1,30 @@ +@import '@libs/ui/styles/includes'; + +.diceModal { + :global(.react-responsive-modal-closeButton) { + display: flex; + } + + .appSoresWrap { + display: flex; + justify-content: space-evenly; + margin: $sp-4 0; + + .appStoreCard { + display: flex; + flex-direction: column; + align-items: center; + } + } + + .qrCode { + margin: auto; + } + + .ctaButtons { + display: flex; + justify-content: space-between; + width: 100%; + flex: 1; + } +} \ No newline at end of file diff --git a/src/apps/accounts/src/settings/tabs/account/security/dice-setup-modal/DiceSetupModal.tsx b/src/apps/accounts/src/settings/tabs/account/security/dice-setup-modal/DiceSetupModal.tsx index bf3620c68..46e5e940c 100644 --- a/src/apps/accounts/src/settings/tabs/account/security/dice-setup-modal/DiceSetupModal.tsx +++ b/src/apps/accounts/src/settings/tabs/account/security/dice-setup-modal/DiceSetupModal.tsx @@ -1,18 +1,266 @@ -import { FC } from 'react' +import { Dispatch, FC, SetStateAction, useEffect, useState } from 'react' +import { QRCodeSVG } from 'qrcode.react' +import { get, isUndefined, lowerCase } from 'lodash' +import { toast } from 'react-toastify' -import { BaseModal } from '~/libs/ui' +import { BaseModal, Button } from '~/libs/ui' +import { AppleStore, diceIdLogoBig, googlePlay } from '~/apps/accounts/src/lib' +import { DiceConnectionStatus, updateMemberMFAStatusAsync, useDiceIdConnection, UserProfile } from '~/libs/core' +import { EnvironmentConfig } from '~/config' +import { VerificationListener } from './VerificationListener' +import styles from './DiceSetupModal.module.scss' + +const GooglePlayLink: string = 'https://play.google.com/store/apps/details?id=com.diwallet1' +const AppleStoreLink: string = 'https://apps.apple.com/us/app/dice-id/id1548148979' interface DiceSetupModalProps { onClose: () => void + profile: UserProfile } -const DiceSetupModal: FC = (props: DiceSetupModalProps) => ( - -) +const DiceSetupModal: FC = (props: DiceSetupModalProps) => { + const [step, setStep]: [number, Dispatch>] = useState(1) + + const [diceConnectionId, setDiceConnectionId]: [ + number | undefined, + Dispatch> + ] = useState() + + const diceConnection: DiceConnectionStatus | undefined = useDiceIdConnection(props.profile.userId, diceConnectionId) + + const [isVerificationProcessing, setIsVerificationProcessing]: [boolean, Dispatch>] + = useState(false) + + useEffect(() => { + if (diceConnection && !diceConnectionId) { + setDiceConnectionId(diceConnection.id) + } + }, [ + diceConnection, + diceConnectionId, + ]) + + function handleSecondaryButtonClick(): void { + if (step === 1 || step === 4) { + props.onClose() + } else { + setStep(step - 1) + } + } + + function handlePrimaryButtonClick(): void { + if (step >= 3) { + props.onClose() + } else { + setStep(step + 1) + } + } + + function verificationCallback(data: any): void { + if (data.success) { + const userEmail: string = get(data, 'user.profile.Email') + if (!isUndefined(userEmail) && lowerCase(userEmail) === lowerCase(props.profile.email)) { + updateMemberMFAStatusAsync(props.profile.userId, { + param: { + diceEnabled: true, + }, + }) + .then(() => { + setStep(4) + // eslint-disable-next-line max-len + toast.success('Your credentials have been verified and you are all set for MFA using your decentralized identity (DICE ID).') + }) + .catch(() => { + toast.error('Something went wrong. Please try again later.') + }) + } else { + setStep(4) + } + } else { + setStep(5) + } + } + + function onStartProcessing(): void { + setIsVerificationProcessing(true) + } + + return ( + +