commit
48f4e9845b
After Width: | Height: | Size: 1022 KiB |
@ -0,0 +1,37 @@ |
|||||||
|
import { cloneDeep } from 'lodash'; |
||||||
|
import { LEDGER_TRANSPORT_TYPES } from '../../../shared/constants/hardware-wallets'; |
||||||
|
|
||||||
|
const version = 66; |
||||||
|
|
||||||
|
/** |
||||||
|
* Changes the useLedgerLive boolean property to the ledgerTransportType enum |
||||||
|
*/ |
||||||
|
export default { |
||||||
|
version, |
||||||
|
async migrate(originalVersionedData) { |
||||||
|
const versionedData = cloneDeep(originalVersionedData); |
||||||
|
versionedData.meta.version = version; |
||||||
|
const state = versionedData.data; |
||||||
|
const newState = transformState(state); |
||||||
|
versionedData.data = newState; |
||||||
|
return versionedData; |
||||||
|
}, |
||||||
|
}; |
||||||
|
|
||||||
|
function transformState(state) { |
||||||
|
const defaultTransportType = window.navigator.hid |
||||||
|
? LEDGER_TRANSPORT_TYPES.WEBHID |
||||||
|
: LEDGER_TRANSPORT_TYPES.U2F; |
||||||
|
const useLedgerLive = Boolean(state.PreferencesController?.useLedgerLive); |
||||||
|
const newState = { |
||||||
|
...state, |
||||||
|
PreferencesController: { |
||||||
|
...state?.PreferencesController, |
||||||
|
ledgerTransportType: useLedgerLive |
||||||
|
? LEDGER_TRANSPORT_TYPES.LIVE |
||||||
|
: defaultTransportType, |
||||||
|
}, |
||||||
|
}; |
||||||
|
delete newState.PreferencesController.useLedgerLive; |
||||||
|
return newState; |
||||||
|
} |
@ -0,0 +1,116 @@ |
|||||||
|
import { LEDGER_TRANSPORT_TYPES } from '../../../shared/constants/hardware-wallets'; |
||||||
|
import migration66 from './066'; |
||||||
|
|
||||||
|
describe('migration #66', () => { |
||||||
|
beforeEach(() => { |
||||||
|
jest.resetAllMocks(); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should update the version metadata', async () => { |
||||||
|
const oldStorage = { |
||||||
|
meta: { |
||||||
|
version: 65, |
||||||
|
}, |
||||||
|
data: {}, |
||||||
|
}; |
||||||
|
|
||||||
|
const newStorage = await migration66.migrate(oldStorage); |
||||||
|
expect(newStorage.meta).toStrictEqual({ |
||||||
|
version: 66, |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should set ledgerTransportType to `u2f` if no preferences controller exists and webhid is not available', async () => { |
||||||
|
const oldStorage = { |
||||||
|
meta: {}, |
||||||
|
data: {}, |
||||||
|
}; |
||||||
|
|
||||||
|
const newStorage = await migration66.migrate(oldStorage); |
||||||
|
expect( |
||||||
|
newStorage.data.PreferencesController.ledgerTransportType, |
||||||
|
).toStrictEqual(LEDGER_TRANSPORT_TYPES.U2F); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should set ledgerTransportType to `u2f` if no useLedgerLive property exists and webhid is not available', async () => { |
||||||
|
const oldStorage = { |
||||||
|
meta: {}, |
||||||
|
data: { |
||||||
|
PreferencesController: {}, |
||||||
|
}, |
||||||
|
}; |
||||||
|
|
||||||
|
const newStorage = await migration66.migrate(oldStorage); |
||||||
|
expect( |
||||||
|
newStorage.data.PreferencesController.ledgerTransportType, |
||||||
|
).toStrictEqual(LEDGER_TRANSPORT_TYPES.U2F); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should set ledgerTransportType to `u2f` if useLedgerLive is false and webhid is not available', async () => { |
||||||
|
const oldStorage = { |
||||||
|
meta: {}, |
||||||
|
data: { |
||||||
|
PreferencesController: { |
||||||
|
useLedgerLive: false, |
||||||
|
}, |
||||||
|
}, |
||||||
|
}; |
||||||
|
|
||||||
|
const newStorage = await migration66.migrate(oldStorage); |
||||||
|
expect( |
||||||
|
newStorage.data.PreferencesController.ledgerTransportType, |
||||||
|
).toStrictEqual(LEDGER_TRANSPORT_TYPES.U2F); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should set ledgerTransportType to `webhid` if useLedgerLive is false and webhid is available', async () => { |
||||||
|
const oldStorage = { |
||||||
|
meta: {}, |
||||||
|
data: { |
||||||
|
PreferencesController: { |
||||||
|
useLedgerLive: false, |
||||||
|
}, |
||||||
|
}, |
||||||
|
}; |
||||||
|
jest |
||||||
|
.spyOn(window, 'navigator', 'get') |
||||||
|
.mockImplementation(() => ({ hid: true })); |
||||||
|
const newStorage = await migration66.migrate(oldStorage); |
||||||
|
expect( |
||||||
|
newStorage.data.PreferencesController.ledgerTransportType, |
||||||
|
).toStrictEqual(LEDGER_TRANSPORT_TYPES.WEBHID); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should set ledgerTransportType to `ledgerLive` if useLedgerLive is true', async () => { |
||||||
|
const oldStorage = { |
||||||
|
meta: {}, |
||||||
|
data: { |
||||||
|
PreferencesController: { |
||||||
|
useLedgerLive: true, |
||||||
|
}, |
||||||
|
}, |
||||||
|
}; |
||||||
|
|
||||||
|
const newStorage = await migration66.migrate(oldStorage); |
||||||
|
expect( |
||||||
|
newStorage.data.PreferencesController.ledgerTransportType, |
||||||
|
).toStrictEqual('ledgerLive'); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should not change ledgerTransportType if useLedgerLive is true and webhid is available', async () => { |
||||||
|
const oldStorage = { |
||||||
|
meta: {}, |
||||||
|
data: { |
||||||
|
PreferencesController: { |
||||||
|
useLedgerLive: true, |
||||||
|
}, |
||||||
|
}, |
||||||
|
}; |
||||||
|
jest |
||||||
|
.spyOn(window, 'navigator', 'get') |
||||||
|
.mockImplementation(() => ({ hid: true })); |
||||||
|
const newStorage = await migration66.migrate(oldStorage); |
||||||
|
expect( |
||||||
|
newStorage.data.PreferencesController.ledgerTransportType, |
||||||
|
).toStrictEqual(LEDGER_TRANSPORT_TYPES.LIVE); |
||||||
|
}); |
||||||
|
}); |
@ -0,0 +1,12 @@ |
|||||||
|
export const readAddressAsContract = async (ethQuery, address) => { |
||||||
|
let contractCode; |
||||||
|
try { |
||||||
|
contractCode = await ethQuery.getCode(address); |
||||||
|
} catch (e) { |
||||||
|
contractCode = null; |
||||||
|
} |
||||||
|
|
||||||
|
const isContractAddress = |
||||||
|
contractCode && contractCode !== '0x' && contractCode !== '0x0'; |
||||||
|
return { contractCode, isContractAddress }; |
||||||
|
}; |
@ -0,0 +1,30 @@ |
|||||||
|
const { readAddressAsContract } = require('./contract-utils'); |
||||||
|
|
||||||
|
describe('Contract Utils', () => { |
||||||
|
it('checks is an address is a contract address or not', async () => { |
||||||
|
let mockEthQuery = { |
||||||
|
getCode: () => { |
||||||
|
return '0xa'; |
||||||
|
}, |
||||||
|
}; |
||||||
|
const { isContractAddress } = await readAddressAsContract( |
||||||
|
mockEthQuery, |
||||||
|
'0x76B4aa9Fc4d351a0062c6af8d186DF959D564A84', |
||||||
|
); |
||||||
|
expect(isContractAddress).toStrictEqual(true); |
||||||
|
|
||||||
|
mockEthQuery = { |
||||||
|
getCode: () => { |
||||||
|
return '0x'; |
||||||
|
}, |
||||||
|
}; |
||||||
|
|
||||||
|
const { |
||||||
|
isContractAddress: isNotContractAddress, |
||||||
|
} = await readAddressAsContract( |
||||||
|
mockEthQuery, |
||||||
|
'0x76B4aa9Fc4d351a0062c6af8d186DF959D564A84', |
||||||
|
); |
||||||
|
expect(isNotContractAddress).toStrictEqual(false); |
||||||
|
}); |
||||||
|
}); |
@ -0,0 +1 @@ |
|||||||
|
export { default } from './ledger-instruction-field'; |
@ -0,0 +1,153 @@ |
|||||||
|
import React, { useEffect } from 'react'; |
||||||
|
import { useDispatch, useSelector } from 'react-redux'; |
||||||
|
import PropTypes from 'prop-types'; |
||||||
|
import { |
||||||
|
LEDGER_TRANSPORT_TYPES, |
||||||
|
LEDGER_USB_VENDOR_ID, |
||||||
|
WEBHID_CONNECTED_STATUSES, |
||||||
|
} from '../../../../shared/constants/hardware-wallets'; |
||||||
|
import { |
||||||
|
PLATFORM_FIREFOX, |
||||||
|
ENVIRONMENT_TYPE_FULLSCREEN, |
||||||
|
} from '../../../../shared/constants/app'; |
||||||
|
|
||||||
|
import { |
||||||
|
setLedgerWebHidConnectedStatus, |
||||||
|
getLedgerWebHidConnectedStatus, |
||||||
|
} from '../../../ducks/app/app'; |
||||||
|
|
||||||
|
import Typography from '../../ui/typography/typography'; |
||||||
|
import Button from '../../ui/button'; |
||||||
|
import { useI18nContext } from '../../../hooks/useI18nContext'; |
||||||
|
import { |
||||||
|
COLORS, |
||||||
|
FONT_WEIGHT, |
||||||
|
TYPOGRAPHY, |
||||||
|
} from '../../../helpers/constants/design-system'; |
||||||
|
import Dialog from '../../ui/dialog'; |
||||||
|
import { |
||||||
|
getPlatform, |
||||||
|
getEnvironmentType, |
||||||
|
} from '../../../../app/scripts/lib/util'; |
||||||
|
import { getLedgerTransportType } from '../../../ducks/metamask/metamask'; |
||||||
|
|
||||||
|
const renderInstructionStep = (text, show = true, color = COLORS.PRIMARY3) => { |
||||||
|
return ( |
||||||
|
show && ( |
||||||
|
<Typography |
||||||
|
boxProps={{ margin: 0 }} |
||||||
|
color={color} |
||||||
|
fontWeight={FONT_WEIGHT.BOLD} |
||||||
|
variant={TYPOGRAPHY.H7} |
||||||
|
> |
||||||
|
{text} |
||||||
|
</Typography> |
||||||
|
) |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default function LedgerInstructionField({ showDataInstruction }) { |
||||||
|
const t = useI18nContext(); |
||||||
|
const dispatch = useDispatch(); |
||||||
|
|
||||||
|
const webHidConnectedStatus = useSelector(getLedgerWebHidConnectedStatus); |
||||||
|
const ledgerTransportType = useSelector(getLedgerTransportType); |
||||||
|
const environmentType = getEnvironmentType(); |
||||||
|
const environmentTypeIsFullScreen = |
||||||
|
environmentType === ENVIRONMENT_TYPE_FULLSCREEN; |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
const initialConnectedDeviceCheck = async () => { |
||||||
|
if ( |
||||||
|
ledgerTransportType === LEDGER_TRANSPORT_TYPES.WEBHID && |
||||||
|
webHidConnectedStatus !== WEBHID_CONNECTED_STATUSES.CONNECTED |
||||||
|
) { |
||||||
|
const devices = await window.navigator.hid.getDevices(); |
||||||
|
const webHidIsConnected = devices.some( |
||||||
|
(device) => device.vendorId === Number(LEDGER_USB_VENDOR_ID), |
||||||
|
); |
||||||
|
dispatch( |
||||||
|
setLedgerWebHidConnectedStatus( |
||||||
|
webHidIsConnected |
||||||
|
? WEBHID_CONNECTED_STATUSES.CONNECTED |
||||||
|
: WEBHID_CONNECTED_STATUSES.NOT_CONNECTED, |
||||||
|
), |
||||||
|
); |
||||||
|
} |
||||||
|
}; |
||||||
|
initialConnectedDeviceCheck(); |
||||||
|
}, [dispatch, ledgerTransportType, webHidConnectedStatus]); |
||||||
|
|
||||||
|
const usingLedgerLive = ledgerTransportType === LEDGER_TRANSPORT_TYPES.LIVE; |
||||||
|
const usingWebHID = ledgerTransportType === LEDGER_TRANSPORT_TYPES.WEBHID; |
||||||
|
|
||||||
|
const isFirefox = getPlatform() === PLATFORM_FIREFOX; |
||||||
|
|
||||||
|
return ( |
||||||
|
<div> |
||||||
|
<div className="confirm-detail-row"> |
||||||
|
<Dialog type="message"> |
||||||
|
<div className="ledger-live-dialog"> |
||||||
|
{renderInstructionStep(t('ledgerConnectionInstructionHeader'))} |
||||||
|
{renderInstructionStep( |
||||||
|
`- ${t('ledgerConnectionInstructionStepOne')}`, |
||||||
|
!isFirefox && usingLedgerLive, |
||||||
|
)} |
||||||
|
{renderInstructionStep( |
||||||
|
`- ${t('ledgerConnectionInstructionStepTwo')}`, |
||||||
|
!isFirefox && usingLedgerLive, |
||||||
|
)} |
||||||
|
{renderInstructionStep( |
||||||
|
`- ${t('ledgerConnectionInstructionStepThree')}`, |
||||||
|
)} |
||||||
|
{renderInstructionStep( |
||||||
|
`- ${t('ledgerConnectionInstructionStepFour')}`, |
||||||
|
showDataInstruction, |
||||||
|
)} |
||||||
|
{renderInstructionStep( |
||||||
|
<span> |
||||||
|
<Button |
||||||
|
type="link" |
||||||
|
onClick={async () => { |
||||||
|
if (environmentTypeIsFullScreen) { |
||||||
|
const connectedDevices = await window.navigator.hid.requestDevice( |
||||||
|
{ |
||||||
|
filters: [{ vendorId: LEDGER_USB_VENDOR_ID }], |
||||||
|
}, |
||||||
|
); |
||||||
|
const webHidIsConnected = connectedDevices.some( |
||||||
|
(device) => |
||||||
|
device.vendorId === Number(LEDGER_USB_VENDOR_ID), |
||||||
|
); |
||||||
|
dispatch( |
||||||
|
setLedgerWebHidConnectedStatus({ |
||||||
|
webHidConnectedStatus: webHidIsConnected |
||||||
|
? WEBHID_CONNECTED_STATUSES.CONNECTED |
||||||
|
: WEBHID_CONNECTED_STATUSES.NOT_CONNECTED, |
||||||
|
}), |
||||||
|
); |
||||||
|
} else { |
||||||
|
global.platform.openExtensionInBrowser(null, null, true); |
||||||
|
} |
||||||
|
}} |
||||||
|
> |
||||||
|
{environmentTypeIsFullScreen |
||||||
|
? t('clickToConnectLedgerViaWebHID') |
||||||
|
: t('openFullScreenForLedgerWebHid')} |
||||||
|
</Button> |
||||||
|
</span>, |
||||||
|
usingWebHID && |
||||||
|
webHidConnectedStatus === |
||||||
|
WEBHID_CONNECTED_STATUSES.NOT_CONNECTED, |
||||||
|
COLORS.SECONDARY1, |
||||||
|
)} |
||||||
|
</div> |
||||||
|
</Dialog> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
LedgerInstructionField.propTypes = { |
||||||
|
showDataInstruction: PropTypes.bool, |
||||||
|
}; |
@ -0,0 +1,41 @@ |
|||||||
|
.onboarding-metametrics { |
||||||
|
width: 600px; |
||||||
|
|
||||||
|
ul { |
||||||
|
margin: 24px 0 0 0; |
||||||
|
|
||||||
|
li { |
||||||
|
padding-bottom: 20px; |
||||||
|
display: flex; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.fa { |
||||||
|
width: 16px; |
||||||
|
} |
||||||
|
|
||||||
|
.fa-check { |
||||||
|
margin-inline-end: 12px; |
||||||
|
color: #1acc56; |
||||||
|
} |
||||||
|
|
||||||
|
.fa-times { |
||||||
|
margin-inline-end: 12px; |
||||||
|
color: #d0021b; |
||||||
|
} |
||||||
|
|
||||||
|
&__terms a { |
||||||
|
color: $Blue-500; |
||||||
|
} |
||||||
|
|
||||||
|
&__buttons { |
||||||
|
margin: 24px auto 0 auto; |
||||||
|
justify-content: space-between; |
||||||
|
display: flex; |
||||||
|
|
||||||
|
button { |
||||||
|
margin-bottom: 24px; |
||||||
|
width: 200px; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,152 @@ |
|||||||
|
import React, { useContext } from 'react'; |
||||||
|
import { useDispatch, useSelector } from 'react-redux'; |
||||||
|
import { useHistory } from 'react-router-dom'; |
||||||
|
import Typography from '../../../components/ui/typography/typography'; |
||||||
|
import { |
||||||
|
TYPOGRAPHY, |
||||||
|
FONT_WEIGHT, |
||||||
|
TEXT_ALIGN, |
||||||
|
COLORS, |
||||||
|
} from '../../../helpers/constants/design-system'; |
||||||
|
import Button from '../../../components/ui/button'; |
||||||
|
import { useI18nContext } from '../../../hooks/useI18nContext'; |
||||||
|
import { setParticipateInMetaMetrics } from '../../../store/actions'; |
||||||
|
import { |
||||||
|
getFirstTimeFlowTypeRoute, |
||||||
|
getFirstTimeFlowType, |
||||||
|
getParticipateInMetaMetrics, |
||||||
|
} from '../../../selectors'; |
||||||
|
|
||||||
|
import { MetaMetricsContext } from '../../../contexts/metametrics'; |
||||||
|
|
||||||
|
const firstTimeFlowTypeNameMap = { |
||||||
|
create: 'Selected Create New Wallet', |
||||||
|
import: 'Selected Import Wallet', |
||||||
|
}; |
||||||
|
|
||||||
|
export default function OnboardingMetametrics() { |
||||||
|
const t = useI18nContext(); |
||||||
|
const dispatch = useDispatch(); |
||||||
|
const history = useHistory(); |
||||||
|
|
||||||
|
const nextRoute = useSelector(getFirstTimeFlowTypeRoute); |
||||||
|
const firstTimeFlowType = useSelector(getFirstTimeFlowType); |
||||||
|
|
||||||
|
const participateInMetaMetrics = useSelector(getParticipateInMetaMetrics); |
||||||
|
const firstTimeSelectionMetaMetricsName = |
||||||
|
firstTimeFlowTypeNameMap[firstTimeFlowType]; |
||||||
|
|
||||||
|
const metricsEvent = useContext(MetaMetricsContext); |
||||||
|
|
||||||
|
const onConfirm = async () => { |
||||||
|
const [, metaMetricsId] = await dispatch(setParticipateInMetaMetrics(true)); |
||||||
|
|
||||||
|
try { |
||||||
|
if (!participateInMetaMetrics) { |
||||||
|
metricsEvent({ |
||||||
|
eventOpts: { |
||||||
|
category: 'Onboarding', |
||||||
|
action: 'Metrics Option', |
||||||
|
name: 'Metrics Opt In', |
||||||
|
}, |
||||||
|
isOptIn: true, |
||||||
|
flushImmediately: true, |
||||||
|
}); |
||||||
|
} |
||||||
|
metricsEvent({ |
||||||
|
eventOpts: { |
||||||
|
category: 'Onboarding', |
||||||
|
action: 'Import or Create', |
||||||
|
name: firstTimeSelectionMetaMetricsName, |
||||||
|
}, |
||||||
|
isOptIn: true, |
||||||
|
metaMetricsId, |
||||||
|
flushImmediately: true, |
||||||
|
}); |
||||||
|
} finally { |
||||||
|
history.push(nextRoute); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const onCancel = async () => { |
||||||
|
await dispatch(setParticipateInMetaMetrics(false)); |
||||||
|
|
||||||
|
try { |
||||||
|
if (!participateInMetaMetrics) { |
||||||
|
metricsEvent({ |
||||||
|
eventOpts: { |
||||||
|
category: 'Onboarding', |
||||||
|
action: 'Metrics Option', |
||||||
|
name: 'Metrics Opt Out', |
||||||
|
}, |
||||||
|
isOptIn: true, |
||||||
|
flushImmediately: true, |
||||||
|
}); |
||||||
|
} |
||||||
|
} finally { |
||||||
|
history.push(nextRoute); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="onboarding-metametrics"> |
||||||
|
<Typography |
||||||
|
variant={TYPOGRAPHY.H2} |
||||||
|
align={TEXT_ALIGN.CENTER} |
||||||
|
fontWeight={FONT_WEIGHT.BOLD} |
||||||
|
> |
||||||
|
{t('metametricsTitle')} |
||||||
|
</Typography> |
||||||
|
<Typography align={TEXT_ALIGN.CENTER}> |
||||||
|
{t('metametricsOptInDescription2')} |
||||||
|
</Typography> |
||||||
|
<ul> |
||||||
|
<li> |
||||||
|
<i className="fa fa-check" /> |
||||||
|
{t('metametricsCommitmentsAllowOptOut2')} |
||||||
|
</li> |
||||||
|
<li> |
||||||
|
<i className="fa fa-check" /> |
||||||
|
{t('metametricsCommitmentsSendAnonymizedEvents')} |
||||||
|
</li> |
||||||
|
<li> |
||||||
|
<i className="fa fa-times" /> |
||||||
|
{t('metametricsCommitmentsNeverCollect')} |
||||||
|
</li> |
||||||
|
<li> |
||||||
|
<i className="fa fa-times" /> |
||||||
|
{t('metametricsCommitmentsNeverIP')} |
||||||
|
</li> |
||||||
|
<li> |
||||||
|
<i className="fa fa-times" /> |
||||||
|
{t('metametricsCommitmentsNeverSell')} |
||||||
|
</li> |
||||||
|
</ul> |
||||||
|
<Typography |
||||||
|
color={COLORS.UI4} |
||||||
|
align={TEXT_ALIGN.CENTER} |
||||||
|
variant={TYPOGRAPHY.H6} |
||||||
|
className="onboarding-metametrics__terms" |
||||||
|
> |
||||||
|
{t('gdprMessage', [ |
||||||
|
<a |
||||||
|
key="metametrics-bottom-text-wrapper" |
||||||
|
href="https://metamask.io/privacy.html" |
||||||
|
target="_blank" |
||||||
|
rel="noopener noreferrer" |
||||||
|
> |
||||||
|
{t('gdprMessagePrivacyPolicy')} |
||||||
|
</a>, |
||||||
|
])} |
||||||
|
</Typography> |
||||||
|
<div className="onboarding-metametrics__buttons"> |
||||||
|
<Button type="secondary" onClick={onCancel}> |
||||||
|
{t('noThanks')} |
||||||
|
</Button> |
||||||
|
<Button type="primary" onClick={onConfirm}> |
||||||
|
{t('affirmAgree')} |
||||||
|
</Button> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
); |
||||||
|
} |
@ -0,0 +1,9 @@ |
|||||||
|
import React from 'react'; |
||||||
|
import OnboardingMetametrics from './metametrics'; |
||||||
|
|
||||||
|
export default { |
||||||
|
title: 'Onboarding', |
||||||
|
id: __filename, |
||||||
|
}; |
||||||
|
|
||||||
|
export const OnboardingComponent = () => <OnboardingMetametrics />; |
@ -0,0 +1,23 @@ |
|||||||
|
.onboarding-pin-extension { |
||||||
|
max-width: 800px; |
||||||
|
|
||||||
|
.control-dots .dot { |
||||||
|
background: $ui-2; |
||||||
|
box-shadow: none; |
||||||
|
|
||||||
|
&.selected { |
||||||
|
background: $ui-4; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
&__diagram { |
||||||
|
margin: 24px auto; |
||||||
|
width: 799px; |
||||||
|
height: 320px; |
||||||
|
} |
||||||
|
|
||||||
|
&__buttons { |
||||||
|
max-width: 50%; |
||||||
|
margin: 40px auto 0 auto; |
||||||
|
} |
||||||
|
} |
File diff suppressed because one or more lines are too long
@ -0,0 +1,75 @@ |
|||||||
|
import React, { useState } from 'react'; |
||||||
|
import { useHistory } from 'react-router-dom'; |
||||||
|
|
||||||
|
import { Carousel } from 'react-responsive-carousel'; |
||||||
|
import Typography from '../../../components/ui/typography/typography'; |
||||||
|
import { useI18nContext } from '../../../hooks/useI18nContext'; |
||||||
|
import Button from '../../../components/ui/button'; |
||||||
|
import { |
||||||
|
TYPOGRAPHY, |
||||||
|
FONT_WEIGHT, |
||||||
|
TEXT_ALIGN, |
||||||
|
} from '../../../helpers/constants/design-system'; |
||||||
|
import { DEFAULT_ROUTE } from '../../../helpers/constants/routes'; |
||||||
|
import OnboardingPinBillboard from './pin-billboard'; |
||||||
|
|
||||||
|
export default function OnboardingPinExtension() { |
||||||
|
const t = useI18nContext(); |
||||||
|
const [selectedIndex, setSelectedIndex] = useState(0); |
||||||
|
const history = useHistory(); |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="onboarding-pin-extension"> |
||||||
|
<Typography |
||||||
|
variant={TYPOGRAPHY.H2} |
||||||
|
align={TEXT_ALIGN.CENTER} |
||||||
|
fontWeight={FONT_WEIGHT.BOLD} |
||||||
|
> |
||||||
|
{t('onboardingPinExtensionTitle')} |
||||||
|
</Typography> |
||||||
|
<Carousel |
||||||
|
selectedItem={selectedIndex} |
||||||
|
showThumbs={false} |
||||||
|
showStatus={false} |
||||||
|
showArrows={false} |
||||||
|
> |
||||||
|
<div> |
||||||
|
<Typography align={TEXT_ALIGN.CENTER}> |
||||||
|
{t('onboardingPinExtensionDescription')} |
||||||
|
</Typography> |
||||||
|
<div className="onboarding-pin-extension__diagram"> |
||||||
|
<OnboardingPinBillboard /> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div> |
||||||
|
<Typography align={TEXT_ALIGN.CENTER}> |
||||||
|
{t('onboardingPinExtensionDescription2')} |
||||||
|
</Typography> |
||||||
|
<Typography align={TEXT_ALIGN.CENTER}> |
||||||
|
{t('onboardingPinExtensionDescription3')} |
||||||
|
</Typography> |
||||||
|
<img |
||||||
|
src="/images/onboarding-pin-browser.svg" |
||||||
|
width="799" |
||||||
|
height="320" |
||||||
|
alt="" |
||||||
|
/> |
||||||
|
</div> |
||||||
|
</Carousel> |
||||||
|
<div className="onboarding-pin-extension__buttons"> |
||||||
|
<Button |
||||||
|
type="primary" |
||||||
|
onClick={() => { |
||||||
|
if (selectedIndex === 0) { |
||||||
|
setSelectedIndex(1); |
||||||
|
} else { |
||||||
|
history.push(DEFAULT_ROUTE); |
||||||
|
} |
||||||
|
}} |
||||||
|
> |
||||||
|
{selectedIndex === 0 ? t('next') : t('done')} |
||||||
|
</Button> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
); |
||||||
|
} |
@ -0,0 +1,9 @@ |
|||||||
|
import React from 'react'; |
||||||
|
import OnboardingPinExtension from './pin-extension'; |
||||||
|
|
||||||
|
export default { |
||||||
|
title: 'Onboarding', |
||||||
|
id: __filename, |
||||||
|
}; |
||||||
|
|
||||||
|
export const OnboardingComponent = () => <OnboardingPinExtension />; |
Loading…
Reference in new issue