Skip to content

Commit 6462558

Browse files
yauheni-derivsuisin-derivutkarsha-deriv
authored
[UPM] evgeniy/suisin/utkarsha/upm-393/ add Passkeys for responsive (#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 <[email protected]> Co-authored-by: utkarsha-deriv <[email protected]>
1 parent e7c12ed commit 6462558

File tree

79 files changed

+2733
-164
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

79 files changed

+2733
-164
lines changed

jest.config.base.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ module.exports = {
1515
coverageDirectory: './coverage/',
1616
testRegex: '(/__tests__/.*|(\\.)(test|spec))\\.(js|jsx|tsx|ts)?$',
1717
// This is needed to transform es modules imported from node_modules of the target component.
18-
transformIgnorePatterns: ['/node_modules/(?!@enykeev/react-virtualized).+\\.js$'],
18+
transformIgnorePatterns: ['/node_modules/(?!(@enykeev/react-virtualized|@simplewebauthn/browser)).+\\.js$'],
1919
setupFiles: ['<rootDir>/../../jest.setup.js'],
2020
setupFilesAfterEnv: ['<rootDir>/../../setupTests.js'],
2121
testPathIgnorePatterns: ['/integration-tests/', '/component-tests/'],

jest.config.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,6 @@ module.exports = {
1717
'^.+\\.(ts|tsx)?$': 'ts-jest',
1818
},
1919
testRegex: '(/__tests__/.*|(\\.)(test|spec))\\.(js|jsx|tsx|ts)?$',
20-
transformIgnorePatterns: ['/node_modules/(?!@enykeev/react-virtualized).+\\.js$'],
20+
transformIgnorePatterns: ['/node_modules/(?!(@enykeev/react-virtualized|@simplewebauthn/browser)).+\\.js$'],
2121
testPathIgnorePatterns: ['/integration-tests/'],
2222
};

package-lock.json

+15
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/account/src/App.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React from 'react';
22
import Routes from './Containers/routes';
33
import ResetTradingPassword from './Containers/reset-trading-password';
4+
import { NetworkStatusToastErrorPopup } from './Containers/toast-popup';
45
import { APIProvider } from '@deriv/api';
56
import { StoreProvider } from '@deriv/stores';
67
import { TCoreStores } from '@deriv/stores/types';
@@ -21,6 +22,7 @@ const App = ({ passthrough }: TAppProps) => {
2122

2223
return (
2324
<StoreProvider store={root_store}>
25+
<NetworkStatusToastErrorPopup />
2426
<APIProvider>
2527
<POIProvider>
2628
{Notifications && <Notifications />}

packages/account/src/Constants/routes-config.ts

+7
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { localize } from '@deriv/translations';
44
import {
55
AccountLimits,
66
Passwords,
7+
Passkeys,
78
PersonalDetails,
89
TradingAssessment,
910
FinancialAssessment,
@@ -111,12 +112,18 @@ const initRoutesConfig = () => [
111112
{
112113
getTitle: () => localize('Security and safety'),
113114
icon: 'IcSecurity',
115+
id: 'security_routes',
114116
subroutes: [
115117
{
116118
path: routes.passwords,
117119
component: Passwords,
118120
getTitle: () => localize('Email and passwords'),
119121
},
122+
{
123+
path: routes.passkeys,
124+
component: Passkeys,
125+
getTitle: () => localize('Passkeys'),
126+
},
120127
{
121128
path: routes.self_exclusion,
122129
component: SelfExclusion,

packages/account/src/Containers/Account/account.tsx

+26-6
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
import React from 'react';
22
import { RouteComponentProps, withRouter } from 'react-router-dom';
33
import { FadeWrapper, Loading } from '@deriv/components';
4-
import { matchRoute, routes as shared_routes } from '@deriv/shared';
4+
import {
5+
deepCopy,
6+
flatten,
7+
matchRoute,
8+
removeExactRouteFromRoutes,
9+
routes as shared_routes,
10+
TRoute as TSharedRoute,
11+
} from '@deriv/shared';
512
import { observer, useStore } from '@deriv/stores';
613
import PageOverlayWrapper from './page-overlay-wrapper';
714
import { TRoute } from '../../Types';
815
import 'Styles/account.scss';
9-
import { flatten } from 'Helpers/utils';
1016

1117
type TAccountProps = RouteComponentProps & {
1218
routes: Array<TRoute>;
@@ -30,17 +36,31 @@ const Account = observer(({ history, location, routes }: TAccountProps) => {
3036
landing_company_shortcode,
3137
should_allow_authentication,
3238
should_allow_poinc_authentication,
39+
is_passkey_supported,
3340
} = client;
34-
const { toggleAccountSettings, is_account_settings_visible } = ui;
41+
const { toggleAccountSettings, is_account_settings_visible, is_mobile, is_desktop } = ui;
42+
43+
const [available_routes, setAvailableRoutes] = React.useState(routes);
44+
45+
React.useEffect(() => {
46+
const should_remove_passkeys_route = is_desktop || (is_mobile && !is_passkey_supported);
47+
if (should_remove_passkeys_route) {
48+
const desktop_routes = removeExactRouteFromRoutes(deepCopy(routes) as TSharedRoute[], 'passkeys');
49+
setAvailableRoutes(desktop_routes as TRoute[]);
50+
} else {
51+
setAvailableRoutes(routes);
52+
}
53+
}, [routes, is_desktop, is_mobile, is_passkey_supported]);
54+
3555
// subroutes of a route is structured as an array of arrays
36-
const subroutes = flatten(routes.map(i => i.subroutes));
56+
const subroutes = flatten(available_routes.map(i => i.subroutes));
3757
const selected_content = subroutes.find(r => matchRoute(r, location.pathname));
3858

3959
React.useEffect(() => {
4060
toggleAccountSettings(true);
4161
}, [toggleAccountSettings]);
4262

43-
routes.forEach(menu_item => {
63+
available_routes.forEach(menu_item => {
4464
if (menu_item?.subroutes?.length) {
4565
menu_item.subroutes.forEach(route => {
4666
if (route.path === shared_routes.financial_assessment) {
@@ -81,7 +101,7 @@ const Account = observer(({ history, location, routes }: TAccountProps) => {
81101
keyname='account-page-wrapper'
82102
>
83103
<div className='account'>
84-
<PageOverlayWrapper routes={routes} subroutes={subroutes} />
104+
<PageOverlayWrapper routes={available_routes} subroutes={subroutes} />
85105
</div>
86106
</FadeWrapper>
87107
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import classNames from 'classnames';
2+
import React from 'react';
3+
import ReactDOM from 'react-dom';
4+
import { MobileWrapper, Toast } from '@deriv/components';
5+
import { observer, useStore } from '@deriv/stores';
6+
7+
type TToastPopUp = {
8+
portal_id?: string;
9+
className: string;
10+
} & React.ComponentProps<typeof Toast>;
11+
12+
type TNetworkStatusToastError = {
13+
status: string;
14+
portal_id: string;
15+
message: string;
16+
};
17+
18+
export const ToastPopup = ({
19+
portal_id = 'popup_root',
20+
children,
21+
className,
22+
...props
23+
}: React.PropsWithChildren<TToastPopUp>) => {
24+
const new_portal_id = document.getElementById(portal_id);
25+
if (!new_portal_id) return null;
26+
return ReactDOM.createPortal(
27+
<Toast className={classNames('dc-toast-popup', className)} {...props}>
28+
{children}
29+
</Toast>,
30+
new_portal_id
31+
);
32+
};
33+
34+
/**
35+
* Network status Toast components
36+
*/
37+
const NetworkStatusToastError = ({ status, portal_id, message }: TNetworkStatusToastError) => {
38+
const [is_open, setIsOpen] = React.useState(false);
39+
const new_portal_id = document.getElementById(portal_id);
40+
41+
if (!new_portal_id || !message) return null;
42+
43+
if (!is_open && status !== 'online') {
44+
setIsOpen(true); // open if status === 'blinker' or 'offline'
45+
} else if (is_open && status === 'online') {
46+
setTimeout(() => {
47+
setIsOpen(false);
48+
}, 1500);
49+
}
50+
51+
return ReactDOM.createPortal(
52+
<MobileWrapper>
53+
<Toast
54+
className={classNames({
55+
'dc-toast--blinker': status === 'blinker',
56+
})}
57+
is_open={is_open}
58+
timeout={0}
59+
type='error'
60+
>
61+
{message}
62+
</Toast>
63+
</MobileWrapper>,
64+
new_portal_id
65+
);
66+
};
67+
68+
export const NetworkStatusToastErrorPopup = observer(() => {
69+
const {
70+
common: { network_status },
71+
} = useStore();
72+
return (
73+
<NetworkStatusToastError
74+
portal_id='popup_root'
75+
message={network_status.tooltip}
76+
status={network_status.class}
77+
/>
78+
);
79+
});

packages/account/src/Helpers/utils.tsx

-3
Original file line numberDiff line numberDiff line change
@@ -199,9 +199,6 @@ export const isDocumentNumberValid = (document_number: string, document_type: Fo
199199

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

202-
// @ts-expect-error as the generic is a Array
203-
export const flatten = <T extends Array<unknown>>(arr: T) => [].concat(...arr);
204-
205202
export const isServerError = (error: unknown): error is TServerError =>
206203
typeof error === 'object' && error !== null && 'code' in error;
207204

0 commit comments

Comments
 (0)