Skip to content

Commit

Permalink
[UPM] evgeniy/suisin/utkarsha/upm-393/ add Passkeys for responsive (b…
Browse files Browse the repository at this point in the history
…inary-com#12587)

* feat: Passkeys init commit

* trigger build

* fix: resolve conflicts, update package lock

* chore: test for useIsPasskeySupported hook

* chore: restore jest config

* chore: remove unneccesary comment

* chore: hook code refactor

* chore: upm-405,upm-407,upm-408: not set screen, lern more screen, fetch hook

* chore: review comments, fix for jest config

* fix: types error

* refactor: review comments

* refactor: move get passkeys key hook to hooks from api

* refactor: remove quill

* chore: register passkey, passkeys-list

* fix: publicKeyCredential type

* chore: added passkey icons

* chore: learnmore back button, passkey list css

* chore: refactor flow, added status content, styles fix

* chore: added name to passkey_register

* chore: register request fix

* chore: update not-set page

* chore: success page content update

* chore: added console logs

* chore: update icons

* chore: code refactor, updates with design

* chore: tests for components, code refactor

* chore: useregister refactor

* fix: unnecessary fragment remove

* fix: mock passkey remove

* refactor: status handling, test cases

* fix: time calculation, consolelog remove

* fix: last_used name field

* chore: added console log for tracking registration errors

* chore: initial test for useRegisterPasskey

* chore: passkey modal for errors add

* chore: added modal to status component

* chore: new icon

* chore: effortless login page content

* chore: status wrapper refactor

* chore: error content config init

* chore: removed modal from status component

* chore: remove route function refactor, context value removed

* chore: error flow and show in modal

* chore: style fix for error content

* chore: useregister passkey refactor

* refactor: content change, hook refactor

* chore: never used passkey

* chore: implement reload logic

* refactor: remove usequery for registration

* chore: is passkey_supported fix

* chore: test fix, udpate content

* Suisin/chore: create effortless login modal (#40)

* chore: create effortless login modal

* chore: update test case for effortless login modal

* chore: learn more effortless login, useshowmodal refactor

* chore: fix tests, removed test for useregister

* chore: first login modal logic refactor

* chore: show effortless page tests

* refactor: register hook new logic with additional step, tests

* chore: new creation flow

* chore: update design

* fix: test cases

* chore: unnesesary code line remove

* chore: review comments

* chore: add feature flag for passkeys using growthbook (#41)

* chore: add feature flag for passkeys using growthbook

* feat: add growthbook feature flag handling

* test: modify testcases for useIsPasskeySupported hook

* feat: add a condition to check if growthbook feature i loaded and then trigger checkPaaskeySupport

* fix: remove comment

* fix: remove typecasting and use git add . instead

* fix: typo

* chore: review comments part 1

* chore: review comments part 2

* fix: growthbook fetching value

* chore: null case for passkey feature flag

* chore: review comments

* chore: review comments

* chore: remove isPasskeySupported hook, added logic to client store

* fix: test cases

* fix: show modal hook test

* fix: load passkey list when the feature is off on BE

* fix: modal text blinckig, css full height

* chore: switch off feature flag, add delay for closing modal

* fix: close reminder modal before creation

* fix: failing test

* chore: feature flag fetching

* fix: bullets color for darkmode

* chore: exclude errors coming from terminating process by user side

* chore: separate root for effortless modal, added network status pop up

* chore: trigger build

* fix: tests, modal opening

* fix: effortless modal in signup, tests

* chore: fix modal appearing

* fix: analytics connection trigger

* fix: move effortless ,odal logic to client store

* fix: test for effortless modal

* chore: well-known files update

---------

Co-authored-by: Sui Sin <103026762+suisin-deriv@users.noreply.github.com>
Co-authored-by: utkarsha-deriv <125863995+utkarsha-deriv@users.noreply.github.com>
  • Loading branch information
3 people committed Apr 1, 2024
1 parent e7c12ed commit 6462558
Show file tree
Hide file tree
Showing 79 changed files with 2,733 additions and 164 deletions.
2 changes: 1 addition & 1 deletion jest.config.base.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ module.exports = {
coverageDirectory: './coverage/',
testRegex: '(/__tests__/.*|(\\.)(test|spec))\\.(js|jsx|tsx|ts)?$',
// This is needed to transform es modules imported from node_modules of the target component.
transformIgnorePatterns: ['/node_modules/(?!@enykeev/react-virtualized).+\\.js$'],
transformIgnorePatterns: ['/node_modules/(?!(@enykeev/react-virtualized|@simplewebauthn/browser)).+\\.js$'],
setupFiles: ['<rootDir>/../../jest.setup.js'],
setupFilesAfterEnv: ['<rootDir>/../../setupTests.js'],
testPathIgnorePatterns: ['/integration-tests/', '/component-tests/'],
Expand Down
2 changes: 1 addition & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@ module.exports = {
'^.+\\.(ts|tsx)?$': 'ts-jest',
},
testRegex: '(/__tests__/.*|(\\.)(test|spec))\\.(js|jsx|tsx|ts)?$',
transformIgnorePatterns: ['/node_modules/(?!@enykeev/react-virtualized).+\\.js$'],
transformIgnorePatterns: ['/node_modules/(?!(@enykeev/react-virtualized|@simplewebauthn/browser)).+\\.js$'],
testPathIgnorePatterns: ['/integration-tests/'],
};
15 changes: 15 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions packages/account/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import Routes from './Containers/routes';
import ResetTradingPassword from './Containers/reset-trading-password';
import { NetworkStatusToastErrorPopup } from './Containers/toast-popup';
import { APIProvider } from '@deriv/api';
import { StoreProvider } from '@deriv/stores';
import { TCoreStores } from '@deriv/stores/types';
Expand All @@ -21,6 +22,7 @@ const App = ({ passthrough }: TAppProps) => {

return (
<StoreProvider store={root_store}>
<NetworkStatusToastErrorPopup />
<APIProvider>
<POIProvider>
{Notifications && <Notifications />}
Expand Down
7 changes: 7 additions & 0 deletions packages/account/src/Constants/routes-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { localize } from '@deriv/translations';
import {
AccountLimits,
Passwords,
Passkeys,
PersonalDetails,
TradingAssessment,
FinancialAssessment,
Expand Down Expand Up @@ -111,12 +112,18 @@ const initRoutesConfig = () => [
{
getTitle: () => localize('Security and safety'),
icon: 'IcSecurity',
id: 'security_routes',
subroutes: [
{
path: routes.passwords,
component: Passwords,
getTitle: () => localize('Email and passwords'),
},
{
path: routes.passkeys,
component: Passkeys,
getTitle: () => localize('Passkeys'),
},
{
path: routes.self_exclusion,
component: SelfExclusion,
Expand Down
32 changes: 26 additions & 6 deletions packages/account/src/Containers/Account/account.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import React from 'react';
import { RouteComponentProps, withRouter } from 'react-router-dom';
import { FadeWrapper, Loading } from '@deriv/components';
import { matchRoute, routes as shared_routes } from '@deriv/shared';
import {
deepCopy,
flatten,
matchRoute,
removeExactRouteFromRoutes,
routes as shared_routes,
TRoute as TSharedRoute,
} from '@deriv/shared';
import { observer, useStore } from '@deriv/stores';
import PageOverlayWrapper from './page-overlay-wrapper';
import { TRoute } from '../../Types';
import 'Styles/account.scss';
import { flatten } from 'Helpers/utils';

type TAccountProps = RouteComponentProps & {
routes: Array<TRoute>;
Expand All @@ -30,17 +36,31 @@ const Account = observer(({ history, location, routes }: TAccountProps) => {
landing_company_shortcode,
should_allow_authentication,
should_allow_poinc_authentication,
is_passkey_supported,
} = client;
const { toggleAccountSettings, is_account_settings_visible } = ui;
const { toggleAccountSettings, is_account_settings_visible, is_mobile, is_desktop } = ui;

const [available_routes, setAvailableRoutes] = React.useState(routes);

React.useEffect(() => {
const should_remove_passkeys_route = is_desktop || (is_mobile && !is_passkey_supported);
if (should_remove_passkeys_route) {
const desktop_routes = removeExactRouteFromRoutes(deepCopy(routes) as TSharedRoute[], 'passkeys');
setAvailableRoutes(desktop_routes as TRoute[]);
} else {
setAvailableRoutes(routes);
}
}, [routes, is_desktop, is_mobile, is_passkey_supported]);

// subroutes of a route is structured as an array of arrays
const subroutes = flatten(routes.map(i => i.subroutes));
const subroutes = flatten(available_routes.map(i => i.subroutes));
const selected_content = subroutes.find(r => matchRoute(r, location.pathname));

React.useEffect(() => {
toggleAccountSettings(true);
}, [toggleAccountSettings]);

routes.forEach(menu_item => {
available_routes.forEach(menu_item => {
if (menu_item?.subroutes?.length) {
menu_item.subroutes.forEach(route => {
if (route.path === shared_routes.financial_assessment) {
Expand Down Expand Up @@ -81,7 +101,7 @@ const Account = observer(({ history, location, routes }: TAccountProps) => {
keyname='account-page-wrapper'
>
<div className='account'>
<PageOverlayWrapper routes={routes} subroutes={subroutes} />
<PageOverlayWrapper routes={available_routes} subroutes={subroutes} />
</div>
</FadeWrapper>
);
Expand Down
79 changes: 79 additions & 0 deletions packages/account/src/Containers/toast-popup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import classNames from 'classnames';
import React from 'react';
import ReactDOM from 'react-dom';
import { MobileWrapper, Toast } from '@deriv/components';
import { observer, useStore } from '@deriv/stores';

type TToastPopUp = {
portal_id?: string;
className: string;
} & React.ComponentProps<typeof Toast>;

type TNetworkStatusToastError = {
status: string;
portal_id: string;
message: string;
};

export const ToastPopup = ({
portal_id = 'popup_root',
children,
className,
...props
}: React.PropsWithChildren<TToastPopUp>) => {
const new_portal_id = document.getElementById(portal_id);
if (!new_portal_id) return null;
return ReactDOM.createPortal(
<Toast className={classNames('dc-toast-popup', className)} {...props}>
{children}
</Toast>,
new_portal_id
);
};

/**
* Network status Toast components
*/
const NetworkStatusToastError = ({ status, portal_id, message }: TNetworkStatusToastError) => {
const [is_open, setIsOpen] = React.useState(false);
const new_portal_id = document.getElementById(portal_id);

if (!new_portal_id || !message) return null;

if (!is_open && status !== 'online') {
setIsOpen(true); // open if status === 'blinker' or 'offline'
} else if (is_open && status === 'online') {
setTimeout(() => {
setIsOpen(false);
}, 1500);
}

return ReactDOM.createPortal(
<MobileWrapper>
<Toast
className={classNames({
'dc-toast--blinker': status === 'blinker',
})}
is_open={is_open}
timeout={0}
type='error'
>
{message}
</Toast>
</MobileWrapper>,
new_portal_id
);
};

export const NetworkStatusToastErrorPopup = observer(() => {
const {
common: { network_status },
} = useStore();
return (
<NetworkStatusToastError
portal_id='popup_root'
message={network_status.tooltip}
status={network_status.class}
/>
);
});
3 changes: 0 additions & 3 deletions packages/account/src/Helpers/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -199,9 +199,6 @@ export const isDocumentNumberValid = (document_number: string, document_type: Fo

export const shouldHideHelperImage = (document_id: string) => document_id === IDV_NOT_APPLICABLE_OPTION.id;

// @ts-expect-error as the generic is a Array
export const flatten = <T extends Array<unknown>>(arr: T) => [].concat(...arr);

export const isServerError = (error: unknown): error is TServerError =>
typeof error === 'object' && error !== null && 'code' in error;

Expand Down
Loading

0 comments on commit 6462558

Please sign in to comment.